New upstream version 0.16.3.1+ds1
authorDennis Braun <d_braun@kabelmail.de>
Mon, 24 Aug 2020 17:04:31 +0000 (19:04 +0200)
committerDennis Braun <d_braun@kabelmail.de>
Mon, 24 Aug 2020 17:04:31 +0000 (19:04 +0200)
233 files changed:
ChangeLog
Makefile.am
README.md
configure.ac
create_source_tarball.sh [new file with mode: 0755]
src/core/audioBuffer.cpp
src/core/audioBuffer.h
src/core/channels/audioReceiver.cpp [new file with mode: 0644]
src/core/channels/audioReceiver.h [new file with mode: 0644]
src/core/channels/channel.cpp
src/core/channels/channel.h
src/core/channels/channelManager.cpp
src/core/channels/channelManager.h
src/core/channels/masterChannel.cpp [deleted file]
src/core/channels/masterChannel.h [deleted file]
src/core/channels/midiActionRecorder.cpp [new file with mode: 0644]
src/core/channels/midiActionRecorder.h [new file with mode: 0644]
src/core/channels/midiChannel.cpp [deleted file]
src/core/channels/midiChannel.h [deleted file]
src/core/channels/midiChannelProc.cpp [deleted file]
src/core/channels/midiChannelProc.h [deleted file]
src/core/channels/midiController.cpp [new file with mode: 0644]
src/core/channels/midiController.h [new file with mode: 0644]
src/core/channels/midiLearner.cpp [new file with mode: 0644]
src/core/channels/midiLearner.h [new file with mode: 0644]
src/core/channels/midiLighter.cpp [new file with mode: 0644]
src/core/channels/midiLighter.h [new file with mode: 0644]
src/core/channels/midiReceiver.cpp [new file with mode: 0644]
src/core/channels/midiReceiver.h [new file with mode: 0644]
src/core/channels/midiSender.cpp [new file with mode: 0644]
src/core/channels/midiSender.h [new file with mode: 0644]
src/core/channels/sampleActionRecorder.cpp [new file with mode: 0644]
src/core/channels/sampleActionRecorder.h [new file with mode: 0644]
src/core/channels/sampleChannel.cpp [deleted file]
src/core/channels/sampleChannel.h [deleted file]
src/core/channels/sampleChannelProc.cpp [deleted file]
src/core/channels/sampleChannelProc.h [deleted file]
src/core/channels/sampleChannelRec.cpp [deleted file]
src/core/channels/sampleChannelRec.h [deleted file]
src/core/channels/sampleController.cpp [new file with mode: 0644]
src/core/channels/sampleController.h [new file with mode: 0644]
src/core/channels/samplePlayer.cpp [new file with mode: 0644]
src/core/channels/samplePlayer.h [new file with mode: 0644]
src/core/channels/state.cpp [new file with mode: 0644]
src/core/channels/state.h [new file with mode: 0644]
src/core/channels/waveReader.cpp [new file with mode: 0644]
src/core/channels/waveReader.h [new file with mode: 0644]
src/core/clock.cpp
src/core/clock.h
src/core/conf.cpp
src/core/conf.h
src/core/const.h
src/core/init.cpp
src/core/kernelAudio.cpp
src/core/kernelAudio.h
src/core/kernelMidi.cpp
src/core/kernelMidi.h
src/core/midiDispatcher.cpp
src/core/midiEvent.cpp
src/core/midiEvent.h
src/core/midiMapConf.cpp
src/core/mixer.cpp
src/core/mixer.h
src/core/mixerHandler.cpp
src/core/mixerHandler.h
src/core/model/model.cpp
src/core/model/model.h
src/core/model/storage.cpp
src/core/model/traits.h [new file with mode: 0644]
src/core/patch.cpp
src/core/patch.h
src/core/pluginHost.cpp
src/core/pluginHost.h
src/core/quantizer.cpp [new file with mode: 0644]
src/core/quantizer.h [new file with mode: 0644]
src/core/queue.h
src/core/range.h
src/core/rcuList.h
src/core/recManager.cpp
src/core/recorder.cpp
src/core/recorder.h
src/core/recorderHandler.cpp
src/core/recorderHandler.h
src/core/ringBuffer.h [new file with mode: 0644]
src/core/sequencer.cpp [new file with mode: 0644]
src/core/sequencer.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/glue/actionEditor.cpp
src/glue/actionEditor.h
src/glue/channel.cpp
src/glue/channel.h
src/glue/events.cpp [new file with mode: 0644]
src/glue/events.h [new file with mode: 0644]
src/glue/io.cpp
src/glue/io.h
src/glue/main.cpp
src/glue/main.h
src/glue/plugin.cpp
src/glue/plugin.h
src/glue/recorder.cpp
src/glue/sampleEditor.cpp
src/glue/sampleEditor.h
src/glue/storage.cpp
src/glue/transport.cpp [deleted file]
src/gui/dialogs/actionEditor/baseActionEditor.cpp
src/gui/dialogs/actionEditor/baseActionEditor.h
src/gui/dialogs/actionEditor/midiActionEditor.cpp
src/gui/dialogs/actionEditor/sampleActionEditor.cpp
src/gui/dialogs/actionEditor/sampleActionEditor.h
src/gui/dialogs/channelNameInput.cpp
src/gui/dialogs/channelNameInput.h
src/gui/dialogs/keyGrabber.cpp
src/gui/dialogs/keyGrabber.h
src/gui/dialogs/mainWindow.cpp
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/midiIO/midiOutputBase.cpp
src/gui/dialogs/midiIO/midiOutputBase.h
src/gui/dialogs/midiIO/midiOutputMidiCh.cpp
src/gui/dialogs/midiIO/midiOutputMidiCh.h
src/gui/dialogs/midiIO/midiOutputSampleCh.cpp
src/gui/dialogs/midiIO/midiOutputSampleCh.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/dispatcher.cpp
src/gui/dispatcher.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/noteEditor.cpp
src/gui/elems/actionEditor/noteEditor.h
src/gui/elems/actionEditor/pianoRoll.cpp
src/gui/elems/actionEditor/pianoRoll.h
src/gui/elems/actionEditor/sampleAction.cpp
src/gui/elems/actionEditor/sampleActionEditor.cpp
src/gui/elems/actionEditor/sampleActionEditor.h
src/gui/elems/actionEditor/velocityEditor.cpp
src/gui/elems/actionEditor/velocityEditor.h
src/gui/elems/basics/baseButton.cpp
src/gui/elems/basics/baseButton.h
src/gui/elems/basics/group.cpp [new file with mode: 0644]
src/gui/elems/basics/group.h [new file with mode: 0644]
src/gui/elems/basics/pack.cpp [new file with mode: 0644]
src/gui/elems/basics/pack.h [new file with mode: 0644]
src/gui/elems/basics/scroll.cpp
src/gui/elems/basics/scrollPack.cpp [new file with mode: 0644]
src/gui/elems/basics/scrollPack.h [new file with mode: 0644]
src/gui/elems/config/tabBehaviors.cpp
src/gui/elems/config/tabBehaviors.h
src/gui/elems/mainWindow/keyboard/channel.cpp
src/gui/elems/mainWindow/keyboard/channel.h
src/gui/elems/mainWindow/keyboard/channelButton.cpp
src/gui/elems/mainWindow/keyboard/channelButton.h
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/channelStatus.h
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/midiChannel.h
src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp
src/gui/elems/mainWindow/keyboard/midiChannelButton.h
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/keyboard/sampleChannelButton.h
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/mainTimer.h
src/gui/elems/mainWindow/mainTransport.cpp
src/gui/elems/mainWindow/mainTransport.h
src/gui/elems/midiIO/midiLearner.cpp [new file with mode: 0644]
src/gui/elems/midiIO/midiLearner.h [new file with mode: 0644]
src/gui/elems/midiIO/midiLearnerBase.cpp [deleted file]
src/gui/elems/midiIO/midiLearnerBase.h [deleted file]
src/gui/elems/midiIO/midiLearnerChannel.cpp [deleted file]
src/gui/elems/midiIO/midiLearnerChannel.h [deleted file]
src/gui/elems/midiIO/midiLearnerMaster.cpp [deleted file]
src/gui/elems/midiIO/midiLearnerMaster.h [deleted file]
src/gui/elems/midiIO/midiLearnerPack.cpp [new file with mode: 0644]
src/gui/elems/midiIO/midiLearnerPack.h [new file with mode: 0644]
src/gui/elems/midiIO/midiLearnerPlugin.cpp [deleted file]
src/gui/elems/midiIO/midiLearnerPlugin.h [deleted file]
src/gui/elems/plugin/pluginElement.cpp
src/gui/elems/plugin/pluginElement.h
src/gui/elems/plugin/pluginParameter.cpp
src/gui/elems/plugin/pluginParameter.h
src/gui/elems/sampleEditor/boostTool.cpp
src/gui/elems/sampleEditor/panTool.cpp
src/gui/elems/sampleEditor/panTool.h
src/gui/elems/sampleEditor/pitchTool.cpp
src/gui/elems/sampleEditor/pitchTool.h
src/gui/elems/sampleEditor/rangeTool.cpp
src/gui/elems/sampleEditor/rangeTool.h
src/gui/elems/sampleEditor/shiftTool.cpp
src/gui/elems/sampleEditor/shiftTool.h
src/gui/elems/sampleEditor/volumeTool.cpp
src/gui/elems/sampleEditor/volumeTool.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/updater.cpp
src/utils/gui.cpp
src/utils/math.h
src/utils/string.cpp
src/utils/vector.h
tests/audioBuffer.cpp
tests/sampleChannel.cpp [deleted file]
tests/waveFx.cpp

index 655967fa37906733052b4d4fc8c8cbc618262b1d..76ea8d940bc8359a07e32771959bb00ec83e7e75 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
 --------------------------------------------------------------------------------
 
 
+0.16.3 --- 2020 . 06. 15
+- Non-virtual Channels architecture
+- Added G_DEBUG macro
+- Optimized CPU usage when playing with many channels
+- Increased UI refresh rate to 30 frames per second
+- Improved quantizer precision
+- Simplified behavior when halting channels containing recorded actions 
+- Fix wrong audio sample looping with pitch != 1.0
+- Fix MIDI input master values not stored on quit
+- Fix One-shot press channel mode not working via mouse 
+- Fix Action recording overlap (both live and via Action Editor)
+- Fix crash when loading a project with missing audio files
+- Fix BPM not changing via Jack
+
+
 0.16.2 --- 2020 . 02 . 18
 - Switch to Json for modern C++ library for reading and writing Json data
 - Resizable channels, improved version
index 9bd86b9e02bae36d3023c7231b6aa5459cc6d19f..ee5841677e6f79d75f66451590e26cb39ca7f0f4 100644 (file)
@@ -6,7 +6,7 @@ AUTOMAKE_OPTIONS = foreign
 
 
 cppFlags = -I$(top_srcdir)/src
-cxxFlags = -std=c++14 -Wall
+cxxFlags = -std=c++17 -Wall
 ldAdd =
 ldFlags =
 sourcesExtra = 
@@ -14,6 +14,7 @@ sourcesMain = src/main.cpp
 sourcesCore =                               \
        src/core/const.h                        \
        src/core/queue.h                        \
+       src/core/ringBuffer.h                   \
        src/core/types.h                        \
        src/core/range.h                        \
        src/core/action.h                       \
@@ -25,6 +26,8 @@ sourcesCore =                               \
        src/core/midiEvent.cpp                  \
        src/core/audioBuffer.h                  \
        src/core/audioBuffer.cpp                \
+       src/core/quantizer.h                    \
+       src/core/quantizer.cpp                  \
        src/core/conf.h                         \
        src/core/conf.cpp                       \
        src/core/kernelAudio.h                  \
@@ -35,6 +38,8 @@ sourcesCore =                               \
        src/core/pluginManager.cpp              \
        src/core/mixerHandler.h                 \
        src/core/mixerHandler.cpp               \
+       src/core/sequencer.h                    \
+       src/core/sequencer.cpp                  \
        src/core/init.h                         \
        src/core/init.cpp                       \
        src/core/plugin.h                       \
@@ -61,28 +66,42 @@ sourcesCore =                               \
        src/core/waveManager.cpp                \
        src/core/recManager.h                   \
        src/core/recManager.cpp                 \
+       src/core/channels/state.h               \
+       src/core/channels/state.cpp             \
+       src/core/channels/sampleActionRecorder.h      \
+       src/core/channels/sampleActionRecorder.cpp    \
+       src/core/channels/midiActionRecorder.h      \
+       src/core/channels/midiActionRecorder.cpp    \
+       src/core/channels/waveReader.h          \
+       src/core/channels/waveReader.cpp        \
+       src/core/channels/midiController.h      \
+       src/core/channels/midiController.cpp    \
+       src/core/channels/sampleController.h    \
+       src/core/channels/sampleController.cpp  \
+       src/core/channels/samplePlayer.h        \
+       src/core/channels/samplePlayer.cpp      \
+       src/core/channels/audioReceiver.h       \
+       src/core/channels/audioReceiver.cpp     \
+       src/core/channels/midiLighter.h         \
+       src/core/channels/midiLighter.cpp       \
+       src/core/channels/midiLearner.h         \
+       src/core/channels/midiLearner.cpp       \
+       src/core/channels/midiSender.h          \
+       src/core/channels/midiSender.cpp        \
+       src/core/channels/midiReceiver.h        \
+       src/core/channels/midiReceiver.cpp      \
        src/core/channels/channel.h             \
        src/core/channels/channel.cpp           \
-       src/core/channels/midiChannel.h         \
-       src/core/channels/midiChannel.cpp       \
-       src/core/channels/masterChannel.h       \
-       src/core/channels/masterChannel.cpp     \
-       src/core/channels/sampleChannel.h       \
-       src/core/channels/sampleChannel.cpp     \
        src/core/channels/channelManager.h      \
        src/core/channels/channelManager.cpp    \
-       src/core/channels/sampleChannelProc.h   \
-       src/core/channels/sampleChannelProc.cpp \
-       src/core/channels/sampleChannelRec.h    \
-       src/core/channels/sampleChannelRec.cpp  \
-       src/core/channels/midiChannelProc.h     \
-       src/core/channels/midiChannelProc.cpp   \
        src/core/model/model.h                  \
        src/core/model/model.cpp                \
        src/core/model/storage.h                \
        src/core/model/storage.cpp              \
        src/core/idManager.h                    \
        src/core/idManager.cpp                  \
+       src/glue/events.h                       \
+       src/glue/events.cpp                     \
        src/glue/main.h                         \
        src/glue/main.cpp                       \
        src/glue/io.h                           \
@@ -161,6 +180,10 @@ sourcesCore =                               \
        src/gui/dialogs/midiIO/midiInputChannel.cpp      \
        src/gui/dialogs/midiIO/midiInputMaster.h         \
        src/gui/dialogs/midiIO/midiInputMaster.cpp       \
+       src/gui/elems/midiIO/midiLearner.h               \
+       src/gui/elems/midiIO/midiLearner.cpp             \
+       src/gui/elems/midiIO/midiLearnerPack.h           \
+       src/gui/elems/midiIO/midiLearnerPack.cpp         \
        src/gui/elems/browser.h                              \
        src/gui/elems/browser.cpp                        \
        src/gui/elems/soundMeter.h                               \
@@ -199,7 +222,7 @@ sourcesCore =                               \
        src/gui/elems/actionEditor/envelopePoint.cpp        \
        src/gui/elems/actionEditor/pianoRoll.h              \
        src/gui/elems/actionEditor/pianoRoll.cpp            \
-       src/gui/elems/actionEditor/noteEditor.h                   \
+       src/gui/elems/actionEditor/noteEditor.h                 \
        src/gui/elems/actionEditor/noteEditor.cpp           \
        src/gui/elems/actionEditor/pianoItem.h              \
        src/gui/elems/actionEditor/pianoItem.cpp            \
@@ -249,16 +272,14 @@ sourcesCore =                               \
        src/gui/elems/config/tabBehaviors.cpp  \
        src/gui/elems/config/tabPlugins.h      \
        src/gui/elems/config/tabPlugins.cpp    \
-       src/gui/elems/midiIO/midiLearnerBase.h        \
-       src/gui/elems/midiIO/midiLearnerBase.cpp      \
-       src/gui/elems/midiIO/midiLearnerMaster.h      \
-       src/gui/elems/midiIO/midiLearnerMaster.cpp    \
-       src/gui/elems/midiIO/midiLearnerChannel.h     \
-       src/gui/elems/midiIO/midiLearnerChannel.cpp   \
-       src/gui/elems/midiIO/midiLearnerPlugin.h      \
-       src/gui/elems/midiIO/midiLearnerPlugin.cpp    \
        src/gui/elems/basics/scroll.h          \
        src/gui/elems/basics/scroll.cpp        \
+       src/gui/elems/basics/pack.h            \
+       src/gui/elems/basics/pack.cpp          \
+       src/gui/elems/basics/group.h           \
+       src/gui/elems/basics/group.cpp         \
+       src/gui/elems/basics/scrollPack.h      \
+       src/gui/elems/basics/scrollPack.cpp    \
        src/gui/elems/basics/boxtypes.h        \
        src/gui/elems/basics/boxtypes.cpp      \
        src/gui/elems/basics/baseButton.h      \
@@ -303,7 +324,7 @@ sourcesCore =                               \
        src/utils/ver.cpp                      \
        src/utils/string.h                     \
        src/utils/string.cpp                   \
-       src/deps/rtaudio/RtAudio.h         \
+       src/deps/rtaudio/RtAudio.h             \
        src/deps/rtaudio/RtAudio.cpp
 sourcesTests =                   \
        tests/main.cpp               \
@@ -313,9 +334,7 @@ sourcesTests =                   \
        tests/utils.cpp              \
        tests/recorder.cpp           \
        tests/waveFx.cpp             \
-       tests/audioBuffer.cpp        \
-       tests/sampleChannel.cpp
-
+       tests/audioBuffer.cpp
 if WITH_VST
 
 sourcesExtra += \
@@ -423,7 +442,7 @@ endif
 
 bin_PROGRAMS = giada
 
-giada_SOURCES = $(sourcesCore) $(sourcesMain) $(sourcesExtra)
+giada_SOURCES = $(sourcesCore) $(sourcesExtra) $(sourcesMain)
 giada_CPPFLAGS = $(cppFlags)
 giada_CXXFLAGS = $(cxxFlags)
 giada_LDADD = $(ldAdd)
index 10a1809ef82f51abd46c339db33bdba6ed17dae5..5982ae812e526b92c10b10dc709fff244fa8ca64 100644 (file)
--- a/README.md
+++ b/README.md
@@ -8,31 +8,35 @@
 
 ## What is Giada?
 
-Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets.
+Giada is an open source, minimalistic and hardcore music production tool. Designed for DJs, live performers and electronic musicians.
 
 <p align="center">
 âœ¦âœ¦âœ¦ <a href="http://www.youtube.com/user/GiadaLoopMachine">See Giada in action!</a> âœ¦âœ¦âœ¦
 </p>
 
-![Giada Loop Machine screenshot](http://giadamusic.com/public/img/screenshots/giada-loop-machine-screenshot-14-carousel.jpg)
+![Giada Loop Machine screenshot](https://giadamusic.com/images/giada-canvas.png)
 
 ## Main features
 
+* Your sample player! Load samples from your crates and play them with a computer keyboard or a MIDI controller;
+* Your loop machine! Build your performance in real time by layering audio tracks or MIDI events, driven by the main sequencer;
+* Your song editor! Write songs from scratch or edit existing live recordings with the powerful Action Editor, for a fine-tuned control;
+* Your live recorder! Record sounds from the real world and MIDI events coming from external devices or other apps;
+* Your FX processor! Process samples or audio/MIDI input signals with VST instruments from your plug-ins collection;
+* Your MIDI controller! Control other software or synchronize physical MIDI devices by using Giada as a MIDI master sequencer.
+
+### And more:
+
 * Ultra-lightweight internal design;
 * multi-thread/multi-core support;
 * 32-bit floating point audio engine;
 * ALSA, JACK + Transport, CoreAudio, ASIO and DirectSound full support;
-* high quality internal resampler;
-* unlimited number of channels (controllable via computer keyboard);
-* several playback modes and combinations;
+* unlimited number of channels (optionally controllable via computer keyboard);
 * BPM and beat sync with sample-accurate loop engine;
-* VST and VSTi (instrument) plug-in support;
-* MIDI input and output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps);
-* super-sleek, built-in wave editor;
-* live sampler from external inputs;
-* live action recorder with automatic quantizer;
-* piano Roll editor;
-* portable patch storage system, based on super-hackable JSON files;
+* MIDI output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps);
+* super-sleek, built-in Wave Editor for audio samples and Piano Roll editor for MIDI messages;
+* automatic quantizer;
+* portable project storage system, based on super-hackable JSON files;
 * support for all major uncompressed file formats;
 * test-driven development style supported by [Travis CI](https://travis-ci.org/monocasual/giada) and [Catch](https://github.com/philsquared/Catch)
 * under a constant stage of development;
index d7121b32033cbc77a1bd6e4edb1ee98b59d02d54..f9c7337a5459ea4bcf36937df3aede37a34b86bf 100644 (file)
@@ -58,7 +58,7 @@ AM_CONDITIONAL(FREEBSD, test "x$os" = "xfreebsd")
 AC_ARG_ENABLE(
        [vst],
        AS_HELP_STRING([--enable-vst], [enable vst support]),
-  [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)],
+       [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)],
        [AM_CONDITIONAL(WITH_VST, false)]
 )
 
@@ -70,7 +70,7 @@ AC_ARG_ENABLE(
 AC_ARG_ENABLE(
        [system-catch],
        AS_HELP_STRING([--enable-system-catch], [use system-provided Catch library]),
-  [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)],
+       [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)],
        [AM_CONDITIONAL(WITH_SYSTEM_CATCH, false)]
 )
 
@@ -81,7 +81,7 @@ AC_ARG_ENABLE(
 AC_ARG_ENABLE(
        [debug],
        AS_HELP_STRING([--enable-debug], [enable debug mode (asserts, ...)]),
-  [],
+       [],
        [AC_DEFINE(NDEBUG)]
 )
 
diff --git a/create_source_tarball.sh b/create_source_tarball.sh
new file mode 100755 (executable)
index 0000000..7be10d0
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/env bash
+#
+# Creates source tarballs for giada in the form of
+# 'giada-x.x.x-src.tar.gz' and optionally detached PGP signatures
+# for the created file of the form 'giada-x.x.x-src.tar.gz.asc'.
+# If the environment variable BUILD_DIR is provided, the files will be moved to
+# $BUILD_DIR/, else to the location of this script (the repository folder).
+#
+# Requirements:
+# - git
+# - tar
+# - a writable (user) /tmp folder for mktemp
+# - gnupg >= 2.0.0 (if source tarball signing is requested)
+# - a valid PGP signing key in the keyring (if source tarball signing is
+# requested)
+
+set -euo pipefail
+
+get_absolute_path() {
+    cd "$(dirname "$1")" && pwd -P
+}
+
+validate_project_tag() {
+    if ! git ls-remote -t "${upstream}"| grep -e "${version}$" > /dev/null; then
+        echo "The tag '$version' could not be found in upstream repository (${upstream})."
+        exit 1
+    fi
+}
+
+checkout_project() {
+    echo "Cloning project below working directory ${working_dir}"
+    cd "$working_dir"
+    git clone "$upstream" --branch "$version" \
+                          --single-branch \
+                          --depth=1 \
+                          --recurse-submodules \
+                          "${output_name}"
+}
+
+clean_sources() {
+    cd "${working_dir}/${output_name}"
+    echo "Removing unneeded files and folders..."
+    rm -rfv .git* \
+            .travis* \
+            create_source_tarball.sh
+}
+
+compress_sources() {
+    cd "${working_dir}"
+    tar cvfz "${output_name}.tar.gz" "${output_name}"
+}
+
+move_sources() {
+    cd "${working_dir}"
+    mv -v "${output_name}.tar.gz" "${output_dir}/"
+}
+
+sign_sources() {
+    cd "${output_dir}"
+    gpg --detach-sign \
+        -u "${signer}" \
+        -o "${output_name}.tar.gz.asc" \
+        "${output_name}.tar.gz"
+}
+
+cleanup_working_dir() {
+    echo "Removing working directory: ${working_dir}"
+    rm -rf "${working_dir}"
+}
+
+print_help() {
+    echo "Usage: $0 -v <version tag> -s <signature email or key ID>"
+    exit 1
+}
+
+if [ -n "${BUILD_DIR:-}" ]; then
+    echo "Build dir provided: ${BUILD_DIR}"
+    output_dir="${BUILD_DIR}/"
+    mkdir -p "${output_dir}"
+else
+    output_dir="$(get_absolute_path "$0")"
+fi
+
+upstream="https://github.com/monocasual/giada"
+package_name="giada"
+working_dir="$(mktemp -d)"
+version="$(date '+%Y-%m-%d')"
+output_version=""
+output_name=""
+signer=""
+signature=0
+
+# remove the working directory, no matter what
+trap cleanup_working_dir EXIT
+
+if [ ${#@} -gt 0 ]; then
+    while getopts 'hv:s:' flag; do
+        case "${flag}" in
+            h) print_help
+                ;;
+            s) signer=$OPTARG
+                signature=1
+                ;;
+            v) version=$OPTARG
+                output_version="${version//v}"
+                ;;
+            *)
+                echo "Error! Try '${0} -h'."
+                exit 1
+                ;;
+        esac
+    done
+else
+    print_help
+fi
+
+output_name="${package_name}-${output_version}-src"
+validate_project_tag
+checkout_project
+clean_sources
+compress_sources
+move_sources
+if [ $signature -eq 1 ]; then
+    sign_sources
+fi
+
+exit 0
+
+# vim:set ts=4 sw=4 et:
index ddb9024a76f4ae172d24bb0aa1438a037ddcd450..01b7f8d4a0a487fef8686d7ac12307b5e02db965 100644 (file)
@@ -1,6 +1,32 @@
-#include <new>
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
 #include <cassert>
-#include <cstring>
+#include <algorithm>
 #include "audioBuffer.h"
 
 
@@ -8,13 +34,32 @@ namespace giada {
 namespace m
 {
 AudioBuffer::AudioBuffer()
-       : m_data    (nullptr),
-         m_size    (0),
-         m_channels(0)
+: m_data    (nullptr)
+, m_size    (0)
+, m_channels(0)
 {
 }
 
 
+AudioBuffer::AudioBuffer(Frame size, int channels)
+: AudioBuffer()
+{
+       alloc(size, channels);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioBuffer::AudioBuffer(const AudioBuffer& o)
+: m_data    (new float[o.m_size * o.m_channels])
+, m_size    (o.m_size)
+, m_channels(o.m_channels)
+{
+       std::copy(o.m_data, o.m_data + (o.m_size * o.m_channels), m_data); 
+}
+
+
 /* -------------------------------------------------------------------------- */
 
 
@@ -27,7 +72,7 @@ AudioBuffer::~AudioBuffer()
 /* -------------------------------------------------------------------------- */
 
 
-float* AudioBuffer::operator [](int offset) const
+float* AudioBuffer::operator [](Frame offset) const
 {
        assert(m_data != nullptr);
        assert(offset < m_size);
@@ -38,35 +83,47 @@ float* AudioBuffer::operator [](int offset) const
 /* -------------------------------------------------------------------------- */
 
 
-void AudioBuffer::clear(int a, int b)
+void AudioBuffer::clear(Frame a, Frame b)
 {
        if (m_data == nullptr)
                return;
        if (b == -1) b = m_size;
-       memset(m_data + (a * m_channels), 0, (b - a) * m_channels * sizeof(float));     
+       std::fill_n(m_data + (a * m_channels), (b - a) * m_channels, 0.0);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-int AudioBuffer::countFrames()   const { return m_size; }
-int AudioBuffer::countSamples()  const { return m_size * m_channels; }
-int AudioBuffer::countChannels() const { return m_channels; }
-bool AudioBuffer::isAllocd()     const { return m_data != nullptr; }
+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(int size, int channels)
+void AudioBuffer::alloc(Frame size, int channels)
 {
        free();
        m_size     = size;
        m_channels = channels;
        m_data     = new float[m_size * m_channels];    
-       clear(); // does nothing if m_data == nullptr
+       clear();
 }
 
 
@@ -75,7 +132,7 @@ void AudioBuffer::alloc(int size, int channels)
 
 void AudioBuffer::free()
 {
-       delete[] m_data;  // No check required, delete nullptr does nothing
+       delete[] m_data;
        setData(nullptr, 0, 0);
 }
 
@@ -83,7 +140,7 @@ void AudioBuffer::free()
 /* -------------------------------------------------------------------------- */
 
 
-void AudioBuffer::setData(float* data, int size, int channels)
+void AudioBuffer::setData(float* data, Frame size, int channels)
 {
        m_data     = data;
        m_size     = size;
@@ -107,20 +164,43 @@ void AudioBuffer::moveData(AudioBuffer& b)
 /* -------------------------------------------------------------------------- */
 
 
-void AudioBuffer::copyFrame(int frame, float* values)
+void AudioBuffer::copyData(const float* data, Frame frames, int offset)
 {
        assert(m_data != nullptr);
-       memcpy(m_data + (frame * m_channels), values, m_channels * sizeof(float));
+       assert(frames <= m_size - offset);
+       std::copy_n(data, frames * m_channels, m_data + (offset * m_channels)); 
+}
+
+
+void AudioBuffer::copyData(const AudioBuffer& b, float gain)
+{
+       copyData(b[0], b.countFrames());
+       if (gain != 1.0f)
+               applyGain(gain);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
-void AudioBuffer::copyData(const float* data, int frames, int offset)
+
+void AudioBuffer::addData(const AudioBuffer& b, float gain, Pan pan)
 {
        assert(m_data != nullptr);
-       assert(frames <= m_size - offset);
-       memcpy(m_data + (offset * m_channels), data, frames * m_channels * sizeof(float));
+       assert(countFrames() <= b.countFrames());
+       assert(countChannels() == pan.size());
+
+       for (int i = 0; i < countFrames(); i++)
+               for (int j = 0; j < countChannels(); j++)
+                       (*this)[i][j] += b[i][j] * gain * pan[j];
 }
 
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::applyGain(float g)
+{
+       for (int i = 0; i < countSamples(); i++)
+               m_data[i] *= g;
+}
 }} // giada::m::
\ No newline at end of file
index ab6fcd32971b30fbe8cbcb51c2f5d8a15f24afaa..6c0128b7916f2947edd1af88b259590dab7c6e03 100644 (file)
@@ -1,16 +1,59 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_AUDIO_BUFFER_H
 #define G_AUDIO_BUFFER_H
 
 
+#include <array>
+#include "core/types.h"
+#include "core/const.h"
+
+
 namespace giada {
 namespace m
 {
-/* TODO - this class needs a serious modern C++ lifting */
 class AudioBuffer
 {
 public:
 
+       using Pan = std::array<float, G_MAX_IO_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(const AudioBuffer& o);
        ~AudioBuffer();
 
        /* operator []
@@ -26,32 +69,34 @@ public:
 
        float* operator [](int offset) const;
 
-       int countFrames() const;
+       Frame countFrames() const;
        int countSamples() const;
        int countChannels() const;
        bool isAllocd() const;
 
-       void alloc(int size, int channels);
+       /* getPeak
+       Returns the highest value from any channel. */
+       
+       float getPeak() const;
+
+       void alloc(Frame size, int channels);
        void free();
 
        /* copyData
        Copies 'frames' frames from the new 'data' into m_data, and fills m_data 
-       starting from frame 'offset'. It takes for granted that the new data contains 
-       the same number of channels than m_channels. */
-
-       void copyData(const float* data, int frames, int offset=0);
+       starting from frame 'offset'. The new data MUST contain the same number of 
+       channels than m_channels. */
 
-       /* copyFrame
-       Copies data pointed by 'values' into m_data[frame]. It takes for granted that
-       'values' contains the same number of channels than m_channels. */
+       void copyData(const float* data, Frame frames, int offset=0);
 
-       void copyFrame(int frame, float* values);
+       void copyData(const AudioBuffer& b, float gain=1.0f);
+       void addData(const AudioBuffer& b, float gain=1.0f, Pan pan={1.0f, 1.0f});
 
        /* setData
        Borrow 'data' as new m_data. Makes sure not to delete the data 'data' points
        to while using it. Set it back to nullptr when done. */
 
-       void setData(float* data, int size, int channels);
+       void setData(float* data, Frame size, int channels);
 
        /* moveData
        Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer. */
@@ -62,12 +107,14 @@ public:
        Clears the internal data by setting all bytes to 0.0f. Optional parameters
        'a' and 'b' set the range. */
        
-       void clear(int a=0, int b=-1);
+       void clear(Frame a=0, Frame b=-1);
+
+       void applyGain(float g);
 
 private:
 
        float* m_data;
-       int    m_size;     // in frames    
+       Frame  m_size;
        int    m_channels;
 };
 
diff --git a/src/core/channels/audioReceiver.cpp b/src/core/channels/audioReceiver.cpp
new file mode 100644 (file)
index 0000000..7d8a118
--- /dev/null
@@ -0,0 +1,78 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "audioReceiver.h"
+
+
+namespace giada {
+namespace m 
+{
+AudioReceiver::AudioReceiver(ChannelState* c)
+: state         (std::make_unique<AudioReceiverState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiver::AudioReceiver(const patch::Channel& p, ChannelState* c)
+: state         (std::make_unique<AudioReceiverState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiver::AudioReceiver(const AudioReceiver& o, ChannelState* c)
+: state         (std::make_unique<AudioReceiverState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioReceiver::render(const 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). */
+
+       bool armed        = m_channelState->armed.load();
+       bool inputMonitor = state->inputMonitor.load();
+
+       if (armed && inputMonitor)
+               m_channelState->buffer.addData(in);  // add, don't overwrite
+}
+}} // giada::m::
diff --git a/src/core/channels/audioReceiver.h b/src/core/channels/audioReceiver.h
new file mode 100644 (file)
index 0000000..a285f92
--- /dev/null
@@ -0,0 +1,71 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_AUDIO_RECEIVER_H
+#define G_CHANNEL_AUDIO_RECEIVER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace patch
+{
+struct Channel; 
+}
+class  AudioBuffer;
+struct ChannelState;
+struct AudioReceiverState;
+
+/* AudioReceiver 
+Operates on input audio streams for audio recording and input monitor. */
+
+class AudioReceiver
+{
+public:
+
+    AudioReceiver(ChannelState*);
+    AudioReceiver(const patch::Channel&, ChannelState*);
+    AudioReceiver(const AudioReceiver&, ChannelState* c=nullptr);
+
+    void render(const AudioBuffer& in) const;
+
+    /* state
+    Pointer to mutable AudioReceiverState state. */
+
+    std::unique_ptr<AudioReceiverState> state;
+
+private:
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
index 2c98f8bee0c5d80c11cdc3b80128d1ffa6a977fe..16b625b0c63a114b7617f8ac376e50ba98e5ca84 100644 (file)
 
 
 #include <cassert>
-#include "utils/log.h"
-#include "core/channels/channelManager.h"
-#include "core/const.h"
-#include "core/pluginManager.h"
-#include "core/plugin.h"
-#include "core/kernelMidi.h"
-#include "core/patch.h"
-#include "core/clock.h"
-#include "core/wave.h"
-#include "core/mixer.h"
+#include "core/channels/state.h"
 #include "core/mixerHandler.h"
-#include "core/recorderHandler.h"
-#include "core/conf.h"
-#include "core/patch.h"
-#include "core/waveFx.h"
-#include "core/midiMapConf.h"
+#include "core/pluginHost.h"
 #include "channel.h"
 
 
 namespace giada {
 namespace m 
 {
-Channel::Channel(ChannelType type, ChannelStatus playStatus, int bufferSize, 
-       ID columnId, ID id)
-: type           (type),
-  playStatus     (playStatus),
-  recStatus      (ChannelStatus::OFF),
-  columnId       (columnId),
-  id             (id),
-  height         (G_GUI_UNIT),
-  previewMode    (PreviewMode::NONE),
-  pan            (0.5f),
-  volume         (G_DEFAULT_VOL),
-  armed          (false),
-  key            (0),
-  mute           (false),
-  solo           (false),
-  volume_i       (1.0f),
-  volume_d       (0.0f),
-  hasActions     (false),
-  readActions    (false),
-  midiIn         (true),
-  midiInKeyPress (0x0),
-  midiInKeyRel   (0x0),
-  midiInKill     (0x0),
-  midiInArm      (0x0),
-  midiInVolume   (0x0),
-  midiInMute     (0x0),
-  midiInSolo     (0x0),
-  midiInFilter   (-1),
-  midiOutL       (false),
-  midiOutLplaying(0x0),
-  midiOutLmute   (0x0),
-  midiOutLsolo   (0x0)
+Channel::Channel(ChannelType type, ID id, ID columnId, Frame bufferSize)
+: id            (id)
+, state         (std::make_unique<ChannelState>(id, bufferSize))
+, midiLighter   (state.get())
+, m_type        (type)
+, m_columnId    (columnId)
 {
-       buffer.alloc(bufferSize, G_MAX_IO_CHANS);
+       switch (m_type) {
 
+               case ChannelType::SAMPLE:
+                       samplePlayer.emplace(state.get());
+                       audioReceiver.emplace(state.get());
+                       sampleActionRecorder.emplace(state.get(), samplePlayer->state.get());   
+                       break;
+               
+               case ChannelType::PREVIEW:
+                       samplePlayer.emplace(state.get());
+                       break;
+               
+               case ChannelType::MIDI:
+                       midiController.emplace(state.get());
 #ifdef WITH_VST
-
-       midiBuffer.ensureSize(bufferSize);
-
+                       midiReceiver.emplace(state.get());
 #endif
+                       midiSender.emplace(state.get());
+                       midiActionRecorder.emplace(state.get());                
+                       break;  
+               
+               default: break;
+       }
 }
 
 
@@ -95,152 +72,160 @@ Channel::Channel(ChannelType type, ChannelStatus playStatus, int bufferSize,
 
 
 Channel::Channel(const Channel& o)
-: type           (o.type),
-  playStatus     (o.playStatus),
-  recStatus      (o.recStatus),
-  columnId       (o.columnId),
-  id             (o.id),
-  height         (o.height),
-  previewMode    (o.previewMode),
-  pan            (o.pan),
-  volume         (o.volume),
-  armed          (o.armed),
-  name           (o.name),
-  key            (o.key),
-  mute           (o.mute),
-  solo           (o.solo),
-  volume_i       (o.volume_i),
-  volume_d       (o.volume_d),
-  hasActions     (o.hasActions),
-  readActions    (o.readActions),
-  midiIn         (o.midiIn),
-  midiInKeyPress (o.midiInKeyPress),
-  midiInKeyRel   (o.midiInKeyRel),
-  midiInKill     (o.midiInKill),
-  midiInArm      (o.midiInArm),
-  midiInVolume   (o.midiInVolume),
-  midiInMute     (o.midiInMute),
-  midiInSolo     (o.midiInSolo),
-  midiInFilter   (o.midiInFilter),
-  midiOutL       (o.midiOutL),
-  midiOutLplaying(o.midiOutLplaying),
-  midiOutLmute   (o.midiOutLmute),
-  midiOutLsolo   (o.midiOutLsolo)
+: id            (o.id)
 #ifdef WITH_VST
- ,pluginIds      (o.pluginIds)
+, pluginIds     (o.pluginIds)
 #endif
+, state         (std::make_unique<ChannelState>(*o.state))
+, midiLearner   (o.midiLearner)
+, midiLighter   (o.midiLighter, state.get())
+, m_type        (o.m_type)
+, m_columnId    (o.m_columnId)
 {
-       buffer.alloc(o.buffer.countFrames(), G_MAX_IO_CHANS);
+       switch (m_type) {
+
+               case ChannelType::SAMPLE:
+                       samplePlayer.emplace(o.samplePlayer.value(), state.get());
+                       audioReceiver.emplace(o.audioReceiver.value(), state.get());
+                       sampleActionRecorder.emplace(o.sampleActionRecorder.value(), state.get(), samplePlayer->state.get());
+                       break;
+               
+               case ChannelType::PREVIEW:
+                       samplePlayer.emplace(o.samplePlayer.value(), state.get());
+                       break;
+               
+               case ChannelType::MIDI:
+                       midiController.emplace(o.midiController.value(), state.get());
+#ifdef WITH_VST
+                       midiReceiver.emplace(o.midiReceiver.value(), state.get());
+#endif
+                       midiSender.emplace(o.midiSender.value(), state.get());
+                       midiActionRecorder.emplace(o.midiActionRecorder.value(), state.get());
+                       break;
+
+               default: break;
+       }
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-Channel::Channel(const patch::Channel& p, int bufferSize)
-: type           (p.type),
-  playStatus     (p.waveId == 0 && type == ChannelType::SAMPLE ? ChannelStatus::EMPTY : ChannelStatus::OFF),
-  recStatus      (ChannelStatus::OFF),
-  columnId       (p.columnId),
-  id             (p.id),
-  height         (p.height),
-  previewMode    (PreviewMode::NONE),
-  pan            (p.pan),
-  volume         (p.volume),
-  armed          (p.armed),
-  name           (p.name),
-  key            (p.key),
-  mute           (p.mute),
-  solo           (p.solo),
-  volume_i       (1.0),
-  volume_d       (0.0),
-  hasActions     (p.hasActions),
-  readActions    (p.readActions),
-  midiIn         (p.midiIn),
-  midiInKeyPress (p.midiInKeyPress),
-  midiInKeyRel   (p.midiInKeyRel),
-  midiInKill     (p.midiInKill),
-  midiInArm      (p.midiInArm),
-  midiInVolume   (p.midiInVolume),
-  midiInMute     (p.midiInMute),
-  midiInSolo     (p.midiInSolo),
-  midiInFilter   (p.midiInFilter),
-  midiOutL       (p.midiOutL),
-  midiOutLplaying(p.midiOutLplaying),
-  midiOutLmute   (p.midiOutLmute),
-  midiOutLsolo   (p.midiOutLsolo)
+Channel::Channel(const patch::Channel& p, Frame bufferSize)
+: id            (p.id)
 #ifdef WITH_VST
- ,pluginIds      (p.pluginIds)
+, pluginIds     (p.pluginIds)
 #endif
+, state         (std::make_unique<ChannelState>(p, bufferSize))
+, midiLearner   (p)
+, midiLighter   (p, state.get())
+, m_type        (p.type)
+, m_columnId    (p.columnId)
 {
-    buffer.alloc(bufferSize, G_MAX_IO_CHANS);
+       switch (m_type) {
+
+               case ChannelType::SAMPLE:
+                       samplePlayer.emplace(p, state.get());
+                       audioReceiver.emplace(p, state.get());
+                       sampleActionRecorder.emplace(state.get(), samplePlayer->state.get());
+                       break;
+               
+               case ChannelType::PREVIEW:
+                       samplePlayer.emplace(p, state.get());
+                       break;
+               
+               case ChannelType::MIDI:
+                       midiController.emplace(state.get());
+#ifdef WITH_VST
+                       midiReceiver.emplace(p, state.get());
+#endif
+                       midiSender.emplace(state.get());
+                       midiActionRecorder.emplace(state.get());        
+                       break;  
+               
+               default: break;
+       }
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-bool Channel::isPlaying() const
+void Channel::parse(const mixer::EventBuffer& events, bool audible) const
 {
-       return playStatus == ChannelStatus::PLAY || 
-              playStatus == ChannelStatus::ENDING;
+       for (const mixer::Event& e : events) {
+
+               if (e.action.channelId > 0 && e.action.channelId != id)
+                       continue;
+
+               parse(e);
+               midiLighter.parse(e, audible);
+
+               if (midiController)       midiController->parse(e);
+#ifdef WITH_VST
+               if (midiReceiver)         midiReceiver->parse(e);
+#endif
+               if (midiSender)           midiSender->parse(e);
+               if (samplePlayer)         samplePlayer->parse(e);
+               if (midiActionRecorder)   midiActionRecorder->parse(e);
+               if (sampleActionRecorder && samplePlayer && samplePlayer->hasWave()) 
+                       sampleActionRecorder->parse(e);
+       }
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void Channel::sendMidiLmute()
+void Channel::advance(Frame bufferSize) const
 {
-       if (!midiOutL || midiOutLmute == 0x0)
-               return;
-       if (mute)
-               kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOn);
-       else
-               kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOff);
+       /* TODO - this is used only to advance samplePlayer for its quantizer. Use
+       this to render actions in the future. */
+
+       if (samplePlayer) samplePlayer->advance(bufferSize);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void Channel::sendMidiLsolo()
+void Channel::render(AudioBuffer* out, AudioBuffer* in, bool audible) const
 {
-       if (!midiOutL || midiOutLsolo == 0x0)
-               return;
-       if (solo)
-               kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOn);
+       if (id == mixer::MASTER_OUT_CHANNEL_ID)
+               renderMasterOut(*out);
        else
-               kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOff);
+       if (id == mixer::MASTER_IN_CHANNEL_ID)
+               renderMasterIn(*in);
+       else 
+               renderChannel(*out, *in, audible);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void Channel::sendMidiLstatus()
+void Channel::parse(const mixer::Event& e) const
 {
-       if (!midiOutL || midiOutLplaying == 0x0)
-               return;
-       switch (playStatus) {
-               case ChannelStatus::OFF:
-                       kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopped);
-                       break;
-               case ChannelStatus::WAIT:
-                       kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.waiting);
-                       break;
-               case ChannelStatus::ENDING:
-                       kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopping);
-                       break;
-               case ChannelStatus::PLAY:
-                       if ((mixer::isChannelAudible(this) && !mute) || 
-                               !midimap::isDefined(midimap::midimap.playingInaudible))
-                               kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playing);
-                       else
-                               kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playingInaudible);
-                       break;
-               default:
+       switch (e.type) {
+
+               case mixer::EventType::CHANNEL_VOLUME:
+                       state->volume.store(e.action.event.getVelocityFloat()); break;
+
+               case mixer::EventType::CHANNEL_PAN:
+                       state->pan.store(e.action.event.getVelocityFloat()); break;
+
+               case mixer::EventType::CHANNEL_MUTE:
+                       state->mute.store(!state->mute.load()); break;
+
+               case mixer::EventType::CHANNEL_TOGGLE_ARM:
+                       state->armed.store(!state->armed.load()); break;
+                       
+               case mixer::EventType::CHANNEL_SOLO:
+                       state->solo.store(!state->solo.load()); 
+                       m::mh::updateSoloCount(); 
                        break;
+
+               default: break;
        }
 }
 
@@ -248,82 +233,98 @@ void Channel::sendMidiLstatus()
 /* -------------------------------------------------------------------------- */
 
 
-bool Channel::isMidiInAllowed(int c) const
+void Channel::renderMasterOut(AudioBuffer& out) const
 {
-       return midiInFilter == -1 || midiInFilter == c;
+       state->buffer.copyData(out);
+#ifdef WITH_VST
+       if (pluginIds.size() > 0)
+               pluginHost::processStack(state->buffer, pluginIds, nullptr);
+#endif
+       out.copyData(state->buffer, state->volume.load());
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void Channel::setPan(float v)
+void Channel::renderMasterIn(AudioBuffer& in) const
 {
-       if (v > 1.0f) v = 1.0f;
-       else 
-       if (v < 0.0f) v = 0.0f;
-       pan = v;
+#ifdef WITH_VST
+       if (pluginIds.size() > 0)
+               pluginHost::processStack(in, pluginIds, nullptr);
+#endif
 }
 
 
-float Channel::getPan() const
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::renderChannel(AudioBuffer& out, AudioBuffer& in, bool audible) const
 {
-       return pan;
-}
+       state->buffer.clear();
 
+       if (samplePlayer)  samplePlayer->render(out);
+       if (audioReceiver) audioReceiver->render(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(pluginIds); 
+       else 
+       if (pluginIds.size() > 0)
+               pluginHost::processStack(state->buffer, pluginIds, nullptr);
+#endif
 
-float Channel::calcPanning(int ch) const
-{      
-       float p = pan;
-       if (p  == 0.5f) // center: nothing to do
-               return 1.0;
-       if (ch == 0)
-               return 1.0 - p;
-       else  // channel 1
-               return p; 
+       if (audible)
+           out.addData(state->buffer, state->volume.load() * state->volume_i, calcPanning());
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void Channel::calcVolumeEnvelope()
+AudioBuffer::Pan Channel::calcPanning() const
 {
-       volume_i = volume_i + volume_d;
-       if (volume_i < 0.0f)
-               volume_i = 0.0f;
-       else
-       if (volume_i > 1.0f)
-               volume_i = 1.0f;        
-}
+       /* TODO - precompute the AudioBuffer::Pan when pan value changes instead of
+       building it on the fly. */
+       
+       float pan = state->pan.load();
 
+       /* Center pan (0.5f)? Pass-through. */
 
-bool Channel::isPreview() const
-{
-       return previewMode != PreviewMode::NONE;
+       if (pan == 0.5f) return { 1.0f, 1.0f };
+       return { 1.0f - pan, pan };
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
+ID Channel::getColumnId() const { return m_columnId; };
+ChannelType Channel::getType() const { return m_type; };
+
+
+/* -------------------------------------------------------------------------- */
+
+
 bool Channel::isInternal() const
 {
-    return id == mixer::MASTER_OUT_CHANNEL_ID ||
-              id == mixer::MASTER_IN_CHANNEL_ID  ||
-              id == mixer::PREVIEW_CHANNEL_ID;
+       return m_type == ChannelType::MASTER || m_type == ChannelType::PREVIEW;
 }
 
 
-/* -------------------------------------------------------------------------- */
+bool Channel::isMuted() const
+{
+       /* Internals can't be muted. */
+       return !isInternal() && state->mute.load() == true;
+}
 
 
-bool Channel::isReadingActions() const
+bool Channel::canInputRec() const
 {
-       return hasActions && readActions;
+       return samplePlayer && !samplePlayer->hasWave() && state->armed.load() == true;
 }
-
 }} // giada::m::
index a6d4e5f3c99577d50c05c01933f440c3c7bb080b..84500114c0ab61bb39c9e5bc09c20b4912bb50bf 100644 (file)
 #define G_CHANNEL_H
 
 
-#include <vector>
-#include <string>
-#include "core/types.h"
-#include "core/patch.h"
+#include <optional>
+#include "core/const.h"
 #include "core/mixer.h"
-#include "core/midiMapConf.h"
-#include "core/midiEvent.h"
-#include "core/recorder.h"
-#include "core/audioBuffer.h"
+#include "core/channels/state.h"
+#include "core/channels/samplePlayer.h"
+#include "core/channels/audioReceiver.h"
 #ifdef WITH_VST
-#include "deps/juce-config.h"
-#include "core/plugin.h"
-#include "core/pluginHost.h"
-#include "core/queue.h"
+#include "core/channels/midiReceiver.h"
 #endif
+#include "core/channels/midiLearner.h"
+#include "core/channels/midiSender.h"
+#include "core/channels/midiController.h"
+#include "core/channels/midiLighter.h"
+#include "core/channels/sampleActionRecorder.h"
+#include "core/channels/midiActionRecorder.h"
 
 
 namespace giada {
 namespace m
 {
-class Channel
+class Channel final
 {
 public:
 
-       virtual ~Channel() {};
+    Channel(ChannelType t, ID id, ID columnId, Frame bufferSize);
+    Channel(const Channel&);
+    Channel(const patch::Channel& p, Frame bufferSize);
+    Channel(Channel&&)                 = default;
+    Channel& operator=(const Channel&) = default;
+    Channel& operator=(Channel&&)      = default;
+    ~Channel()                         = default;
 
-       /* clone
-       A trick to give the caller the ability to invoke the derived class copy
-       constructor given the base. TODO - This thing will go away with the Channel
-       "no-virtual inheritance" refactoring. */
+    /* parse
+    Parses live events. */
 
-       virtual Channel* clone() const = 0;
+    void parse(const mixer::EventBuffer& e, bool audible) const;
 
-       /* load
-       Loads persistence data into an existing channel. Used for built-in channels
-       such as masters and preview. */
+    /* advance
+    Processes static events (e.g. actions) in the current block. */
 
-       virtual void load(const patch::Channel& p) {}
+    void advance(Frame bufferSize) const;
 
-       /* parseEvents
-       Prepares channel for rendering. This is called on each frame. */
+    /* render
+    Renders audio data to I/O buffers. */
+     
+    void render(AudioBuffer* out, AudioBuffer* in, bool audible) const;
 
-       virtual void parseEvents(mixer::FrameEvents fe) {};
+    bool isInternal() const;
+    bool isMuted() const;
+    bool canInputRec() const;
+    ID getColumnId() const;
+    ChannelType getType() const;
+    
+    ID id;
 
-       /* render
-       Audio rendering. Warning: inBuffer might be unallocated if no input devices 
-       are available for recording. */
-
-       virtual void render(AudioBuffer& out, const AudioBuffer& in, 
-               AudioBuffer& inToOut, bool audible, bool running) {};
-
-       /* start
-       Action to do when channel starts. doQuantize = false (don't quantize)
-       when Mixer is reading actions from Recorder. */
-
-       virtual void start(int localFrame, bool doQuantize, int velocity) {};
-
-       /* stop
-       What to do when channel is stopped normally (via key or MIDI). */
-
-       virtual void stop() {};
-
-       /* kill
-       What to do when channel stops abruptly. */
-
-       virtual void kill(int localFrame) {};
-
-       /* set mute
-       What to do when channel is un/muted. */
-
-       virtual void setMute(bool value) {};
-
-       /* set solo
-       What to do when channel is un/soloed. */
-
-       virtual void setSolo(bool value) {};
-
-       /* empty
-       Frees any associated resources (e.g. waveform for SAMPLE). */
-
-       virtual void empty() {};
-
-       /* stopBySeq
-       What to do when channel is stopped by sequencer. */
-
-       virtual void stopBySeq(bool chansStopOnSeqHalt) {};
-
-       /* rewind
-       Rewinds channel when rewind button is pressed. */
-
-       virtual void rewindBySeq() {};
-
-       /* canInputRec
-       Tells whether a channel can accept and handle input audio. Always false for
-       Midi channels, true for Sample channels only if they don't contain a
-       sample yet.*/
-
-       virtual bool canInputRec()    const { return false; };
-       virtual bool hasLogicalData() const { return false; };
-       virtual bool hasEditedData()  const { return false; };
-       virtual bool hasData()        const { return false; };
-
-       virtual bool recordStart(bool canQuantize) { return true; };
-       virtual bool recordKill() { return true; };
-       virtual void recordStop() {};
-
-       virtual void startReadingActions(bool treatRecsAsLoops, 
-               bool recsStopOnChanHalt) {};
-       virtual void stopReadingActions(bool running, bool treatRecsAsLoops, 
-               bool recsStopOnChanHalt) {};
-
-       virtual void stopInputRec(int globalFrame) {};
-
-       /* receiveMidi
-       Receives and processes midi messages from external devices. */
-
-       virtual void receiveMidi(const MidiEvent& midiEvent) {};
-
-       /* calcPanning
-       Given an audio channel (stereo: 0 or 1) computes the current panning value. */
-
-       float calcPanning(int ch) const;
-
-       bool isPlaying() const;
-       float getPan() const;
-       bool isPreview() const;
-       bool isInternal() const;
-
-       /* isMidiInAllowed
-       Given a MIDI channel 'c' tells whether this channel should be allowed to 
-       receive and process MIDI events on MIDI channel 'c'. */
-
-       bool isMidiInAllowed(int c) const;
-
-       /* isReadingActions
-       Tells whether the channel as actions and it is currently reading them. */
-
-       bool isReadingActions() const;
-
-       /* sendMidiL*
-       Sends MIDI lightning events to a physical device. */
-
-       void sendMidiLmute();
-       void sendMidiLsolo();
-       void sendMidiLstatus();
-
-       void setPan(float v);
-
-       void calcVolumeEnvelope();
-
-       /* buffer
-       Working buffer for internal processing. */
-       
-       AudioBuffer buffer;
-
-       ChannelType   type;
-       ChannelStatus playStatus;
-       ChannelStatus recStatus;
-
-       ID columnId;
-       ID id;
-
-       int height;
-
-       /* previewMode
-       Whether the channel is in audio preview mode or not. */
-
-       PreviewMode previewMode;
-
-       float pan;
-       float volume;   // global volume
-       bool armed;
-       std::string name;
-       int  key;
-       bool mute;
-       bool solo;
-
-       /* volume_*
-       Internal volume variables: volume_i for envelopes, volume_d keeps track of
-       the delta during volume changes (or the line slope between two volume 
-       points). */
-       
-       double volume_i;
-       double volume_d;
-       
-       bool hasActions;  // If has some actions recorded
-       bool readActions; // If should read recorded actions
-
-       bool     midiIn;               // enable midi input
-       uint32_t midiInKeyPress;
-       uint32_t midiInKeyRel;
-       uint32_t midiInKill;
-       uint32_t midiInArm;
-       uint32_t midiInVolume;
-       uint32_t midiInMute;
-       uint32_t midiInSolo;
-
-       /* midiInFilter
-       Which MIDI channel should be filtered out when receiving MIDI messages. -1
-       means 'all'. */
+#ifdef WITH_VST
+    std::vector<ID> pluginIds;
+#endif
 
-       int midiInFilter;
+    /* state
+    Pointer to mutable Channel state. */
 
-       /*  midiOutL*
-       Enables MIDI lightning output, plus a set of midi lighting event to be sent
-       to a device. Those events basically contains the MIDI channel, everything
-       else gets stripped out. */
+    std::unique_ptr<ChannelState> state;
 
-       bool     midiOutL;
-       uint32_t midiOutLplaying;
-       uint32_t midiOutLmute;
-       uint32_t midiOutLsolo;
+    MidiLearner midiLearner;
+    MidiLighter midiLighter;
 
+    std::optional<SamplePlayer>         samplePlayer;
+    std::optional<AudioReceiver>        audioReceiver;
+    std::optional<MidiController>       midiController;
 #ifdef WITH_VST
+    std::optional<MidiReceiver>         midiReceiver;
+#endif
+    std::optional<MidiSender>           midiSender;
+    std::optional<SampleActionRecorder> sampleActionRecorder;
+    std::optional<MidiActionRecorder>   midiActionRecorder;
 
-       std::vector<ID> pluginIds;
-
-       /* MidiBuffer 
-       Contains MIDI events. When ready, events are sent to each plugin in the 
-       channel. This is available for any kind of channel, but it makes sense only 
-       for MIDI channels. */
-       
-       juce::MidiBuffer midiBuffer;
-
-       /* midiQueue
-       FIFO queue for collecting MIDI events from the MIDI thread and passing them
-       to the audio thread. */
-       /* TODO - magic number */
+private:
 
-       Queue<MidiEvent, 32> midiQueue;
+    void parse(const mixer::Event& e) const;
 
-#endif
+    void renderMasterOut(AudioBuffer& out) const;
+    void renderMasterIn(AudioBuffer& in) const;
+    void renderChannel(AudioBuffer& out, AudioBuffer& in, bool audible) const;
 
-protected:
+    AudioBuffer::Pan calcPanning() const;
 
-       Channel(ChannelType type, ChannelStatus status, int bufferSize,
-               ID columnId, ID id);
-       Channel(const Channel& o);
-       Channel(const patch::Channel& p, int bufferSize);
+    ChannelType m_type;
+    ID m_columnId;
 };
-
 }} // giada::m::
 
 
index 8bc2b43f6895ae5c13cabcea88d4bb750306afa0..260fd5ea3aecca258aad6e0180eb80e5b4b9a472 100644 (file)
 
 #include <cassert>
 #include "utils/fs.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
-#include "core/channels/masterChannel.h"
 #include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
 #include "core/const.h"
+#include "core/kernelAudio.h"
 #include "core/patch.h"
 #include "core/mixer.h"
 #include "core/idManager.h"
@@ -69,25 +68,11 @@ void init()
 /* -------------------------------------------------------------------------- */
 
 
-std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
+std::unique_ptr<Channel> create(ChannelType type, int bufferSize, 
        bool inputMonitorOn, ID columnId)
 {
-       std::unique_ptr<Channel> ch = nullptr;
-
-       if (type == ChannelType::SAMPLE)
-               ch = std::make_unique<SampleChannel>(inputMonitorOn, bufferSize, columnId, channelId_.get());
-       else
-       if (type == ChannelType::MIDI)
-               ch = std::make_unique<MidiChannel>(bufferSize, columnId, channelId_.get());
-       else
-       if (type == ChannelType::MASTER)
-               ch = std::make_unique<MasterChannel>(bufferSize, channelId_.get());
-       else
-       if (type == ChannelType::PREVIEW)
-               ch = std::make_unique<MasterChannel>(bufferSize, channelId_.get()); // TODO - temporary placeholder
-       
-       assert(ch != nullptr);
-       return ch;
+       return std::make_unique<Channel>(type, channelId_.get(), columnId, 
+               kernelAudio::getRealBufSize());
 }
 
 
@@ -96,22 +81,8 @@ std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
 
 std::unique_ptr<Channel> create(const Channel& o)
 {
-       std::unique_ptr<Channel> ch = nullptr;
-       
-       if (o.type == ChannelType::SAMPLE)
-               ch = std::make_unique<SampleChannel>(static_cast<const SampleChannel&>(o));
-       else
-       if (o.type == ChannelType::MIDI)
-               ch = std::make_unique<MidiChannel>(static_cast<const MidiChannel&>(o));
-       else
-       if (o.type == ChannelType::MASTER)
-               ch = std::make_unique<MasterChannel>(static_cast<const MasterChannel&>(o));
-
-       assert(ch != nullptr);
-
-       if (o.type != ChannelType::MASTER)
-               ch->id = channelId_.get();
-
+       std::unique_ptr<Channel> ch = std::make_unique<Channel>(o);
+       ch->id = channelId_.get();
        return ch;
 }
 
@@ -121,19 +92,8 @@ std::unique_ptr<Channel> create(const Channel& o)
 
 std::unique_ptr<Channel> deserializeChannel(const patch::Channel& pch, int bufferSize)
 {
-       std::unique_ptr<Channel> ch = nullptr;
-
-       if (pch.type == ChannelType::SAMPLE)
-               ch = std::make_unique<SampleChannel>(pch, bufferSize);
-       else
-       if (pch.type == ChannelType::MIDI)
-               ch = std::make_unique<MidiChannel>(pch, bufferSize);
-
-       assert(ch != nullptr);
-
        channelId_.set(pch.id);
-
-       return ch;
+       return std::make_unique<Channel>(pch, bufferSize);
 }
 
 
@@ -144,58 +104,55 @@ const patch::Channel serializeChannel(const Channel& c)
 {
        patch::Channel pc;
 
-       pc.id   = c.id;
-       pc.type = c.type;
-
 #ifdef WITH_VST
        for (ID pid : c.pluginIds)
                pc.pluginIds.push_back(pid);
-#endif 
-
-       if (c.type != ChannelType::MASTER) {
-               pc.height          = c.height;
-               pc.name            = c.name.c_str();
-               pc.columnId        = c.columnId;
-               pc.key             = c.key;
-               pc.mute            = c.mute;
-               pc.solo            = c.solo;
-               pc.volume          = c.volume;
-               pc.pan             = c.pan;
-               pc.hasActions      = c.hasActions;
-               pc.armed           = c.armed;
-               pc.midiIn          = c.midiIn;
-               pc.midiInKeyPress  = c.midiInKeyRel;
-               pc.midiInKeyRel    = c.midiInKeyPress;
-               pc.midiInKill      = c.midiInKill;
-               pc.midiInArm       = c.midiInArm;
-               pc.midiInVolume    = c.midiInVolume;
-               pc.midiInMute      = c.midiInMute;
-               pc.midiInSolo      = c.midiInSolo;
-               pc.midiInFilter    = c.midiInFilter;
-               pc.midiOutL        = c.midiOutL;
-               pc.midiOutLplaying = c.midiOutLplaying;
-               pc.midiOutLmute    = c.midiOutLmute;
-               pc.midiOutLsolo    = c.midiOutLsolo;
-       }
+#endif
+
+       pc.id                = c.id;
+       pc.type              = c.getType();
+    pc.columnId          = c.getColumnId();
+    pc.height            = c.state->height;
+    pc.name              = c.state->name;
+    pc.key               = c.state->key.load();
+    pc.mute              = c.state->mute.load();
+    pc.solo              = c.state->solo.load();
+    pc.volume            = c.state->volume.load();
+    pc.pan               = c.state->pan.load();
+    pc.hasActions        = c.state->hasActions;
+    pc.readActions       = c.state->readActions.load();
+    pc.armed             = c.state->armed.load();
+    pc.midiIn            = c.midiLearner.state->enabled.load();
+    pc.midiInFilter      = c.midiLearner.state->filter.load();
+    pc.midiInKeyPress    = c.midiLearner.state->keyPress.load();
+    pc.midiInKeyRel      = c.midiLearner.state->keyRelease.load();
+    pc.midiInKill        = c.midiLearner.state->kill.load();
+    pc.midiInArm         = c.midiLearner.state->arm.load();
+    pc.midiInVolume      = c.midiLearner.state->volume.load();
+    pc.midiInMute        = c.midiLearner.state->mute.load();
+    pc.midiInSolo        = c.midiLearner.state->solo.load();
+       pc.midiInReadActions = c.midiLearner.state->readActions.load();
+       pc.midiInPitch       = c.midiLearner.state->pitch.load();
+    pc.midiOutL          = c.midiLighter.state->enabled.load(); 
+    pc.midiOutLplaying   = c.midiLighter.state->playing.load();
+    pc.midiOutLmute      = c.midiLighter.state->mute.load();
+    pc.midiOutLsolo      = c.midiLighter.state->solo.load();
+
+       if (c.getType() == ChannelType::SAMPLE) {
+               pc.waveId            = c.samplePlayer->getWaveId();
+               pc.mode              = c.samplePlayer->state->mode.load();
+               pc.begin             = c.samplePlayer->state->begin.load();
+               pc.end               = c.samplePlayer->state->end.load();
+               pc.pitch             = c.samplePlayer->state->pitch.load();
+               pc.shift             = c.samplePlayer->state->shift.load();
+               pc.midiInVeloAsVol   = c.samplePlayer->state->velocityAsVol.load();
+               pc.inputMonitor      = c.audioReceiver->state->inputMonitor.load();
 
-       if (c.type == ChannelType::SAMPLE) {
-               const SampleChannel& sc = static_cast<const SampleChannel&>(c);
-               pc.waveId            = sc.waveId;
-               pc.mode              = sc.mode;
-               pc.begin             = sc.begin;
-               pc.end               = sc.end;
-               pc.readActions       = sc.readActions;
-               pc.pitch             = sc.pitch;
-               pc.inputMonitor      = sc.inputMonitor;
-               pc.midiInVeloAsVol   = sc.midiInVeloAsVol;
-               pc.midiInReadActions = sc.midiInReadActions;
-               pc.midiInPitch       = sc.midiInPitch;
        }
        else
-       if (c.type == ChannelType::MIDI) {
-               const MidiChannel& mc = static_cast<const MidiChannel&>(c);
-               pc.midiOut     = mc.midiOut;
-               pc.midiOutChan = mc.midiOutChan;
+       if (c.getType() == ChannelType::MIDI) { 
+               pc.midiOut     = c.midiSender->state->enabled.load();
+               pc.midiOutChan = c.midiSender->state->filter.load();
        }
 
        return pc;
index 4229a047da54aae8885ab613feec453a2ccf0f69..e7667feaac47378d46a306c59ceeb4e09d7964f0 100644 (file)
@@ -40,9 +40,11 @@ namespace patch
 {
 struct Channel;
 }
-class Channel;
-class SampleChannel;
-class MidiChannel;
+class  Channel;
+struct ChannelState;
+class  Channel;
+class  SampleChannel;
+class  MidiChannel;
 namespace channelManager
 {
 /* init
@@ -53,7 +55,7 @@ void init();
 /* create (1)
 Creates a new Channel from scratch. */
 
-std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
+std::unique_ptr<Channel> create(ChannelType type, int bufferSize, 
        bool inputMonitorOn, ID columnId);
 
 /* create (2)
diff --git a/src/core/channels/masterChannel.cpp b/src/core/channels/masterChannel.cpp
deleted file mode 100644 (file)
index 9ce1735..0000000
+++ /dev/null
@@ -1,87 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include "masterChannel.h"
-
-
-namespace giada {
-namespace m 
-{
-MasterChannel::MasterChannel(int bufferSize, ID id)
-: Channel(ChannelType::MASTER, ChannelStatus::OFF, bufferSize, 0, id)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MasterChannel::MasterChannel(const patch::Channel& p, int bufferSize)
-: Channel(p, bufferSize)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MasterChannel* MasterChannel::clone() const
-{
-       return new MasterChannel(*this);
-}
-
-       
-/* -------------------------------------------------------------------------- */
-
-
-void MasterChannel::load(const patch::Channel& p)
-{
-       volume    = p.volume;
-#ifdef WITH_VST
-    pluginIds = p.pluginIds;
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MasterChannel::render(AudioBuffer& out, const AudioBuffer& in, 
-       AudioBuffer& inToOut, bool audible, bool running)
-{
-#ifdef WITH_VST
-       if (pluginIds.size() == 0)
-               return;
-       if (id == mixer::MASTER_OUT_CHANNEL_ID)
-               pluginHost::processStack(out, pluginIds);
-       else
-       if (id == mixer::MASTER_IN_CHANNEL_ID)
-               pluginHost::processStack(inToOut, pluginIds);
-#endif
-}
-
-}} // giada::m::
diff --git a/src/core/channels/masterChannel.h b/src/core/channels/masterChannel.h
deleted file mode 100644 (file)
index 1e336c6..0000000
+++ /dev/null
@@ -1,64 +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_MASTER_CHANNEL_H
-#define G_MASTER_CHANNEL_H
-
-
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m 
-{
-class MasterChannel : public Channel
-{
-public:
-
-       MasterChannel(int bufferSize, ID id);
-       MasterChannel(const patch::Channel& p, int bufferSize);
-
-       MasterChannel* clone() const override;
-       void load(const patch::Channel& p) override;
-       void parseEvents(mixer::FrameEvents fe) override {};
-       void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, 
-               bool audible, bool running) override;
-       void start(int frame, bool doQuantize, int velocity) override {};
-       void kill(int localFrame) override {};
-       void empty() override {};
-       void stopBySeq(bool chansStopOnSeqHalt) override {};
-       void stop() override {};
-       void rewindBySeq() override {};
-       void setMute(bool value) override {};
-       void setSolo(bool value) override {};
-       void receiveMidi(const MidiEvent& midiEvent) override {};
-};
-
-}} // giada::m::
-
-
-#endif
diff --git a/src/core/channels/midiActionRecorder.cpp b/src/core/channels/midiActionRecorder.cpp
new file mode 100644 (file)
index 0000000..dd1e038
--- /dev/null
@@ -0,0 +1,91 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/action.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/mixer.h"
+#include "core/recorderHandler.h"
+#include "core/recManager.h"
+#include "core/channels/state.h"
+#include "midiActionRecorder.h"
+
+
+namespace giada {
+namespace m
+{
+MidiActionRecorder::MidiActionRecorder(ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiActionRecorder::MidiActionRecorder(const MidiActionRecorder& o, ChannelState* c)
+: MidiActionRecorder(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiActionRecorder::parse(const mixer::Event& e) const
+{
+       assert(m_channelState != nullptr);
+
+       if (e.type == mixer::EventType::MIDI && canRecord()) 
+               record(e.action.event);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiActionRecorder::record(const MidiEvent& e) const
+{
+       MidiEvent flat(e);
+       flat.setChannel(0);
+       recorderHandler::liveRec(m_channelState->id, flat, clock::quantize(clock::getCurrentFrame()));
+       m_channelState->hasActions = true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool MidiActionRecorder::canRecord() const
+{
+       return recManager::isRecordingAction() && 
+              clock::isRunning()              && 
+              !recManager::isRecordingInput();
+}
+}} // giada::m::
+
diff --git a/src/core/channels/midiActionRecorder.h b/src/core/channels/midiActionRecorder.h
new file mode 100644 (file)
index 0000000..50e3109
--- /dev/null
@@ -0,0 +1,62 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_ACTION_RECORDER_H
+#define G_CHANNEL_MIDI_ACTION_RECORDER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+class MidiActionRecorder
+{
+public:
+
+    MidiActionRecorder(ChannelState*);
+    MidiActionRecorder(const MidiActionRecorder&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e) const;
+
+private:
+
+    bool canRecord() const;
+    void record(const MidiEvent& e) const;
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/midiChannel.cpp b/src/core/channels/midiChannel.cpp
deleted file mode 100644 (file)
index 89dae2d..0000000
+++ /dev/null
@@ -1,226 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/log.h"
-#include "core/channels/midiChannelProc.h"
-#include "core/channels/channelManager.h"
-#include "core/channels/channel.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/recManager.h"
-#include "core/action.h"
-#include "core/patch.h"
-#include "core/const.h"
-#include "core/conf.h"
-#include "core/mixer.h"
-#include "core/pluginHost.h"
-#include "core/kernelMidi.h"
-#include "midiChannel.h"
-
-
-namespace giada {
-namespace m 
-{
-MidiChannel::MidiChannel(int bufferSize, ID columnId, ID id)
-: Channel    (ChannelType::MIDI, ChannelStatus::OFF, bufferSize, columnId, id),
-  midiOut    (false),
-  midiOutChan(G_MIDI_CHANS[0])
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel::MidiChannel(const MidiChannel& o)
-: Channel    (o),
-  midiOut    (o.midiOut),
-  midiOutChan(o.midiOutChan)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel::MidiChannel(const patch::Channel& p, int bufferSize)
-: Channel    (p, bufferSize),
-  midiOut    (p.midiOut),
-  midiOutChan(p.midiOutChan)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel* MidiChannel::clone() const
-{
-       return new MidiChannel(*this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::parseEvents(mixer::FrameEvents fe)
-{
-       midiChannelProc::parseEvents(this, fe);
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::render(AudioBuffer& out, const AudioBuffer& in, 
-       AudioBuffer& inToOut, bool audible, bool running)
-{
-       midiChannelProc::process(this, out, in, audible);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::stopBySeq(bool chansStopOnSeqHalt)
-{
-       midiChannelProc::stopBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::start(int frame, bool doQuantize, int velocity)
-{
-       midiChannelProc::start(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::kill(int localFrame)
-{
-       midiChannelProc::kill(this, localFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::rewindBySeq()
-{
-       midiChannelProc::rewindBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::setMute(bool value)
-{
-       midiChannelProc::setMute(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::setSolo(bool value)
-{
-       midiChannelProc::setSolo(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::empty()
-{
-       hasActions = false;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::sendMidi(const MidiEvent& e, int localFrame)
-{
-       if (midiOut) {
-               MidiEvent e_ = e;
-               e_.setChannel(midiOutChan);
-               kernelMidi::send(e_.getRaw());
-       }
-
-#ifdef WITH_VST
-
-       /* Enqueue this MIDI event for plug-ins processing. Will be read and
-       rendered later on by the audio thread. */
-
-       MidiEvent e_ = e;
-       e_.setDelta(localFrame);
-       midiQueue.push(e_);
-
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::receiveMidi(const MidiEvent& midiEvent)
-{
-       namespace mrh = m::recorderHandler;
-       namespace mr  = m::recorder;
-
-       if (!armed)
-               return;
-
-       /* Now all messages are turned into Channel-0 messages. Giada doesn't care 
-       about holding MIDI channel information. Moreover, having all internal 
-       messages on channel 0 is way easier. */
-
-       MidiEvent midiEventFlat(midiEvent);
-       midiEventFlat.setChannel(0);
-
-#ifdef WITH_VST
-
-       /* Enqueue this MIDI event for plug-ins processing. Will be read and
-       rendered later on by the audio thread. */
-
-       midiQueue.push(midiEventFlat);
-
-#endif
-
-       if (recManager::isRecordingAction()) {
-               mrh::liveRec(id, midiEventFlat);
-               hasActions = true;
-       }
-}
-
-}} // giada::m::
diff --git a/src/core/channels/midiChannel.h b/src/core/channels/midiChannel.h
deleted file mode 100644 (file)
index 7f2f550..0000000
+++ /dev/null
@@ -1,75 +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_MIDI_CHANNEL_H
-#define G_MIDI_CHANNEL_H
-
-
-#ifdef WITH_VST
-#include "deps/juce-config.h"
-#endif
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m 
-{
-class MidiChannel : public Channel
-{
-public:
-
-       MidiChannel(int bufferSize, ID columnId, ID id);
-       MidiChannel(const MidiChannel& o);
-       MidiChannel(const patch::Channel& p, int bufferSize);
-
-       MidiChannel* clone() const override;
-       void parseEvents(mixer::FrameEvents fe) override;
-       void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, 
-               bool audible, bool running) override;
-       void start(int frame, bool doQuantize, int velocity) override;
-       void kill(int localFrame) override;
-       void empty() override;
-       void stopBySeq(bool chansStopOnSeqHalt) override;
-       void stop() override {};
-       void rewindBySeq() override;
-       void setMute(bool value) override;
-       void setSolo(bool value) override;
-       void receiveMidi(const MidiEvent& midiEvent) override;
-
-       /* sendMidi
-       Sends Midi event to the outside world. */
-
-       void sendMidi(const MidiEvent& e, int localFrame);
-       
-       bool midiOut;      // enable midi output
-       int  midiOutChan;  // midi output channel
-};
-
-}} // giada::m::
-
-
-#endif
diff --git a/src/core/channels/midiChannelProc.cpp b/src/core/channels/midiChannelProc.cpp
deleted file mode 100644 (file)
index 204f5a6..0000000
+++ /dev/null
@@ -1,215 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "core/pluginHost.h"
-#include "core/kernelMidi.h"
-#include "core/const.h"
-#include "core/action.h"
-#include "core/mixerHandler.h"
-#include "midiChannelProc.h"
-
-
-namespace giada {
-namespace m {
-namespace midiChannelProc
-{
-namespace
-{
-void onFirstBeat_(MidiChannel* ch)
-{
-       if (ch->playStatus == ChannelStatus::ENDING) {
-               ch->playStatus = ChannelStatus::OFF;
-               ch->sendMidiLstatus();
-       }
-       else
-       if (ch->playStatus == ChannelStatus::WAIT) {
-               ch->playStatus = ChannelStatus::PLAY;
-               ch->sendMidiLstatus();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void sendAllNotesOff_(MidiChannel* ch)
-{
-       MidiEvent e(MIDI_ALL_NOTES_OFF);
-       ch->sendMidi(e, /*localFrame=*/0);
-
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(MidiChannel* ch, mixer::FrameEvents fe)
-{
-       if (fe.onFirstBeat)
-               onFirstBeat_(ch);
-       if (fe.actions != nullptr)
-               for (const Action& action : *fe.actions)
-                       if (action.channelId == ch->id && ch->isPlaying() && !ch->mute)
-                               ch->sendMidi(action.event, fe.frameLocal);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible)
-{
-#ifdef WITH_VST
-
-       ch->midiBuffer.clear();
-       
-       /* Fill the MIDI buffer vector with messages coming from the MIDI queue
-       filled by the MIDI thread. This is for live events, e.g. piano keyboards,
-       controllers, ... */
-
-       MidiEvent e;
-       while (ch->midiQueue.pop(e)) {
-               juce::MidiMessage message = juce::MidiMessage(
-                       e.getStatus(), 
-                       e.getNote(), 
-                       e.getVelocity());
-               ch->midiBuffer.addEvent(message, e.getDelta());
-       }
-       pluginHost::processStack(ch->buffer, ch->pluginIds, &ch->midiBuffer);
-       
-       /* Process the plugin stack first, then quit if the channel is muted/soloed. 
-       This way there's no risk of cutting midi event pairs such as note-on and 
-       note-off while triggering a mute/solo. */
-
-       if (!audible)
-               return;
-
-       for (int i=0; i<out.countFrames(); i++)
-               for (int j=0; j<out.countChannels(); j++)
-                       out[i][j] += ch->buffer[i][j] * ch->volume;     
-
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(MidiChannel* ch)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::PLAY:
-                       ch->playStatus = ChannelStatus::ENDING;
-                       ch->sendMidiLstatus();
-                       break;
-
-               case ChannelStatus::ENDING:
-               case ChannelStatus::WAIT:
-                       ch->playStatus = ChannelStatus::OFF;
-                       ch->sendMidiLstatus();
-                       break;
-
-               case ChannelStatus::OFF:
-                       ch->playStatus = ChannelStatus::WAIT;
-                       ch->sendMidiLstatus();
-                       break;
-
-               default: break;
-       }       
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void kill(MidiChannel* ch, int localFrame)
-{
-       if (ch->isPlaying())
-               sendAllNotesOff_(ch);
-
-       ch->playStatus = ChannelStatus::OFF;
-       ch->sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindBySeq(MidiChannel* ch)
-{
-       sendAllNotesOff_(ch);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(MidiChannel* ch, bool v)
-{
-       ch->mute = v;
-       if (v)
-               sendAllNotesOff_(ch);
-
-       // This is for processing playing_inaudible
-       ch->sendMidiLstatus();  
-
-       ch->sendMidiLmute();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(MidiChannel* ch, bool v)
-{
-       ch->solo = v;
-       mh::updateSoloCount();
-
-       // This is for processing playing_inaudible
-       // TODO
-       //for (std::unique_ptr<Channel>& c : model::getLayout()->channels)
-       //      c->sendMidiLstatus();
-
-       ch->sendMidiLsolo();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopBySeq(MidiChannel* ch)
-{      
-       sendAllNotesOff_(ch);
-       kill(ch, 0);
-}
-}}};
diff --git a/src/core/channels/midiChannelProc.h b/src/core/channels/midiChannelProc.h
deleted file mode 100644 (file)
index 724759f..0000000
+++ /dev/null
@@ -1,78 +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_MIDI_CHANNEL_PROC_H
-#define G_MIDI_CHANNEL_PROC_H
-
-
-#include "core/mixer.h"
-#include "core/audioBuffer.h"
-
-
-namespace giada {
-namespace m 
-{
-class MidiChannel;
-namespace midiChannelProc
-{
-/* parseEvents
-Parses events gathered by Mixer::masterPlay(). */
-
-void parseEvents(MidiChannel* ch, mixer::FrameEvents ev);
-
-/**/
-void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible);
-
-/* kill
-Stops a channel abruptly. */
-
-void kill(MidiChannel* ch, int localFrame);
-
-/* start
-Starts a channel. */
-
-void start(MidiChannel* ch);
-
-/* stopBySeq
-Stops a channel when the stop button on main transport is pressed. */
-
-void stopBySeq(MidiChannel* ch);
-
-/* rewind
-Rewinds channel when rewind button on main transport is pressed. */
-
-void rewindBySeq(MidiChannel* ch);
-
-/* mute|unmute
-Mutes/unmutes a channel. */
-
-void setMute(MidiChannel* ch, bool v);
-void setSolo(MidiChannel* ch, bool v);
-}}};
-
-
-#endif
diff --git a/src/core/channels/midiController.cpp b/src/core/channels/midiController.cpp
new file mode 100644 (file)
index 0000000..9b73df4
--- /dev/null
@@ -0,0 +1,126 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/conf.h"
+#include "core/channels/state.h"
+#include "midiController.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiController::MidiController(ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiController::MidiController(const MidiController& o, ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::parse(const mixer::Event& e) const
+{
+       assert(m_channelState != nullptr);
+
+       switch (e.type) {
+
+               case mixer::EventType::KEY_PRESS:
+                       press(); break;
+
+               case mixer::EventType::KEY_KILL:
+               case mixer::EventType::SEQUENCER_STOP:
+                       kill(); break;
+
+               case mixer::EventType::SEQUENCER_FIRST_BEAT:
+               case mixer::EventType::SEQUENCER_REWIND:
+                       onFirstBeat();  
+
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::press() const
+{
+    ChannelStatus playStatus = m_channelState->playStatus.load();
+
+       switch (playStatus) {
+               case ChannelStatus::PLAY:
+                       playStatus = ChannelStatus::ENDING; break;
+
+               case ChannelStatus::ENDING:
+               case ChannelStatus::WAIT:
+                       playStatus = ChannelStatus::OFF; break;
+
+               case ChannelStatus::OFF:
+                       playStatus = ChannelStatus::WAIT; break;
+
+               default: break;
+       }       
+       
+       m_channelState->playStatus.store(playStatus);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::kill() const
+{
+       m_channelState->playStatus.store(ChannelStatus::OFF);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::onFirstBeat() const
+{
+       ChannelStatus playStatus = m_channelState->playStatus.load();
+
+       if (playStatus == ChannelStatus::ENDING)
+               playStatus = ChannelStatus::OFF;
+       else
+       if (playStatus == ChannelStatus::WAIT)
+               playStatus = ChannelStatus::PLAY;
+       
+       m_channelState->playStatus.store(playStatus);
+}
+}} // giada::m::
diff --git a/src/core/channels/midiController.h b/src/core/channels/midiController.h
new file mode 100644 (file)
index 0000000..7a43778
--- /dev/null
@@ -0,0 +1,62 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_CONTROLLER_H
+#define G_CHANNEL_MIDI_CONTROLLER_H
+
+
+#include "core/types.h"
+#include "core/mixer.h"  // TODO - forward declare
+
+
+namespace giada {
+namespace m
+{
+/* MidiController
+Manages events for a MIDI Channel. */
+
+class MidiController
+{
+public:
+
+    MidiController(ChannelState*);
+    MidiController(const MidiController&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e) const;
+
+private:
+
+    void press() const;
+    void kill() const;
+    void onFirstBeat() const;
+    
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/midiLearner.cpp b/src/core/channels/midiLearner.cpp
new file mode 100644 (file)
index 0000000..0386546
--- /dev/null
@@ -0,0 +1,57 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "midiLearner.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiLearner::MidiLearner()
+: state(std::make_unique<MidiLearnerState>())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLearner::MidiLearner(const patch::Channel& p)
+: state(std::make_unique<MidiLearnerState>(p))
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLearner::MidiLearner(const MidiLearner& o)
+: state(std::make_unique<MidiLearnerState>(*o.state))
+{
+}
+}} // giada::m::
diff --git a/src/core/channels/midiLearner.h b/src/core/channels/midiLearner.h
new file mode 100644 (file)
index 0000000..5acb93d
--- /dev/null
@@ -0,0 +1,55 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_LEARNER_H
+#define G_CHANNEL_MIDI_LEARNER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+struct MidiLearnerState;
+class MidiLearner
+{
+public:
+
+    MidiLearner();
+    MidiLearner(const patch::Channel&);
+    MidiLearner(const MidiLearner&);
+
+    /* state
+    Pointer to mutable MidiLearnerState state. */
+
+    std::unique_ptr<MidiLearnerState> state;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/midiLighter.cpp b/src/core/channels/midiLighter.cpp
new file mode 100644 (file)
index 0000000..03b5b19
--- /dev/null
@@ -0,0 +1,149 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "core/mixer.h"
+#include "core/kernelMidi.h"
+#include "core/midiMapConf.h"
+#include "midiLighter.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiLighter::MidiLighter(ChannelState* c)
+: state         (std::make_unique<MidiLighterState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighter::MidiLighter(const patch::Channel& p, ChannelState* c)
+: state         (std::make_unique<MidiLighterState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighter::MidiLighter(const MidiLighter& o, ChannelState* c)
+: state         (std::make_unique<MidiLighterState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::parse(const mixer::Event& e, bool audible) const
+{
+    if (state->enabled.load() == false)
+        return;
+
+    uint32_t l_playing = state->playing.load();
+    uint32_t l_mute    = state->mute.load();
+    uint32_t l_solo    = state->solo.load();
+
+       switch (e.type) {
+
+        case mixer::EventType::KEY_PRESS:
+        case mixer::EventType::KEY_RELEASE:
+        case mixer::EventType::KEY_KILL:
+        case mixer::EventType::SEQUENCER_STOP:
+            if (l_playing != 0x0) sendStatus(l_playing, audible); 
+            break;
+
+        case mixer::EventType::CHANNEL_MUTE:
+            if (l_mute != 0x0) sendMute(l_mute); 
+            break;
+
+        case mixer::EventType::CHANNEL_SOLO:
+            if (l_solo != 0x0) sendSolo(l_solo); 
+            break;
+
+        default: break;
+    }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendMute(uint32_t l_mute) const
+{
+       if (m_channelState->mute.load() == true)
+               kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOn);
+       else
+               kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOff);    
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendSolo(uint32_t l_solo) const
+{
+       if (m_channelState->solo.load() == true)
+               kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOn);
+       else
+               kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOff);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendStatus(uint32_t l_playing, bool audible) const
+{
+    switch (m_channelState->playStatus.load()) {
+        
+        case ChannelStatus::OFF:
+            kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopped);
+            break;
+        
+        case ChannelStatus::WAIT:
+            kernelMidi::sendMidiLightning(l_playing, midimap::midimap.waiting);
+            break;
+
+        case ChannelStatus::ENDING:
+            kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopping);
+            break;
+
+        case ChannelStatus::PLAY:
+            kernelMidi::sendMidiLightning(l_playing, audible ? midimap::midimap.playing : midimap::midimap.playingInaudible);
+            break;
+
+        default: break;        
+    }
+}
+}} // giada::m::
diff --git a/src/core/channels/midiLighter.h b/src/core/channels/midiLighter.h
new file mode 100644 (file)
index 0000000..8b77b29
--- /dev/null
@@ -0,0 +1,73 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_LIGHTER_H
+#define G_CHANNEL_MIDI_LIGHTER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct MidiLighterState;
+
+/* MidiLighter
+Learns and emits MIDI lightning messages to physical hardware on events. */
+
+class MidiLighter
+{
+public:
+
+    MidiLighter(ChannelState*);
+    MidiLighter(const patch::Channel&, ChannelState*);
+    MidiLighter(const MidiLighter&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e, bool audible) const;
+
+    /* state
+    Pointer to mutable MidiLighterState state. */
+
+    std::unique_ptr<MidiLighterState> state;
+
+private:
+
+    void sendMute(uint32_t l_mute) const;
+    void sendSolo(uint32_t l_solo) const;
+    void sendStatus(uint32_t l_playing, bool audible) const;
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/midiReceiver.cpp b/src/core/channels/midiReceiver.cpp
new file mode 100644 (file)
index 0000000..0801b1e
--- /dev/null
@@ -0,0 +1,131 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include "core/mixer.h"
+#include "core/pluginHost.h"
+#include "core/channels/state.h"
+#include "midiReceiver.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiReceiver::MidiReceiver(ChannelState* c)
+: state           (std::make_unique<MidiReceiverState>())
+, m_channelState  (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiReceiver::MidiReceiver(const patch::Channel& p, ChannelState* c)
+: state           (std::make_unique<MidiReceiverState>())
+, m_channelState  (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiReceiver::MidiReceiver(const MidiReceiver& o, ChannelState* c)
+: state           (std::make_unique<MidiReceiverState>())
+, m_channelState  (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::parse(const mixer::Event& e) const
+{
+       switch (e.type) {
+
+               case mixer::EventType::MIDI:
+                       parseMidi(e.action.event); break;
+
+               case mixer::EventType::ACTION:
+                       if (m_channelState->isPlaying())
+                               sendToPlugins(e.action.event, e.delta);
+                       break;
+               
+               case mixer::EventType::KEY_KILL:
+               case mixer::EventType::SEQUENCER_STOP:
+               case mixer::EventType::SEQUENCER_REWIND:
+                       sendToPlugins(MidiEvent(G_MIDI_ALL_NOTES_OFF), 0); break;
+               
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::render(const std::vector<ID>& pluginIds) const
+{
+       pluginHost::processStack(m_channelState->buffer, pluginIds, &state->midiBuffer);
+       state->midiBuffer.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::parseMidi(const MidiEvent& 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(flat, /*delta=*/0); 
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::sendToPlugins(const MidiEvent& e, Frame localFrame) const
+{
+       juce::MidiMessage message = juce::MidiMessage(
+               e.getStatus(), 
+               e.getNote(), 
+               e.getVelocity());
+       state->midiBuffer.addEvent(message, localFrame);
+}
+}} // giada::m::
+
+
+#endif // WITH_VST
\ No newline at end of file
diff --git a/src/core/channels/midiReceiver.h b/src/core/channels/midiReceiver.h
new file mode 100644 (file)
index 0000000..dc83bbe
--- /dev/null
@@ -0,0 +1,89 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_RECEIVER_H
+#define G_CHANNEL_MIDI_RECEIVER_H
+
+
+#ifdef WITH_VST
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct MidiReceiverState;
+
+/* MidiReceiver 
+Takes live action gestures AND recorded actions and redirect them as MIDI events 
+to plug-in soft synths. */
+
+class MidiReceiver
+{
+public:
+
+    MidiReceiver(ChannelState*);
+    MidiReceiver(const patch::Channel&, ChannelState*);
+    MidiReceiver(const MidiReceiver&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e) const;
+    void render(const std::vector<ID>& pluginIds) const;
+
+    /* state
+    Pointer to mutable MidiReceiverState state. */
+
+    std::unique_ptr<MidiReceiverState> state;
+
+private:
+
+    /* parseMidi
+    Takes a live message (e.g. from a MIDI keyboard), strips it and sends it
+    to plug-ins. */
+
+    void parseMidi(const MidiEvent& e) const;
+
+       /* sendToPlugins
+    Enqueues the MIDI event for plug-ins processing. This will be read later on 
+    by the PluginHost. */
+
+    void sendToPlugins(const MidiEvent& e, Frame localFrame) const;
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif // WITH_VST
+
+
+#endif
diff --git a/src/core/channels/midiSender.cpp b/src/core/channels/midiSender.cpp
new file mode 100644 (file)
index 0000000..a5a616a
--- /dev/null
@@ -0,0 +1,92 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/mixer.h"
+#include "core/kernelMidi.h"
+#include "core/channels/state.h"
+#include "midiSender.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiSender::MidiSender(ChannelState* c)
+: state(std::make_unique<MidiSenderState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiSender::MidiSender(const patch::Channel& p, ChannelState* c)
+: state(std::make_unique<MidiSenderState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiSender::MidiSender(const MidiSender& o, ChannelState* c)
+: state(std::make_unique<MidiSenderState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiSender::parse(const mixer::Event& e) const
+{
+       bool isPlaying = m_channelState->isPlaying();
+       bool isEnabled = state->enabled.load();
+
+       if (!isPlaying || !isEnabled)
+               return;
+
+       if (e.type == mixer::EventType::KEY_KILL || 
+           e.type == mixer::EventType::SEQUENCER_STOP)
+               send(MidiEvent(G_MIDI_ALL_NOTES_OFF));
+       else
+       if (e.type == mixer::EventType::ACTION)
+               send(e.action.event);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiSender::send(MidiEvent e) const
+{
+       e.setChannel(state->filter.load());
+       kernelMidi::send(e.getRaw());
+}
+}} // giada::m::
diff --git a/src/core/channels/midiSender.h b/src/core/channels/midiSender.h
new file mode 100644 (file)
index 0000000..5f2d51f
--- /dev/null
@@ -0,0 +1,65 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_MIDI_SENDER_H
+#define G_CHANNEL_MIDI_SENDER_H
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+struct MidiSenderState;
+class MidiSender
+{
+public:
+
+    MidiSender(ChannelState*);
+    MidiSender(const patch::Channel&, ChannelState*);
+    MidiSender(const MidiSender&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e) const;
+
+    /* state
+    Pointer to mutable MidiSenderState state. */
+
+    std::unique_ptr<MidiSenderState> state;
+
+private:
+
+    void send(MidiEvent e) const;
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/sampleActionRecorder.cpp b/src/core/channels/sampleActionRecorder.cpp
new file mode 100644 (file)
index 0000000..10c61c6
--- /dev/null
@@ -0,0 +1,235 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/action.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/mixer.h"
+#include "core/recorderHandler.h"
+#include "core/recManager.h"
+#include "core/channels/state.h"
+#include "sampleActionRecorder.h"
+
+
+namespace giada {
+namespace m
+{
+SampleActionRecorder::SampleActionRecorder(ChannelState* c, SamplePlayerState* sc)
+: m_channelState(c)
+, m_samplePlayerState(sc)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SampleActionRecorder::SampleActionRecorder(const SampleActionRecorder& o, 
+       ChannelState* c, SamplePlayerState* sc)
+: SampleActionRecorder(c, sc)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::parse(const mixer::Event& e) const
+{
+       assert(m_channelState != nullptr);
+
+       switch (e.type) {
+
+               case mixer::EventType::KEY_PRESS:
+                       onKeyPress(); break;
+
+               /* Record a stop event only if channel is SINGLE_PRESS. For any other 
+               mode the key release event is meaningless. */
+
+               case mixer::EventType::KEY_RELEASE:
+                       if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) 
+                               record(MidiEvent::NOTE_OFF);
+                       break;
+
+               case mixer::EventType::KEY_KILL:
+                       if (canRecord()) 
+                               record(MidiEvent::NOTE_KILL);
+                       break;
+               
+               case mixer::EventType::SEQUENCER_FIRST_BEAT:
+                       onFirstBeat(); break;
+
+               case mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS:     
+                       toggleReadActions(); break;            
+               
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::record(int note) const
+{
+       recorderHandler::liveRec(m_channelState->id, MidiEvent(note, 0, 0), 
+               clock::quantize(clock::getCurrentFrame()));
+
+       m_channelState->hasActions = true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::startReadActions() const
+{
+       if (conf::conf.treatRecsAsLoops) 
+               m_channelState->recStatus.store(ChannelStatus::WAIT);
+       else {
+               m_channelState->recStatus.store(ChannelStatus::PLAY);
+               m_channelState->readActions.store(true);
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::stopReadActions(ChannelStatus curRecStatus) const
+{
+       /* First of all, if the clock 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) {
+               m_channelState->recStatus.store(ChannelStatus::OFF);
+           m_channelState->readActions.store(false);
+       }
+       else
+       if (curRecStatus == ChannelStatus::WAIT)
+               m_channelState->recStatus.store(ChannelStatus::OFF);
+       else
+       if (curRecStatus == ChannelStatus::ENDING)
+               m_channelState->recStatus.store(ChannelStatus::PLAY);
+       else
+               m_channelState->recStatus.store(ChannelStatus::ENDING);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::toggleReadActions() 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. */
+
+       if (!m_channelState->hasActions)
+               return;
+
+       bool          readActions = m_channelState->readActions.load();
+       ChannelStatus recStatus   = m_channelState->recStatus.load();
+
+       if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
+               stopReadActions(recStatus);
+       else
+               startReadActions();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onKeyPress() const
+{
+       if (!canRecord()) 
+               return;
+       record(MidiEvent::NOTE_ON);
+
+       /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to 
+       prevent existing actions to interfere with the keypress/keyrel combo. */
+
+       if (m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS)
+               m_channelState->readActions = false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onKeyRelease() const
+{
+       if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) {
+               record(MidiEvent::NOTE_OFF);
+               m_channelState->readActions = true;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onFirstBeat() const
+{
+       ChannelStatus recStatus = m_channelState->recStatus.load();
+
+       switch (recStatus) { 
+
+               case ChannelStatus::ENDING:
+            m_channelState->recStatus.store(ChannelStatus::OFF);
+                       m_channelState->readActions = false;
+                       break;
+
+               case ChannelStatus::WAIT:
+            m_channelState->recStatus.store(ChannelStatus::PLAY);
+                       m_channelState->readActions = true;
+                       break;
+
+               default: break;
+       }       
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleActionRecorder::canRecord() const
+{
+       return recManager::isRecordingAction() && 
+              clock::isRunning()              && 
+              !recManager::isRecordingInput() &&
+                  !m_samplePlayerState->isAnyLoopMode();
+}
+}} // giada::m::
+
diff --git a/src/core/channels/sampleActionRecorder.h b/src/core/channels/sampleActionRecorder.h
new file mode 100644 (file)
index 0000000..0f81b17
--- /dev/null
@@ -0,0 +1,76 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_SAMPLE_ACTION_RECORDER_H
+#define G_CHANNEL_SAMPLE_ACTION_RECORDER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+
+/* SampleActionRecorder
+Records actions for channels and optionally manages the 'read action' state ('R' 
+button on Sample Channels). */
+
+class SampleActionRecorder
+{
+public:
+
+    SampleActionRecorder(ChannelState*, SamplePlayerState*);
+    SampleActionRecorder(const SampleActionRecorder&, ChannelState* c=nullptr, 
+        SamplePlayerState* sc=nullptr);
+
+    void parse(const mixer::Event& e) const;
+
+private:
+    void record(int note) const;
+    void onKeyPress() const;
+    void onKeyRelease() const;
+    void onFirstBeat() const;
+
+    void toggleReadActions() const;
+    void startReadActions() const;
+    void stopReadActions(ChannelStatus curRecStatus) const;
+
+    bool canRecord() const;
+
+    ChannelState*      m_channelState;
+    SamplePlayerState* m_samplePlayerState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/sampleChannel.cpp b/src/core/channels/sampleChannel.cpp
deleted file mode 100644 (file)
index 73eb4f0..0000000
+++ /dev/null
@@ -1,553 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/log.h"
-#include "core/const.h"
-#include "core/wave.h"
-#include "core/model/model.h"
-#include "sampleChannelProc.h"
-#include "sampleChannelRec.h"
-#include "channelManager.h"
-#include "sampleChannel.h"
-
-
-namespace giada {
-namespace m 
-{
-SampleChannel::SampleChannel(bool inputMonitor, int bufferSize,
-       ID columnId, ID id)
-: Channel          (ChannelType::SAMPLE, ChannelStatus::EMPTY, bufferSize,
-                    columnId, id),
-  hasWave          (false),
-  waveId           (0),
-  shift            (0),
-  mode             (ChannelMode::SINGLE_BASIC),
-  quantizing       (false),
-  inputMonitor     (inputMonitor),
-  pitch            (G_DEFAULT_PITCH),
-  tracker          (0),
-  trackerPreview   (0),
-  begin            (0),
-  end              (0),
-  midiInVeloAsVol  (false),
-  midiInReadActions(0x0),
-  midiInPitch      (0x0),
-  bufferOffset     (0),
-  rewinding        (false),
-  rsmp_state       (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
-       if (rsmp_state == nullptr) {
-               u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
-               throw std::bad_alloc();
-       }
-       bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::SampleChannel(const SampleChannel& o)
-: Channel          (o),
-  hasWave          (o.hasWave),
-  waveId           (o.waveId),
-  shift            (o.shift),
-  mode             (o.mode),
-  quantizing       (o.quantizing),
-  inputMonitor     (o.inputMonitor),
-  pitch            (o.pitch),
-  tracker          (o.tracker),
-  trackerPreview   (0),
-  begin            (o.begin),
-  end              (o.end),
-  midiInVeloAsVol  (o.midiInVeloAsVol),
-  midiInReadActions(o.midiInReadActions),
-  midiInPitch      (o.midiInPitch),
-  bufferOffset     (o.bufferOffset),
-  rewinding        (o.rewinding),
-  rsmp_state       (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
-       if (rsmp_state == nullptr) {
-               u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
-               throw std::bad_alloc();
-       }
-       
-       bufferPreview.alloc(o.bufferPreview.countFrames(), G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::SampleChannel(const patch::Channel& p, int bufferSize)
-: Channel          (p, bufferSize),
-  hasWave          (p.waveId != 0),
-  waveId           (p.waveId),
-  shift            (0), // TODO
-  mode             (p.mode),
-  quantizing       (false),
-  inputMonitor     (p.inputMonitor),
-  pitch            (p.pitch),
-  tracker          (0),
-  trackerPreview   (0),
-  begin            (p.begin),
-  end              (p.end),
-  midiInVeloAsVol  (p.midiInVeloAsVol),
-  midiInReadActions(p.midiInReadActions),
-  midiInPitch      (p.midiInPitch),
-  bufferOffset     (0),
-  rewinding        (0),
-  rsmp_state       (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
-       if (rsmp_state == nullptr) {
-               u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
-               throw std::bad_alloc();
-       }
-       
-       bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel* SampleChannel::clone() const
-{
-       return new SampleChannel(*this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::~SampleChannel()
-{
-       if (rsmp_state != nullptr)
-               src_delete(rsmp_state);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::parseEvents(mixer::FrameEvents fe)
-{
-       sampleChannelProc::parseEvents(this, fe);
-       sampleChannelRec::parseEvents(this, fe);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::render(AudioBuffer& out, const AudioBuffer& in, 
-        AudioBuffer& inToOut, bool audible, bool running)
-{
-       sampleChannelProc::render(this, out, in, inToOut, audible, running);
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::rewindBySeq()
-{
-       sampleChannelProc::rewindBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::start(int localFrame, bool doQuantize, int velocity)
-{
-       sampleChannelProc::start(this, localFrame, doQuantize, velocity);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stop()
-{
-       sampleChannelProc::stop(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopBySeq(bool chansStopOnSeqHalt)
-{
-       sampleChannelProc::stopBySeq(this, chansStopOnSeqHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::kill(int localFrame)
-{
-       sampleChannelProc::kill(this, localFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::recordStart(bool canQuantize) 
-{
-       return sampleChannelRec::recordStart(this, canQuantize);
-}
-
-
-bool SampleChannel::recordKill()
-{
-       return sampleChannelRec::recordKill(this);
-}
-
-
-void SampleChannel::recordStop()
-{
-       sampleChannelRec::recordStop(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt)
-{
-       sampleChannelRec::startReadingActions(this, treatRecsAsLoops, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopReadingActions(bool running, bool treatRecsAsLoops, 
-               bool recsStopOnChanHalt)
-{
-       sampleChannelRec::stopReadingActions(this, running, treatRecsAsLoops, 
-               recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopInputRec(int globalFrame)
-{
-       sampleChannelProc::stopInputRec(this, globalFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setMute(bool value)
-{
-       sampleChannelProc::setMute(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setSolo(bool value)
-{
-       sampleChannelProc::setSolo(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt)
-{
-       sampleChannelRec::setReadActions(this, v, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::hasLogicalData() const
-{ 
-       if (!hasWave)
-               return false;
-
-       model::WavesLock wl(model::waves);
-       return model::get(model::waves, waveId).isLogical();
-};
-
-
-bool SampleChannel::hasEditedData() const
-{ 
-       if (!hasWave)
-               return false;
-
-       model::WavesLock wl(model::waves);
-       return model::get(model::waves, waveId).isEdited();
-};
-
-
-bool SampleChannel::hasData() const
-{
-       return hasWave;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setBegin(int f)
-{
-       model::WavesLock lock(model::waves);
-       const Wave& wave = model::get(model::waves, waveId);
-       
-       if (f < 0)
-               f = 0;
-       else
-       if (f > wave.getSize())
-               f = wave.getSize();
-       else
-       if (f >= end)
-               f = end - 1;
-
-       begin          = f;
-       tracker        = f;
-       trackerPreview = f;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setEnd(int f)
-{
-       model::WavesLock lock(model::waves);
-       const Wave& wave = model::get(model::waves, waveId);
-       
-       if (f >= wave.getSize())
-               f = wave.getSize() - 1;
-       else
-       if (f <= begin)
-               f = begin + 1;
-
-       end = f;}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::getBegin() const { return begin; }
-int SampleChannel::getEnd() const   { return end; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setPitch(float v)
-{
-       if (v > G_MAX_PITCH)
-               pitch = G_MAX_PITCH;
-       else
-       if (v < G_MIN_PITCH)
-               pitch = G_MIN_PITCH;
-       else 
-               pitch = v;
-
-// ????        /* if status is off don't slide between frequencies */
-// ???? 
-// ????        if (status & (STATUS_OFF | STATUS_WAIT))
-// ????                src_set_ratio(rsmp_state, 1/pitch);
-}
-
-
-float SampleChannel::getPitch() const { return pitch; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::getPosition() const
-{
-       if (playStatus != ChannelStatus::EMPTY   && 
-           playStatus != ChannelStatus::MISSING && 
-           playStatus != ChannelStatus::OFF)
-               return tracker - begin;
-       else
-               return -1;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::empty()
-{
-       playStatus = ChannelStatus::EMPTY;
-       begin      = 0;
-       end        = 0;
-       tracker    = 0;
-       volume     = G_DEFAULT_VOL;
-       hasActions = false;
-       hasWave    = false;
-       waveId     = 0;
-       sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::pushWave(ID wid, Frame size)
-{
-       playStatus = ChannelStatus::OFF;
-       waveId     = wid;
-       begin      = 0;
-       end        = size;
-       tracker    = 0;
-       hasWave    = true;
-       sendMidiLstatus();
-}
-
-
-void SampleChannel::popWave()
-{
-       playStatus = ChannelStatus::OFF;
-       waveId     = 0;
-       begin      = 0;
-       end        = 0;
-       tracker    = 0;
-       hasWave    = false;
-       sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-std::string SampleChannel::getSamplePath() const
-{
-       if (!hasWave)
-               return "";
-
-       model::WavesLock wl(model::waves);
-       return model::get(model::waves, waveId).getPath();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::canInputRec() const
-{
-       return !hasWave && armed == true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBuffer(AudioBuffer& dest, int start, int offset)
-{
-       assert(offset < dest.countFrames());
-       
-       if (pitch == 1.0) return fillBufferCopy(dest, start, offset);
-       else              return fillBufferResampled(dest, start, offset);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBufferResampled(AudioBuffer& dest, int start, int offset)
-{
-       model::WavesLock lock(model::waves);
-       const Wave& wave = model::get(model::waves, waveId);
-       
-       rsmp_data.data_in       = wave.getFrame(start);        // Source data
-       rsmp_data.input_frames  = end - start;                  // How many readable frames
-       rsmp_data.data_out      = dest[offset];                 // Destination (processed data)
-       rsmp_data.output_frames = dest.countFrames() - offset;  // How many frames to process
-       rsmp_data.end_of_input  = false;
-       rsmp_data.src_ratio     = 1 / pitch;
-
-       src_process(rsmp_state, &rsmp_data);
-
-       return rsmp_data.input_frames_used; // Returns used frames
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBufferCopy(AudioBuffer& dest, int start, int offset)
-{
-       model::WavesLock lock(model::waves);
-       const Wave& wave = model::get(model::waves, waveId);
-
-       int used = dest.countFrames() - offset;
-       if (used > wave.getSize() - start)
-               used = wave.getSize() - start;
-
-       dest.copyData(wave.getFrame(start), used, offset);
-
-       return used;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::isAnyLoopMode() const
-{
-       return mode == ChannelMode::LOOP_BASIC  || 
-              mode == ChannelMode::LOOP_ONCE   || 
-              mode == ChannelMode::LOOP_REPEAT || 
-              mode == ChannelMode::LOOP_ONCE_BAR;
-}
-
-
-bool SampleChannel::isAnySingleMode() const
-{
-       return !isAnyLoopMode();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::isOnLastFrame() const
-{
-       return tracker >= end;
-}
-
-}} // giada::m::
diff --git a/src/core/channels/sampleChannel.h b/src/core/channels/sampleChannel.h
deleted file mode 100644 (file)
index 21f8388..0000000
+++ /dev/null
@@ -1,175 +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_SAMPLE_CHANNEL_H
-#define G_SAMPLE_CHANNEL_H
-
-
-#include <memory>
-#include <functional>
-#include <samplerate.h>
-#include "core/types.h"
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m 
-{
-class Wave;
-
-class SampleChannel : public Channel
-{
-public:
-
-       SampleChannel(bool inputMonitor, int bufferSize, ID columnId, ID id);
-       SampleChannel(const SampleChannel& o);
-       SampleChannel(const patch::Channel& p, int bufferSize);
-       ~SampleChannel();
-
-       SampleChannel* clone() const override;
-       void parseEvents(mixer::FrameEvents fe) override;
-       void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, 
-               bool audible, bool running) override;
-
-       void start(int frame, bool doQuantize, int velocity) override;
-       void stop() override;
-       void kill(int frame) override;
-       bool recordStart(bool canQuantize) override;
-       bool recordKill() override;
-       void recordStop() override;
-       void setMute(bool value) override;
-       void setSolo(bool value) override;
-       void startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) override;
-       void stopReadingActions(bool running, bool treatRecsAsLoops, 
-               bool recsStopOnChanHalt) override;
-       void empty() override;
-       void stopBySeq(bool chansStopOnSeqHalt) override;
-       void rewindBySeq() override;
-       void stopInputRec(int globalFrame) override;
-       bool canInputRec() const override;
-       bool hasLogicalData() const override;
-       bool hasEditedData() const override;
-       bool hasData() const override;
-
-       int   getBegin() const;
-       int   getEnd() const;
-       float getPitch() const;
-       bool isAnyLoopMode() const;
-       bool isAnySingleMode() const;
-       bool isOnLastFrame() const;
-       std::string getSamplePath() const;
-
-       /* getPosition
-       Returns the position of an active sample. If EMPTY o MISSING returns -1. */
-
-       int getPosition() const;
-
-       /* fillBuffer
-       Fills 'dest' buffer at point 'offset' with Wave data taken from 'start'. 
-       Returns how many frames have been used from the original Wave data. It also
-       resamples data if pitch != 1.0f. */
-
-       int fillBuffer(AudioBuffer& dest, int start, int offset);
-
-       /* pushWave
-       Adds a new wave to this channel. */
-
-       void pushWave(ID waveId, Frame waveSize);
-       void popWave();
-
-       void setPitch(float v);
-       void setBegin(int f);
-       void setEnd(int f);
-
-       void setReadActions(bool v, bool recsStopOnChanHalt);
-
-       /* bufferPreview
-       Extra buffer for audio preview. */
-
-       AudioBuffer bufferPreview;
-       
-       /* hasWave
-       Tells if a wave is linked to this channel. */
-       /* TODO - useless: check if waveId != 0 */
-
-       bool hasWave;
-
-       /* waveId
-       ID of a Wave object. Might be useless if hasWave == false. */
-
-       ID waveId;
-
-       int shift;
-       ChannelMode mode;
-       bool quantizing;                    // quantization in progress
-       bool inputMonitor;  
-       float pitch;
-       
-       Frame tracker;         // chan position
-       Frame trackerPreview;  // chan position for audio preview
-
-       /* begin, end
-       Begin/end point to read wave data from/to. */
-
-       Frame begin;  
-       Frame end;    
-
-       /* midiIn*
-       MIDI input parameters. */
-
-       bool     midiInVeloAsVol;
-       uint32_t midiInReadActions;
-       uint32_t midiInPitch;
-
-       /* bufferOffset
-       Offset used while filling the internal buffer with audio data. Value is 
-       greater than zero on start sample. */
-       
-       Frame bufferOffset;
-
-       /* rewinding
-       Tells whether a rewind event is taking place. Used to fill the audio
-       buffer twice. */
-
-       bool rewinding; 
-
-private:
-
-       /* rsmp_state, rsmp_data
-       Structs from libsamplerate. */
-
-       SRC_STATE* rsmp_state;
-       SRC_DATA   rsmp_data;
-
-       int fillBufferResampled(AudioBuffer& dest, int start, int offset);
-       int fillBufferCopy     (AudioBuffer& dest, int start, int offset);
-};
-
-}} // giada::m::
-
-
-#endif
diff --git a/src/core/channels/sampleChannelProc.cpp b/src/core/channels/sampleChannelProc.cpp
deleted file mode 100644 (file)
index cd8b193..0000000
+++ /dev/null
@@ -1,526 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/math.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/const.h"
-#include "core/pluginHost.h"
-#include "core/mixerHandler.h"
-#include "sampleChannelProc.h"
-
-
-namespace giada {
-namespace m {
-namespace sampleChannelProc
-{
-namespace
-{
-void rewind_(SampleChannel* ch, Frame localFrame)
-{
-       /* Quantization stops on rewind. */
-
-       ch->quantizing = false; 
-
-       if (ch->isPlaying()) { 
-               ch->rewinding    = true;
-               ch->bufferOffset = localFrame;
-       }
-       else
-               ch->tracker = ch->begin;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* quantize
-Starts channel according to quantizer. */
-
-void quantize_(SampleChannel* ch, int localFrame)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::OFF:
-                       ch->playStatus   = ChannelStatus::PLAY;
-                       ch->bufferOffset = localFrame;
-                       ch->sendMidiLstatus();
-                       // ch->quantizing = false is set by sampleChannelRec::quantize()
-                       break;
-
-               default:
-                       rewind_(ch, localFrame);
-                       break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onBar
-Things to do when the sequencer is on a bar. */
-
-void onBar_(SampleChannel* ch, int localFrame)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::PLAY:
-                       if (ch->mode == ChannelMode::LOOP_REPEAT)
-                               rewind_(ch, localFrame);
-                       break;
-
-               case ChannelStatus::WAIT:
-                       if (ch->mode == ChannelMode::LOOP_ONCE_BAR) {
-                               ch->playStatus       = ChannelStatus::PLAY;
-                               ch->bufferOffset = localFrame;
-                               ch->sendMidiLstatus();
-                       }
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onFirstBeat
-Things to do when the sequencer is on the first beat. */
-
-void onFirstBeat_(SampleChannel* ch, Frame localFrame)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::PLAY: 
-                       if (ch->isAnyLoopMode())
-                               rewind_(ch, localFrame);
-                       break;
-
-               case ChannelStatus::WAIT:
-                       ch->playStatus       = ChannelStatus::PLAY;
-                       ch->bufferOffset = localFrame;
-                       ch->sendMidiLstatus();
-                       break;
-
-               case ChannelStatus::ENDING: 
-                       if (ch->isAnyLoopMode())
-                               kill(ch, localFrame);
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onLastFrame
-Things to do when the sample has reached the end (i.e. last frame). Called by
-prepareBuffer(). */
-
-void onLastFrame_(SampleChannel* ch, bool running)
-{
-       switch (ch->playStatus) {
-               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 ((ch->mode == ChannelMode::SINGLE_BASIC   || 
-                                ch->mode == ChannelMode::SINGLE_PRESS   ||
-                                ch->mode == ChannelMode::SINGLE_RETRIG) || 
-                               (ch->isAnyLoopMode() && !running))
-                               ch->playStatus = ChannelStatus::OFF;
-                       else
-                       if (ch->mode == ChannelMode::LOOP_ONCE     ||
-                           ch->mode == ChannelMode::LOOP_ONCE_BAR)
-                               ch->playStatus = ChannelStatus::WAIT;
-                       ch->sendMidiLstatus();
-                       break;                  
-
-               case ChannelStatus::ENDING:
-                       /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested 
-                       their termination), stop 'em. Let them wait otherwise. */
-                       if (ch->mode == ChannelMode::LOOP_ONCE ||
-                           ch->mode == ChannelMode::LOOP_ONCE_BAR)
-                               ch->playStatus = ChannelStatus::WAIT;
-                       else {
-                               ch->playStatus = ChannelStatus::OFF;
-                               ch->sendMidiLstatus();
-                       }
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void processIO_(SampleChannel* ch, m::AudioBuffer& out, const m::AudioBuffer& in, 
-       bool running)
-{
-       assert(out.countSamples() == ch->buffer.countSamples());
-       if (in.isAllocd())
-               assert(in.countSamples() == ch->buffer.countSamples());
-
-       /* If armed and input buffer is not empty (i.e. input device available) and
-       input monitor is on, copy input buffer to channel buffer: this enables the 
-       input monitoring. The channel buffer will be overwritten later on by 
-       pluginHost::processStack, so that you would record "clean" audio 
-       (i.e. not plugin-processed). */
-
-       if (ch->armed && in.isAllocd() && ch->inputMonitor) {
-               for (int i=0; i<ch->buffer.countFrames(); i++)
-                       for (int j=0; j<ch->buffer.countChannels(); j++)
-                               ch->buffer[i][j] += in[i][j];   // add, don't overwrite
-       }
-
-#ifdef WITH_VST
-       pluginHost::processStack(ch->buffer, ch->pluginIds);
-#endif
-
-       for (int i=0; i<out.countFrames(); i++) {
-               if (running)
-                       ch->calcVolumeEnvelope();
-               if (!ch->mute)
-                       for (int j=0; j<out.countChannels(); j++)
-                               out[i][j] += ch->buffer[i][j] * ch->volume * ch->volume_i * ch->calcPanning(j); 
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void processPreview_(SampleChannel* ch, m::AudioBuffer& out)
-{
-       ch->bufferPreview.clear();
-
-       /* If the tracker exceedes the end point and preview is looped, split the 
-       rendering as in SampleChannel::reset(). */
-
-       if (ch->trackerPreview == ch->end)
-               ch->trackerPreview = ch->begin;
-       else
-       if (ch->trackerPreview + ch->bufferPreview.countFrames() >= ch->end) {
-               int offset = ch->end - ch->trackerPreview;
-               ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
-               ch->trackerPreview = ch->begin;
-               if (ch->previewMode == PreviewMode::LOOP)
-                       ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->begin, offset);
-               else
-               if (ch->previewMode == PreviewMode::NORMAL)
-                       ch->previewMode = PreviewMode::NONE;
-       }
-       else
-               ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
-
-       for (int i=0; i<out.countFrames(); i++)
-               for (int j=0; j<out.countChannels(); j++)
-                       out[i][j] += ch->bufferPreview[i][j] * ch->volume * ch->calcPanning(j); 
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void fillBuffer_(SampleChannel* ch, bool running)
-{
-       ch->buffer.clear();
-
-       if (!ch->hasData() || !ch->isPlaying())
-               return;
-
-       if (ch->rewinding) {
-
-               /* Fill the tail. */
-               
-               if (!ch->isOnLastFrame())
-                       ch->fillBuffer(ch->buffer, ch->tracker, 0);
-               
-               /* Reset tracker to begin point. */
-
-               ch->tracker = ch->begin;
-               
-               /* Then fill the new head. */
-
-               ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, ch->bufferOffset);
-               ch->bufferOffset = 0;
-               ch->rewinding    = false;
-       }
-       else {
-               Frame framesUsed = ch->fillBuffer(ch->buffer, ch->tracker, ch->bufferOffset);
-               ch->tracker      += framesUsed;
-               ch->bufferOffset  = 0;
-               if (ch->isOnLastFrame()) {
-                       onLastFrame_(ch, running);
-                       ch->tracker = ch->begin;
-                       if (ch->mode == ChannelMode::LOOP_BASIC  || 
-                           ch->mode == ChannelMode::LOOP_REPEAT || 
-                           ch->mode == ChannelMode::SINGLE_ENDLESS) {
-                               /* framesUsed might be imprecise when working with resampled 
-                               audio, which could cause a buffer overflow if used as offset.
-                               Let's clamp it to be at most buffer->countFrames(). */
-                               ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, 
-                                       u::math::bound(framesUsed, 0, ch->buffer.countFrames() - 1));
-                       }
-               }
-       }
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void kill(SampleChannel* ch, int localFrame)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::WAIT:
-               case ChannelStatus::PLAY:
-               case ChannelStatus::ENDING:
-                       /*  Clear data in range [localFrame, (buffer.size)) if the kill event 
-                       occurs in the middle of the buffer. */
-                       if (localFrame != 0)
-                               ch->buffer.clear(localFrame);
-                       ch->playStatus = ChannelStatus::OFF;
-                       ch->sendMidiLstatus();
-                       rewind_(ch, localFrame);
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stop(SampleChannel* ch)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::PLAY:
-                       if (ch->mode == ChannelMode::SINGLE_PRESS)
-                               kill(ch, 0);
-                       break;
-
-               default:
-                       /* If quantizing, stop a SINGLE_PRESS immediately. */
-                       if (ch->mode == ChannelMode::SINGLE_PRESS && ch->quantizing)
-                               ch->quantizing = false; 
-                       break;          
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopInputRec(SampleChannel* ch, int globalFrame)
-{
-       /* Start all sample channels in loop mode that were armed, i.e. that were
-       recording stuff and not yet in play. They are also started in force mode, i.e.
-       they must start playing right away at the current global frame, not at the 
-       next first beat. */
-       if (ch->isAnyLoopMode() && ch->playStatus == ChannelStatus::OFF && ch->armed) {
-               ch->playStatus  = ChannelStatus::PLAY;
-               ch->tracker = globalFrame;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt)
-{
-       switch (ch->playStatus) {
-               case ChannelStatus::WAIT:
-                       /* Loop-mode channels in wait status get stopped right away. */
-                       if (ch->isAnyLoopMode())
-                               ch->playStatus = ChannelStatus::OFF;
-                       break;
-
-               case ChannelStatus::PLAY:
-                       /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end 
-                       otherwise); b) when a channel is reading (and playing) actions. */
-                       if (chansStopOnSeqHalt)
-                               if (ch->isAnyLoopMode() || ch->isReadingActions())
-                                       kill(ch, 0);
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindBySeq(SampleChannel* ch)
-{
-       /* Rewind LOOP_ANY or SINGLE_ANY only if it's in read-record-mode. Rewind by 
-       sequencer is a user-generated event, it always occurs on local frame 0. */
-
-       if (ch->hasData()) {
-               if ((ch->isAnyLoopMode()) || (ch->recStatus == ChannelStatus::PLAY && (ch->isAnySingleMode())))
-                       rewind_(ch, 0);
-       }       
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(SampleChannel* ch, bool value)
-{
-       ch->mute = value;
-
-       // This is for processing playing_inaudible
-       ch->sendMidiLstatus();  
-
-       ch->sendMidiLmute();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(SampleChannel* ch, bool value)
-{
-       ch->solo = value;
-
-       // This is for processing playing_inaudible
-       model::ChannelsLock l(model::channels);
-       for (Channel* c : model::channels)
-               c->sendMidiLstatus();
-       
-       ch->sendMidiLsolo();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity)
-{
-       /* For one-shot modes, velocity drives the internal volume. */
-       if (velocity != 0) {
-               if (ch->isAnySingleMode() && ch->midiInVeloAsVol)
-                       ch->volume_i = u::math::map<int, float>(velocity, 0, G_MAX_VELOCITY, 0.0, 1.0);         
-       }
-
-       switch (ch->playStatus) {
-               case ChannelStatus::OFF:
-                       ch->bufferOffset = localFrame;
-                       if (ch->isAnyLoopMode()) {
-                               ch->playStatus = ChannelStatus::WAIT;
-                               ch->sendMidiLstatus();
-                       }
-                       else {
-                               if (doQuantize)
-                                       ch->quantizing = true;
-                               else {
-                                       ch->playStatus = ChannelStatus::PLAY;
-                                       ch->sendMidiLstatus();
-                               }
-                       }
-                       break;
-
-               case ChannelStatus::PLAY:
-                       if (ch->mode == ChannelMode::SINGLE_RETRIG) {
-                               if (doQuantize)
-                                       ch->quantizing = true;
-                               else
-                                       rewind_(ch, localFrame);
-                       }
-                       else
-                       if (ch->isAnyLoopMode() || ch->mode == ChannelMode::SINGLE_ENDLESS) {
-                               ch->playStatus = ChannelStatus::ENDING;
-                               ch->sendMidiLstatus();
-                       }
-                       else
-                       if (ch->mode == ChannelMode::SINGLE_BASIC) {
-                               rewind_(ch, localFrame);
-                               ch->playStatus = ChannelStatus::OFF;
-                               ch->sendMidiLstatus();
-                       }
-                       break;
-
-               case ChannelStatus::WAIT:
-                       ch->playStatus = ChannelStatus::OFF;
-                       ch->sendMidiLstatus();
-                       break;
-
-               case ChannelStatus::ENDING:
-                       ch->playStatus = ChannelStatus::PLAY;
-                       ch->sendMidiLstatus();
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in, 
-               AudioBuffer& inToOut, bool audible, bool running)
-{
-       fillBuffer_(ch, running);
-
-       if (audible)
-               processIO_(ch, out, in, running);
-
-       if (ch->isPreview())
-               processPreview_(ch, out);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
-{
-       if (!ch->hasData())
-               return;
-
-       /* Quantize only if is single mode and in quantizer-wait mode and a
-       quantizer step has passed. */
-
-       if (ch->isAnySingleMode() && ch->quantizing && fe.quantoPassed) 
-               quantize_(ch, fe.frameLocal);
-       if (fe.onBar)
-               onBar_(ch, fe.frameLocal);
-       if (fe.onFirstBeat)
-               onFirstBeat_(ch, fe.frameLocal);
-}
-}}};
diff --git a/src/core/channels/sampleChannelProc.h b/src/core/channels/sampleChannelProc.h
deleted file mode 100644 (file)
index 075f125..0000000
+++ /dev/null
@@ -1,89 +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_SAMPLE_CHANNEL_PROC_H
-#define G_SAMPLE_CHANNEL_PROC_H
-
-
-#include "core/mixer.h"
-#include "core/audioBuffer.h"
-#include "core/types.h"
-
-
-namespace giada {
-namespace m 
-{
-class SampleChannel;
-
-namespace sampleChannelProc
-{
-/**/
-void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in, 
-    AudioBuffer& inToOut, bool audible, bool running);
-
-/* parseEvents
-Parses events gathered by Mixer::masterPlay(). */
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents ev);
-
-/* kill
-Stops a channel abruptly. */
-
-void kill(SampleChannel* ch, int localFrame);
-
-/* stop
-Stops a channel normally (via key or MIDI). */
-
-void stop(SampleChannel* ch);
-
-/* stopInputRec
-Prepare a channel for playing when the input recording is done. */
-
-void stopInputRec(SampleChannel* ch, int globalFrame);
-
-/* stopBySeq
-Stops a channel when the stop button on main transport is pressed. */
-
-void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt);
-
-/* rewind
-Rewinds channel when rewind button on main transport is pressed. */
-
-void rewindBySeq(SampleChannel* ch);
-
-/* start
-Starts a channel. doQuantize = false (don't quantize) when Mixer is reading 
-actions from Recorder. */
-
-void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity);
-
-void setMute(SampleChannel* ch, bool value);
-void setSolo(SampleChannel* ch, bool value);
-}}};
-
-
-#endif
diff --git a/src/core/channels/sampleChannelRec.cpp b/src/core/channels/sampleChannelRec.cpp
deleted file mode 100644 (file)
index 1b74a61..0000000
+++ /dev/null
@@ -1,276 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/math.h"
-#include "core/channels/sampleChannel.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/recManager.h"
-#include "core/const.h"
-#include "core/conf.h"
-#include "core/clock.h"
-#include "core/action.h"
-#include "core/kernelAudio.h"
-#include "sampleChannelRec.h"
-
-
-namespace giada {
-namespace m {
-namespace sampleChannelRec
-{
-namespace
-{
-/* onFirstBeat
-Things to do when the sequencer is on the first beat. */
-
-void onFirstBeat_(SampleChannel* ch, bool recsStopOnChanHalt)
-{
-       switch (ch->recStatus) {
-               case ChannelStatus::ENDING:
-                       ch->recStatus = ChannelStatus::OFF;
-                       setReadActions(ch, false, recsStopOnChanHalt);  // rec stop
-                       break;
-
-               case ChannelStatus::WAIT:
-                       ch->recStatus = ChannelStatus::PLAY;
-                       setReadActions(ch, true, recsStopOnChanHalt);   // rec start
-                       break;
-
-               default: break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recorderCanRec_(SampleChannel* ch)
-{
-       /* Can record on a channel if:
-               - recorder is on
-               - mixer is running
-               - mixer is not recording a take somewhere
-               - channel is MIDI or SAMPLE type with data in it  */
-
-       return recManager::isRecordingAction() && 
-              clock::isRunning()              && 
-              !recManager::isRecordingInput() && 
-              (ch->type == ChannelType::MIDI || (ch->type == ChannelType::SAMPLE && ch->hasData()));
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* calcVolumeEnv
-Computes any changes in volume done via envelope tool. */
-
-void calcVolumeEnv_(SampleChannel* ch, const Action& a1)
-{
-       assert(a1.next != nullptr);
-
-       const Action a2 = *a1.next;
-
-       double vf1 = u::math::map<int, double>(a1.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
-       double vf2 = u::math::map<int, double>(a2.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
-
-       ch->volume_i = vf1;
-       ch->volume_d = a2.frame == a1.frame ? 0 : (vf2 - vf1) / (a2.frame - a1.frame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void parseAction_(SampleChannel* ch, const Action& a, int localFrame, int globalFrame)
-{
-       switch (a.event.getStatus()) {
-               case MidiEvent::NOTE_ON:
-                       if (ch->isAnySingleMode())
-                               ch->start(localFrame, /*quantize=*/false, /*velocity=*/0);
-                       break;
-               case MidiEvent::NOTE_OFF:
-                       if (ch->isAnySingleMode())
-                               ch->stop();
-                       break;
-               case MidiEvent::NOTE_KILL:
-                       if (ch->isAnySingleMode())
-                               ch->kill(localFrame);
-                       break;
-               case MidiEvent::ENVELOPE:
-                       calcVolumeEnv_(ch, a);
-                       break;
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordKeyPressAction_(SampleChannel* ch)
-{
-       if (!recorderCanRec_(ch))
-               return;
-
-       /* Disable reading actions while recording SINGLE_PRESS mode. Don't let 
-       existing actions interfere with the current one being recorded. */
-
-       if (ch->mode == ChannelMode::SINGLE_PRESS)
-               ch->readActions = false;
-       
-       recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_ON, 0, 0));
-       ch->hasActions = true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void quantize_(SampleChannel* ch, bool quantoPassed)
-{
-       /* Skip if in loop mode or not in a quantization stage. Otherwise the 
-       quantization wait has expired: record the keypress.  */
-
-       if (!ch->isAnyLoopMode() && ch->quantizing && quantoPassed && ch->playStatus == ChannelStatus::PLAY) {
-               ch->quantizing = false;
-               recordKeyPressAction_(ch);
-       }
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
-{
-       if (!ch->hasWave)
-               return;
-       quantize_(ch, fe.quantoPassed);
-       if (fe.onFirstBeat)
-               onFirstBeat_(ch, conf::conf.recsStopOnChanHalt);
-       if (ch->readActions && fe.actions != nullptr)
-               for (const Action& action : *fe.actions)
-                       if (action.channelId == ch->id)
-                               parseAction_(ch, action, fe.frameLocal, fe.frameGlobal);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recordStart(SampleChannel* ch, bool canQuantize)
-{
-       /* Record a 'start' event if the quantizer is off, otherwise let mixer to 
-       handle it when a quantoWait has passed (see quantize_()). Also skip if 
-       channel is in any loop mode, where KEYPRESS and KEYREL are meaningless. */
-       
-       if (!canQuantize && !ch->isAnyLoopMode() && recorderCanRec_(ch))
-               recordKeyPressAction_(ch);
-       return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recordKill(SampleChannel* ch)
-{
-       /* Don't record NOTE_KILL actions for LOOP channels. */
-       if (recorderCanRec_(ch) && !ch->isAnyLoopMode()) {
-               recorder::rec(ch->id, clock::getCurrentFrame(), MidiEvent(MidiEvent::NOTE_KILL, 0, 0));
-               ch->hasActions = true;
-       }
-       return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordStop(SampleChannel* ch)
-{
-       /* Record a stop event only if channel is SINGLE_PRESS. For any other mode 
-       the stop event is meaningless. */
-       if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS)
-               recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_OFF, 0, 0));
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt)
-{
-       ch->readActions = v;
-       if (!v && recsStopOnChanHalt)
-               ch->kill(0); // FIXME - wrong frame value
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, bool recsStopOnChanHalt)
-{
-       if (treatRecsAsLoops)
-               ch->recStatus = ChannelStatus::WAIT;
-       else
-               setReadActions(ch, true, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopReadingActions(SampleChannel* ch, bool isClockRunning, bool treatRecsAsLoops, 
-               bool recsStopOnChanHalt)
-{
-       /* First of all, if the clock is not running just stop and disable everything.
-       Then if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put the
-       channel in REC_ENDING status. */
-
-       if (!isClockRunning) {
-               ch->recStatus = ChannelStatus::OFF;
-               setReadActions(ch, false, false);
-       }
-       else
-       if (ch->recStatus == ChannelStatus::WAIT)
-               ch->recStatus = ChannelStatus::OFF;
-       else
-       if (ch->recStatus == ChannelStatus::ENDING)
-               ch->recStatus = ChannelStatus::PLAY;
-       else
-       if (treatRecsAsLoops)
-               ch->recStatus = ChannelStatus::ENDING;
-       else
-               setReadActions(ch, false, recsStopOnChanHalt);
-}
-}}};
diff --git a/src/core/channels/sampleChannelRec.h b/src/core/channels/sampleChannelRec.h
deleted file mode 100644 (file)
index 29a9240..0000000
+++ /dev/null
@@ -1,71 +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_SAMPLE_CHANNEL_REC_H
-#define G_SAMPLE_CHANNEL_REC_H
-
-
-namespace giada {
-namespace m 
-{
-class SampleChannel;
-
-namespace sampleChannelRec
-{
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe);
-
-/* recordStart
-Records a 'start' action if capable of. Returns true if a start() call can
-be performed. */
-
-bool recordStart(SampleChannel* ch, bool doQuantize);
-
-/* recordKill
-Records a 'kill' action if capable of. Returns true if a kill() call can
-be performed. */
-
-bool recordKill(SampleChannel* ch);
-
-/* recordStop
-Ends overdub mode SINGLE_PRESS channels. */
-
-void recordStop(SampleChannel* ch);
-
-/* setReadActions
-If enabled (v == true), Recorder will read actions from channel 'ch'. If 
-recsStopOnChanHalt == true and v == false, will also kill the channel. */
-
-void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt);
-
-void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, 
-       bool recsStopOnChanHalt);
-void stopReadingActions(SampleChannel* ch, bool isClockRunning, 
-       bool treatRecsAsLoops, bool recsStopOnChanHalt);
-}}};
-
-
-#endif
\ No newline at end of file
diff --git a/src/core/channels/sampleController.cpp b/src/core/channels/sampleController.cpp
new file mode 100644 (file)
index 0000000..0dc1281
--- /dev/null
@@ -0,0 +1,419 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/conf.h"
+#include "core/clock.h"
+#include "core/action.h"
+#include "core/mixer.h"
+#include "core/channels/state.h"
+#include "utils/math.h"
+#include "sampleController.h"
+
+
+namespace giada {
+namespace m 
+{
+namespace
+{
+constexpr int Q_ACTION_PLAY   = 0;
+constexpr int Q_ACTION_REWIND = 1;
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+SampleController::SampleController(ChannelState* c, SamplePlayerState* s)
+: m_channelState     (c)
+, m_samplePlayerState(s) 
+{
+       m_samplePlayerState->quantizer.schedule(Q_ACTION_PLAY, 
+               [&status = m_channelState->playStatus, &offset = m_samplePlayerState->offset]
+               (Frame delta)
+       {
+               offset = delta;
+               status = ChannelStatus::PLAY;
+       });
+
+       m_samplePlayerState->quantizer.schedule(Q_ACTION_REWIND, [this] (Frame delta)
+       {
+               rewind(delta);
+       });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SampleController::SampleController(const SampleController& o, ChannelState* c, SamplePlayerState* s)
+: SampleController(c, s)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::parse(const mixer::Event& e) const
+{
+       assert(m_channelState      != nullptr);
+       assert(m_samplePlayerState != nullptr);
+
+       switch (e.type) {
+               case mixer::EventType::KEY_PRESS:
+                       press(e.delta, e.action.event.getVelocity(), /*manual=*/true); break;
+
+               case mixer::EventType::KEY_RELEASE:
+                       release(e.delta); break;
+
+               case mixer::EventType::KEY_KILL:
+                       kill(e.delta); break;
+
+               case mixer::EventType::SEQUENCER_FIRST_BEAT:
+                       if (clock::isRunning())
+                               onFirstBeat(e.delta); 
+                       break;
+               
+               case mixer::EventType::SEQUENCER_BAR:
+                       onBar(e.delta);  break;
+               
+               case mixer::EventType::SEQUENCER_STOP:  
+                       onStopBySeq(); break;
+               
+               case mixer::EventType::ACTION:
+                       if (m_channelState->readActions.load() == true)
+                               parseAction(e.action, e.delta);
+                       break;
+                       
+               case mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS:     
+                       toggleReadActions(); break;   
+               
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onLastFrame() const
+{
+    ChannelStatus    playStatus = m_channelState->playStatus.load();
+    SamplePlayerMode mode       = m_samplePlayerState->mode.load();
+    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) || 
+            (m_samplePlayerState->isAnyLoopMode() && !running))
+            playStatus = ChannelStatus::OFF;
+        else
+        if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+            playStatus = ChannelStatus::WAIT;
+    }
+    else
+    if (playStatus == ChannelStatus::ENDING) {
+               /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested 
+               their termination), stop 'em. Let them wait otherwise. */
+               if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+                       playStatus = ChannelStatus::WAIT;                       
+        else
+            playStatus = ChannelStatus::OFF;
+       }
+
+    m_channelState->playStatus.store(playStatus);      
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::advance(Frame bufferSize) const
+{
+       Range<Frame> block(clock::getCurrentFrame(), clock::getCurrentFrame() + bufferSize);
+       m_samplePlayerState->quantizer.advance(block, clock::getQuantizerStep());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::press(Frame localFrame, int velocity, bool manual) const
+{
+    ChannelStatus    playStatus = m_channelState->playStatus.load();
+    SamplePlayerMode mode       = m_samplePlayerState->mode.load();
+       bool             isLoop     = m_samplePlayerState->isAnyLoopMode();
+
+    switch (playStatus) {
+               case ChannelStatus::OFF:
+                       playStatus = pressWhileOff(localFrame, velocity, isLoop, manual); break;
+
+               case ChannelStatus::PLAY:
+                       playStatus = pressWhilePlay(localFrame, mode, isLoop, manual); break;
+
+               case ChannelStatus::WAIT:
+                       playStatus = ChannelStatus::OFF; break;
+
+               case ChannelStatus::ENDING:
+                       playStatus = ChannelStatus::PLAY; break;
+
+               default: break;
+       }
+
+    m_channelState->playStatus.store(playStatus); 
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::release(Frame localFrame) const
+{
+       /* Key release is meaningful only for SINGLE_PRESS modes. */
+       
+       if (m_samplePlayerState->mode.load() != 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 (m_channelState->playStatus.load() == ChannelStatus::PLAY)
+               kill(localFrame);
+       else
+    if (m_samplePlayerState->quantizer.isTriggered())
+        m_samplePlayerState->quantizer.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::kill(Frame localFrame) const
+{
+    m_channelState->playStatus.store(ChannelStatus::OFF);
+    m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load());
+    m_samplePlayerState->quantizing = false; 
+
+    /*  Clear data in range [localFrame, (buffer.size)) if the kill event occurs
+    in the middle of the buffer. */
+
+    if (localFrame != 0)
+        m_channelState->buffer.clear(localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::rewind(Frame localFrame) const
+{
+       /* Quantization stops on rewind. */
+
+       m_samplePlayerState->quantizer.clear(); 
+
+       if (m_channelState->isPlaying()) { 
+               m_samplePlayerState->rewinding = true;
+               m_samplePlayerState->offset    = localFrame;
+       }
+       else
+               m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ChannelStatus SampleController::pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const
+{
+       m_samplePlayerState->offset = localFrame;
+
+       if (isLoop)
+               return ChannelStatus::WAIT;
+
+       if (m_samplePlayerState->velocityAsVol.load() == true)  
+               m_channelState->volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME); 
+
+       if (clock::canQuantize() && manual) { // manual: don't quantize recorded actions
+               m_samplePlayerState->quantizer.trigger(Q_ACTION_PLAY);
+               return ChannelStatus::OFF;
+       }
+       else
+               return ChannelStatus::PLAY;
+}
+
+
+ChannelStatus SampleController::pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const
+{
+       if (mode == SamplePlayerMode::SINGLE_RETRIG) {
+               if (clock::canQuantize() && manual)  // manual: don't quantize recorded actions 
+                       m_samplePlayerState->quantizer.trigger(Q_ACTION_REWIND);
+               else
+                       rewind(localFrame);
+               return ChannelStatus::PLAY;
+       }
+
+       if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS)
+               return ChannelStatus::ENDING;
+
+       if (mode == SamplePlayerMode::SINGLE_BASIC) {
+               rewind(localFrame);
+               return ChannelStatus::OFF;
+       }
+
+       return ChannelStatus::OFF;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onBar(Frame localFrame) const
+{
+       G_DEBUG("onBar ch=" << m_channelState->id);
+
+    ChannelStatus    playStatus = m_channelState->playStatus.load();
+    SamplePlayerMode mode       = m_samplePlayerState->mode.load();
+
+    if (playStatus == ChannelStatus::PLAY && mode == SamplePlayerMode::LOOP_REPEAT)
+        rewind(localFrame);
+    else
+    if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR) {
+               m_channelState->playStatus.store(ChannelStatus::PLAY);
+        m_samplePlayerState->offset = localFrame;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onFirstBeat(Frame localFrame) const
+{
+G_DEBUG("onFirstBeat ch=" << m_channelState->id << ", localFrame=" << localFrame);
+
+    ChannelStatus playStatus = m_channelState->playStatus.load();
+       bool          isLoop     = m_samplePlayerState->isAnyLoopMode();
+
+       switch (playStatus) { 
+
+               case ChannelStatus::PLAY:
+                       if (isLoop) 
+                               rewind(localFrame); 
+                       break;
+               
+               case ChannelStatus::WAIT:
+               m_samplePlayerState->offset = localFrame;
+            m_channelState->playStatus.store(ChannelStatus::PLAY);
+                       break;
+
+               case ChannelStatus::ENDING:
+                       if (isLoop) 
+                               kill(localFrame);
+                       break;
+               
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onStopBySeq() const
+{
+       G_DEBUG("onStopBySeq ch=" << m_channelState->id);
+
+       ChannelStatus playStatus       = m_channelState->playStatus.load();
+       bool          isReadingActions = m_channelState->readActions.load() == true;
+       bool          isLoop           = m_samplePlayerState->isAnyLoopMode();
+
+       switch (playStatus) {
+
+               case ChannelStatus::WAIT:
+                       /* Loop-mode channels in wait status get stopped right away. */
+                       if (isLoop)
+                               m_channelState->playStatus.store(ChannelStatus::OFF);   
+                       break;
+
+               case ChannelStatus::PLAY:
+                       /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end 
+                       otherwise); b) when a channel is reading (and playing) actions. */
+                       if (conf::conf.chansStopOnSeqHalt)
+                               if (isLoop || isReadingActions)
+                                       kill(0);
+                       break;
+
+               default: break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::parseAction(const Action& a, Frame localFrame) const
+{
+       bool isLoop = m_samplePlayerState->isAnyLoopMode();
+
+       switch (a.event.getStatus()) {
+               case MidiEvent::NOTE_ON:
+                       if (!isLoop)
+                               press(localFrame, /*velocity=*/G_MAX_VELOCITY, /*manual=*/false);
+                       break;
+               case MidiEvent::NOTE_OFF:
+                       if (!isLoop)
+                               release(localFrame);
+                       break;
+               case MidiEvent::NOTE_KILL:
+                       if (!isLoop)
+                               kill(localFrame);
+                       break;
+               case MidiEvent::ENVELOPE:
+                       //calcVolumeEnv_(ch, a); TODO
+                       break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::toggleReadActions() const
+{
+       ChannelStatus recStatus = m_channelState->recStatus.load();
+       if (clock::isRunning() && recStatus == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops)
+               kill(0);
+}
+}} // giada::m::
diff --git a/src/core/channels/sampleController.h b/src/core/channels/sampleController.h
new file mode 100644 (file)
index 0000000..7a38dd2
--- /dev/null
@@ -0,0 +1,76 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_SAMPLE_CONTROLLER_H
+#define G_CHANNEL_SAMPLE_CONTROLLER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct SamplePlayerState;
+class SampleController
+{
+public:
+
+    SampleController(ChannelState*, SamplePlayerState*);
+    SampleController(const SampleController&, ChannelState* c=nullptr, SamplePlayerState* s=nullptr);
+
+    void parse(const mixer::Event& e) const;
+    void onLastFrame() const;
+    void advance(Frame bufferSize) const;
+
+private:
+
+    void press(Frame localFrame, int velocity, bool manual) const;
+    void release(Frame localFrame) const;
+    void kill(Frame localFrame) const;
+    void rewind(Frame localFrame) const;
+
+    ChannelStatus pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const;
+    ChannelStatus pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const;
+    void toggleReadActions() const;
+
+    void onBar(Frame localFrame) const;
+    void onFirstBeat(Frame localFrame) const;
+    void onStopBySeq() const;
+    void parseAction(const Action& a, Frame localFrame) const;
+    
+    ChannelState*      m_channelState;
+    SamplePlayerState* m_samplePlayerState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/samplePlayer.cpp b/src/core/channels/samplePlayer.cpp
new file mode 100644 (file)
index 0000000..e9b96ac
--- /dev/null
@@ -0,0 +1,249 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <algorithm>
+#include <cassert>
+#include "core/channels/channel.h"
+#include "core/channels/state.h"
+#include "core/wave.h"
+#include "core/clock.h"
+#include "samplePlayer.h"
+
+
+namespace giada {
+namespace m 
+{
+SamplePlayer::SamplePlayer(ChannelState* c)
+: state             (std::make_unique<SamplePlayerState>())
+, m_waveId          (0)
+, m_sampleController(c, state.get())
+, m_channelState    (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayer::SamplePlayer(const SamplePlayer& o, ChannelState* c)
+: state             (std::make_unique<SamplePlayerState>(*o.state))
+, m_waveId          (o.m_waveId)
+, m_waveReader      (o.m_waveReader)
+, m_sampleController(o.m_sampleController, c, state.get())
+, m_channelState    (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayer::SamplePlayer(const patch::Channel& p, ChannelState* c)
+: state             (std::make_unique<SamplePlayerState>(p))
+, m_waveId          (p.waveId)
+, m_sampleController(c, state.get())
+, m_channelState    (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::parse(const mixer::Event& e) const
+{
+    if (e.type == mixer::EventType::CHANNEL_PITCH)
+        state->pitch.store(e.action.event.getVelocityFloat());
+
+    if (hasWave())
+        m_sampleController.parse(e);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::advance(Frame bufferSize) const
+{
+    m_sampleController.advance(bufferSize);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::render(AudioBuffer& out) const
+{
+    assert(m_channelState != nullptr);
+
+    if (m_waveReader.wave == nullptr || !m_channelState->isPlaying())
+        return;
+
+    /* Advance SampleController: this is needed for quantization. */
+
+    Frame begin   = state->begin.load();
+    Frame end     = state->end.load();
+    Frame tracker = state->tracker.load();
+    float pitch   = state->pitch.load();
+    Frame used    = 0;
+
+    /* Audio data is temporarily stored to the working audio buffer. */
+
+    AudioBuffer& buffer = m_channelState->buffer;
+
+    /* Adjust tracker in case someone has changed the begin/end points in the
+    meantime. */
+    
+    if (tracker < begin || tracker >= end)
+        tracker = begin;
+
+    /* If rewinding, fill the tail first, then reset the tracker to the begin
+    point. The rest is performed as usual. */
+
+    if (state->rewinding) {
+               if (tracker < end)
+            m_waveReader.fill(buffer, tracker, 0, pitch);
+        state->rewinding = false;
+               tracker = begin;
+    }
+
+    used     = m_waveReader.fill(buffer, tracker, state->offset, pitch);
+    tracker += used;
+
+//G_DEBUG ("block=[" << tracker - used << ", " << tracker << ")" << 
+//         ", used=" << used << ", range=[" << begin << ", " << end << ")" <<
+//         ", offset=" << state->offset << ", globalFrame=" << clock::getCurrentFrame());
+
+    if (tracker >= end) {
+//G_DEBUG ("last frame tracker=" << tracker);
+        tracker = begin;
+        m_sampleController.onLastFrame();
+        if (shouldLoop()) {
+            Frame offset = std::min(static_cast<Frame>(used / pitch), buffer.countFrames() - 1);
+            tracker += m_waveReader.fill(buffer, tracker, offset, pitch);
+        }
+    }
+
+    state->offset = 0;
+    state->tracker.store(tracker);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::loadWave(const Wave* w)
+{
+    m_waveReader.wave = w;
+
+    state->tracker.store(0);
+    state->shift.store(0);
+    state->begin.store(0);
+
+    if (w != nullptr) {
+        m_waveId = w->id;
+        m_channelState->playStatus.store(ChannelStatus::OFF);
+        m_channelState->name = w->getBasename(/*ext=*/false);
+        state->end.store(w->getSize() - 1);
+    }
+    else {
+        m_waveId = 0;
+        m_channelState->playStatus.store(ChannelStatus::EMPTY);
+        m_channelState->name = "";
+        state->end.store(0);
+    }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::setWave(const Wave& w)
+{
+    m_waveReader.wave = &w;
+    m_waveId = w.id;
+}
+
+
+void SamplePlayer::setInvalidWave()
+{
+    m_waveReader.wave = nullptr;
+    m_waveId = 0;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::kickIn(Frame f)
+{
+    assert(hasWave());
+    
+       state->tracker.store(f);
+       m_channelState->playStatus.store(ChannelStatus::PLAY);    
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayer::shouldLoop() const
+{
+    ChannelStatus    playStatus = m_channelState->playStatus.load();
+    SamplePlayerMode mode       = state->mode.load();
+    
+    return (mode == SamplePlayerMode::LOOP_BASIC  || 
+            mode == SamplePlayerMode::LOOP_REPEAT || 
+            mode == SamplePlayerMode::SINGLE_ENDLESS) && playStatus == ChannelStatus::PLAY;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayer::hasWave() const        { return m_waveReader.wave != nullptr; }
+bool SamplePlayer::hasLogicalWave() const { return hasWave() && m_waveReader.wave->isLogical(); }
+bool SamplePlayer::hasEditedWave() const  { return hasWave() && m_waveReader.wave->isEdited(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ID SamplePlayer::getWaveId() const
+{
+    return m_waveId;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame SamplePlayer::getWaveSize() const
+{
+    return hasWave() ? m_waveReader.wave->getSize() : 0;
+}
+}} // giada::m::
diff --git a/src/core/channels/samplePlayer.h b/src/core/channels/samplePlayer.h
new file mode 100644 (file)
index 0000000..d22af29
--- /dev/null
@@ -0,0 +1,115 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_SAMPLE_PLAYER_H
+#define G_CHANNEL_SAMPLE_PLAYER_H
+
+
+#include "core/types.h"
+#include "core/const.h"
+#include "core/mixer.h" // TODO - forward declare
+#include "core/audioBuffer.h" // TODO - forward declare
+#include "core/channels/waveReader.h"
+#include "core/channels/sampleController.h"
+
+
+namespace giada {
+namespace m
+{
+class  Wave;
+struct SamplePlayerState;
+class SamplePlayer
+{
+public:
+
+    SamplePlayer(ChannelState*);
+    SamplePlayer(const patch::Channel& p, ChannelState*);
+    SamplePlayer(const SamplePlayer&, ChannelState* c=nullptr);
+
+    void parse(const mixer::Event& e) const;
+    void advance(Frame bufferSize) const;
+    void render(AudioBuffer& out) const;
+
+    bool hasWave() const;
+    bool hasLogicalWave() const;
+    bool hasEditedWave() const;
+    ID getWaveId() const;
+    Frame getWaveSize() const;
+
+    /* loadWave
+    Loads Wave 'w' into this channel and sets it up (name, markers, ...). */
+
+    void loadWave(const Wave* w);
+    
+    /* setWave
+    Just sets the pointer to a Wave object. Used during de-serialization. */
+
+    void setWave(const Wave& w);
+
+    /* setInvalidWave
+    Same as setWave(nullptr) plus the invalid ID (i.e. 0). */
+    
+    void setInvalidWave(); 
+
+    /* kickIn
+    Starts the player right away at frame 'f'. Used when launching a loop after
+    being live recorded. */
+    
+    void kickIn(Frame f);
+
+
+    /* state
+    Pointer to mutable SamplePlayerState state. */
+
+    std::unique_ptr<SamplePlayerState> state;
+
+private:
+
+    bool shouldLoop() const;
+
+    ID m_waveId;
+
+    /* m_waveReader
+    Used to read data from Wave and fill incoming buffer. */
+
+    WaveReader m_waveReader;
+
+    /* m_sampleController
+    Managers events for this Sample Player. */
+
+    SampleController m_sampleController;
+
+    /* m_channelState
+    Pointer to Channel state. Needed to alter the playStatus status when the
+    sample is over. */
+
+    ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/state.cpp b/src/core/channels/state.cpp
new file mode 100644 (file)
index 0000000..18f4aa7
--- /dev/null
@@ -0,0 +1,325 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/patch.h"
+#include "state.h"
+
+
+namespace giada {
+namespace m 
+{
+MidiLearnerState::MidiLearnerState()
+: enabled      (true)
+, filter       (0)
+, keyPress     (0x0)
+, keyRelease   (0x0)
+, kill         (0x0)
+, arm          (0x0)
+, volume       (0x0)
+, mute         (0x0)
+, solo         (0x0)
+, readActions  (0x0)
+, pitch        (0x0)
+{
+}
+
+
+MidiLearnerState::MidiLearnerState(const patch::Channel& p)
+: enabled      (p.midiIn)
+, filter       (p.midiInFilter)
+, keyPress     (p.midiInKeyPress)
+, keyRelease   (p.midiInKeyRel)
+, kill         (p.midiInKill)
+, arm          (p.midiInArm)
+, volume       (p.midiInVolume)
+, mute         (p.midiInMute)
+, solo         (p.midiInSolo)
+, readActions  (p.midiInReadActions)
+, pitch        (p.midiInPitch)
+{
+}
+
+
+MidiLearnerState::MidiLearnerState(const MidiLearnerState& o)
+: enabled      (o.enabled.load())
+, filter       (o.filter.load())
+, keyPress     (o.keyPress.load())
+, keyRelease   (o.keyRelease.load())
+, kill         (o.kill.load())
+, arm          (o.arm.load())
+, volume       (o.volume.load())
+, mute         (o.mute.load())
+, solo         (o.solo.load())
+, readActions  (o.readActions.load())
+, pitch        (o.pitch.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool MidiLearnerState::isAllowed(int c) const
+{
+    int filter_   = filter.load();
+    bool enabled_ = enabled.load();
+
+       return enabled_ && (filter_ == -1 || filter_ == c);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighterState::MidiLighterState()
+: enabled(false)
+, playing(0x0)
+, mute   (0x0)
+, solo   (0x0)
+{
+}
+
+
+MidiLighterState::MidiLighterState(const patch::Channel& p)
+: enabled(p.midiOutL)
+, playing(p.midiOutLplaying)
+, mute   (p.midiOutLmute)
+, solo   (p.midiOutLsolo)
+{
+}
+
+
+MidiLighterState::MidiLighterState(const MidiLighterState& o)
+: enabled(o.enabled.load())
+, playing(o.playing.load())
+, mute   (o.mute.load())
+, solo   (o.solo.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+MidiSenderState::MidiSenderState()
+: enabled(true)
+, filter (0)
+{
+}
+
+
+MidiSenderState::MidiSenderState(const patch::Channel& p)
+: enabled(p.midiOut)
+, filter (p.midiOutChan)
+{
+}
+
+
+MidiSenderState::MidiSenderState(const MidiSenderState& o)
+: enabled(o.enabled.load())
+, filter (o.filter.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+MidiReceiverState::MidiReceiverState()
+{
+    midiBuffer.ensureSize(G_DEFAULT_VST_MIDIBUFFER_SIZE);
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayerState::SamplePlayerState()
+: tracker      (0)
+, pitch        (G_DEFAULT_PITCH)
+, mode         (SamplePlayerMode::SINGLE_BASIC)
+, velocityAsVol(false)
+, rewinding    (false)
+, quantizing   (false)
+, offset       (0)
+{
+}
+
+
+SamplePlayerState::SamplePlayerState(const SamplePlayerState& o)
+: tracker      (o.tracker.load())
+, pitch        (o.pitch.load())
+, mode         (o.mode.load())
+, shift        (o.shift.load())
+, begin        (o.begin.load())
+, end          (o.end.load())
+, velocityAsVol(o.velocityAsVol.load())
+, rewinding    (o.rewinding)
+, quantizing   (o.quantizing)
+, offset       (o.offset)
+, quantizer    (o.quantizer)
+{
+}
+
+
+SamplePlayerState::SamplePlayerState(const patch::Channel& p)
+: tracker      (0)
+, pitch        (p.pitch)
+, mode         (p.mode)
+, shift        (p.shift)
+, begin        (p.begin)
+, end          (p.end)
+, velocityAsVol(p.midiInVeloAsVol)
+, rewinding    (false)
+, quantizing   (false)
+, offset       (0)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayerState::isAnyLoopMode() const
+{
+    SamplePlayerMode m = mode.load();
+
+       return m == SamplePlayerMode::LOOP_BASIC  || 
+              m == SamplePlayerMode::LOOP_ONCE   || 
+              m == SamplePlayerMode::LOOP_REPEAT || 
+              m == SamplePlayerMode::LOOP_ONCE_BAR;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiverState::AudioReceiverState()
+: inputMonitor(false)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const patch::Channel& p)
+: inputMonitor(p.inputMonitor)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const AudioReceiverState& o)
+: inputMonitor(o.inputMonitor.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+ChannelState::ChannelState(ID id, Frame bufferSize)
+: id         (id)
+, playStatus (ChannelStatus::OFF)
+, recStatus  (ChannelStatus::OFF)
+, volume     (G_DEFAULT_VOL)
+, pan        (G_DEFAULT_PAN)
+, mute       (false)
+, solo       (false)
+, armed      (false)
+, key        (0)
+, readActions(true)
+, buffer     (bufferSize, G_MAX_IO_CHANS)
+, hasActions (false)
+, height     (G_GUI_UNIT)
+, volume_i   (1.0f)
+{
+}
+    
+
+ChannelState::ChannelState(const ChannelState& o)
+: id         (o.id)
+, playStatus (o.playStatus.load())
+, recStatus  (o.recStatus.load())
+, volume     (o.volume.load())
+, pan        (o.pan.load())
+, mute       (o.mute.load())
+, solo       (o.solo.load())
+, armed      (o.armed.load())
+, key        (o.key.load())
+, readActions(o.readActions.load())
+, buffer     (o.buffer)
+, hasActions (o.hasActions)
+, name       (o.name)
+, height     (o.height)
+, volume_i   (o.volume_i)
+{
+}
+
+
+ChannelState::ChannelState(const patch::Channel& p, Frame bufferSize)
+: id         (p.id)
+, playStatus (ChannelStatus::OFF)
+, recStatus  (ChannelStatus::OFF)
+, volume     (p.volume)
+, pan        (p.pan)
+, mute       (p.mute)
+, solo       (p.solo)
+, armed      (p.armed)
+, key        (p.key)
+, readActions(p.readActions)
+, buffer     (bufferSize, G_MAX_IO_CHANS)
+, hasActions (p.hasActions)
+, name       (p.name)
+, height     (p.height)
+, volume_i   (1.0f)
+{
+}
+    
+
+/* -------------------------------------------------------------------------- */
+
+
+bool ChannelState::isPlaying() const
+{
+    ChannelStatus s = playStatus.load();
+       return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
+}
+}} // giada::m::
diff --git a/src/core/channels/state.h b/src/core/channels/state.h
new file mode 100644 (file)
index 0000000..e7e8576
--- /dev/null
@@ -0,0 +1,231 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_STATE_H
+#define G_CHANNEL_STATE_H
+
+
+#include <string>
+#include <atomic>
+#include "core/const.h"
+#include "core/types.h"
+#include "core/quantizer.h"
+#include "core/audioBuffer.h"
+#ifdef WITH_VST
+#include "deps/juce-config.h"
+#endif
+
+
+namespace giada {
+namespace m {
+namespace patch
+{
+struct Channel;
+}
+struct MidiLearnerState
+{
+    MidiLearnerState();
+    MidiLearnerState(const patch::Channel& p);
+    MidiLearnerState(const MidiLearnerState& o);
+
+    /* isAllowed
+    Tells whether the current MIDI channel 'channel' is enabled to receive MIDI
+    data. */
+
+    bool isAllowed(int channel) const;
+
+    /* enabled
+    Tells whether MIDI learning is enabled for the current channel. */
+    
+       std::atomic<bool> enabled;
+
+    /* filter
+    Which MIDI channel should be filtered out when receiving MIDI messages. 
+    If -1 means 'all'. */
+    
+    std::atomic<int> filter;
+
+    /* MIDI learning fields. */
+
+       std::atomic<uint32_t> keyPress;
+       std::atomic<uint32_t> keyRelease;
+       std::atomic<uint32_t> kill;
+       std::atomic<uint32_t> arm;
+       std::atomic<uint32_t> volume;
+       std::atomic<uint32_t> mute;
+       std::atomic<uint32_t> solo;
+       std::atomic<uint32_t> readActions; // Sample Channels only
+       std::atomic<uint32_t> pitch;       // Sample Channels only
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct MidiLighterState
+{
+    MidiLighterState();
+    MidiLighterState(const patch::Channel& p);
+    MidiLighterState(const MidiLighterState& o);
+
+    /* enabled
+    Tells whether MIDI ligthing is enabled or not. */
+    
+       std::atomic<bool> enabled;
+
+    /* MIDI learning fields for MIDI ligthing. */
+
+       std::atomic<uint32_t> playing;
+       std::atomic<uint32_t> mute;
+       std::atomic<uint32_t> solo;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct MidiSenderState
+{
+    MidiSenderState();
+    MidiSenderState(const patch::Channel& p);
+    MidiSenderState(const MidiSenderState& o);
+
+    /* enabled
+    Tells whether MIDI output is enabled or not. */
+    
+       std::atomic<bool> enabled;
+
+    /* filter
+    Which MIDI channel data should be sent to. */
+    
+    std::atomic<int> filter;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+struct MidiReceiverState
+{
+    MidiReceiverState();
+
+       /* midiBuffer 
+       Contains MIDI events to be sent to plug-ins. */
+
+       juce::MidiBuffer midiBuffer;
+};
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct SamplePlayerState
+{
+    SamplePlayerState();
+    SamplePlayerState(const patch::Channel& p);
+    SamplePlayerState(const SamplePlayerState& o);
+
+    bool isAnyLoopMode() const;
+
+    std::atomic<Frame>            tracker;
+    std::atomic<float>            pitch;
+    std::atomic<SamplePlayerMode> mode;
+    std::atomic<Frame>            shift;
+    std::atomic<Frame>            begin;
+    std::atomic<Frame>            end;
+
+    /* velocityAsVol
+    Velocity drives volume. */
+
+       std::atomic<bool> velocityAsVol;
+
+    bool      rewinding;
+    bool      quantizing;
+    Frame     offset;
+    Quantizer quantizer;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct AudioReceiverState
+{
+    AudioReceiverState();
+    AudioReceiverState(const patch::Channel& p);
+    AudioReceiverState(const AudioReceiverState& o);
+
+    std::atomic<bool> inputMonitor;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct ChannelState
+{
+    ChannelState(ID id, Frame bufferSize);
+    ChannelState(const patch::Channel& p, Frame bufferSize);
+    ChannelState(const ChannelState& o);
+
+    bool isPlaying() const;
+
+    ID id;
+
+    std::atomic<ChannelStatus> playStatus;
+       std::atomic<ChannelStatus> recStatus;
+    std::atomic<float>         volume;
+    std::atomic<float>         pan;
+    std::atomic<bool>          mute;
+    std::atomic<bool>          solo;
+    std::atomic<bool>          armed;
+    std::atomic<int>           key;
+       std::atomic<bool>          readActions;
+       
+       /* buffer (internal)
+       Working buffer for internal processing. */
+
+    AudioBuffer buffer;
+
+    bool        hasActions;
+    std::string name;
+    Pixel       height;
+
+       /* volume_i (internal)
+       Internal volume used for volume automation and velocity-drives-volume mode
+    on Sample Channels. */
+
+    float volume_i;
+};
+}} // giada::m::
+
+
+#endif
diff --git a/src/core/channels/waveReader.cpp b/src/core/channels/waveReader.cpp
new file mode 100644 (file)
index 0000000..b96c71d
--- /dev/null
@@ -0,0 +1,176 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <memory>
+#include <cassert>
+#include <algorithm>
+#include "core/const.h"
+#include "core/model/model.h"
+#include "core/audioBuffer.h"
+#include "core/wave.h"
+#include "utils/log.h"
+#include "waveReader.h"
+
+
+namespace giada {
+namespace m 
+{
+WaveReader::WaveReader()
+: wave      (nullptr),
+  m_srcState(nullptr)
+{
+       allocateSrc();
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::WaveReader(const WaveReader& o)
+: wave      (o.wave),
+  m_srcState(nullptr)
+{
+       allocateSrc();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::WaveReader(WaveReader&& o)
+: wave      (o.wave),
+  m_srcState(nullptr)
+{
+       moveSrc(&o.m_srcState);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader& WaveReader::operator=(const WaveReader& o)
+{
+       if (this == &o) return *this;
+       wave = o.wave;
+       allocateSrc();
+       return *this;
+}
+
+
+WaveReader& WaveReader::operator=(WaveReader&& o)
+{
+       if (this == &o) return *this;
+       wave = o.wave;
+       moveSrc(&o.m_srcState);
+       return *this;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::~WaveReader()
+{
+       if (m_srcState != nullptr)
+               src_delete(m_srcState);    
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fill(AudioBuffer& out, Frame start, Frame offset, float pitch) const
+{
+       assert(wave != nullptr);
+       assert(start >= 0);
+       assert(offset < out.countFrames());
+
+       model::WavesLock l(model::waves); // TODO dependency
+       
+       if (pitch == 1.0) return fillCopy(out, start, offset);
+       else              return fillResampled(out, start, offset, pitch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fillResampled(AudioBuffer& dest, Frame start, Frame offset, float pitch) const
+{
+    SRC_DATA srcData;
+       
+       srcData.data_in       = wave->getFrame(start);        // Source data
+       srcData.input_frames  = wave->getSize() - start;      // How many readable frames
+       srcData.data_out      = dest[offset];                 // Destination (processed data)
+       srcData.output_frames = dest.countFrames() - offset;  // How many frames to process
+       srcData.end_of_input  = false;
+       srcData.src_ratio     = 1 / pitch;
+
+       src_process(m_srcState, &srcData);
+
+       return srcData.input_frames_used;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame offset) const
+{
+       Frame used = dest.countFrames() - offset;
+       if (used > wave->getSize() - start)
+               used = wave->getSize() - start;
+
+       dest.copyData(wave->getFrame(start), used, offset);
+
+       return used;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void WaveReader::allocateSrc()
+{
+       m_srcState = src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr);
+       if (m_srcState == nullptr) {
+               u::log::print("[WaveReader] unable to allocate memory for SRC_STATE!\n");
+               throw std::bad_alloc();
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void WaveReader::moveSrc(SRC_STATE** other)
+{
+       if (m_srcState != nullptr)
+               src_delete(m_srcState);
+       m_srcState = *other;
+       *other = nullptr;
+}
+}} // giada::m::
diff --git a/src/core/channels/waveReader.h b/src/core/channels/waveReader.h
new file mode 100644 (file)
index 0000000..1d6bc44
--- /dev/null
@@ -0,0 +1,74 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_CHANNEL_WAVE_READER_H
+#define G_CHANNEL_WAVE_READER_H
+
+
+#include <samplerate.h>
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+class Wave;
+class WaveReader final
+{
+public:
+
+    WaveReader();
+    WaveReader(const WaveReader&);
+    WaveReader(WaveReader&&);
+    WaveReader& operator=(const WaveReader&);
+    WaveReader& operator=(WaveReader&&);
+    ~WaveReader();
+
+    Frame fill(AudioBuffer& out, Frame start, Frame offset, float pitch) const;
+
+       /* wave
+       Wave object. Might be null if the channel has no sample. */
+
+       const Wave* wave;
+
+private:
+
+       Frame fillResampled(AudioBuffer& out, Frame start, Frame offset, float pitch) const;
+       Frame fillCopy     (AudioBuffer& out, Frame start, Frame offset) const;
+
+       void allocateSrc();
+       void moveSrc(SRC_STATE** o);
+
+       /* srcState
+       Struct from libsamplerate. */
+
+       SRC_STATE* m_srcState;
+};
+}} // giada::m::
+
+
+#endif
index 5a84d3add19abefc0b70ff939a3ff706a5b82732..3b6b9798906a2aefc09b17c5a7480f381ad4cd5b 100644 (file)
 #include <atomic>
 #include <cassert>
 #include "glue/main.h"
-#include "utils/math.h"
+#include "glue/events.h"
 #include "core/model/model.h"
 #include "core/conf.h"
+#include "core/sequencer.h"
 #include "core/const.h"
 #include "core/kernelAudio.h"
 #include "core/mixerHandler.h"
 #include "core/kernelMidi.h"
+#include "utils/math.h"
 #include "clock.h"
 
 
@@ -48,7 +50,13 @@ std::atomic<int> currentFrameWait_(0);
 std::atomic<int> currentFrame_(0);
 std::atomic<int> currentBeat_(0);
 
-int quanto_ = 1;             // Quantizer step
+/* 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;
@@ -56,7 +64,6 @@ int midiTCseconds_ = 0;
 int midiTCminutes_ = 0;
 int midiTChours_   = 0;
 
-
 #if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
 kernelAudio::JackState jackStatePrev_;
 #endif
@@ -75,7 +82,7 @@ void recomputeFrames_(model::Clock& c)
        c.framesInSeq  = c.framesInBeat * G_MAX_BEATS;
 
        if (c.quantize != 0)
-               quanto_ = c.framesInBeat / c.quantize;
+               quantizerStep_ = c.framesInBeat / c.quantize;
 }
 }; // {anonymous}
 
@@ -131,7 +138,7 @@ bool isActive()
 
 bool quantoHasPassed()
 {
-       return currentFrame_.load() % quanto_ == 0;
+       return clock::getQuantizerValue() != 0 && currentFrame_.load() % quantizerStep_ == 0;
 }
 
 
@@ -172,16 +179,7 @@ bool isOnFirstBeat()
 
 void setBpm(float b)
 {      
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-       
-       /* Can't change bpm from within Giada when using JACK. */
-
-       if (m::kernelAudio::getAPI() == G_SYS_API_JACK)
-               return;
-
-#endif
-
-       b = u::math::bound(b, G_MIN_BPM, G_MAX_BPM);
+       b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
 
        model::onSwap(model::clock, [&](model::Clock& c)
        {
@@ -193,8 +191,8 @@ void setBpm(float b)
 
 void setBeats(int newBeats, int newBars)
 {
-       newBeats = u::math::bound(newBeats, 1, G_MAX_BEATS);
-       newBars  = u::math::bound(newBars, 1, newBeats); // Bars cannot be greater than beats
+       newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
+       newBars  = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
 
        model::onSwap(model::clock, [&](model::Clock& c)
        {
@@ -380,6 +378,9 @@ void sendMIDIrewind()
                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);
 }
 
 
@@ -393,26 +394,27 @@ void recvJackSync()
        /* TODO - these things should be processed by a higher level, 
        above clock:: ----> clockManager */
 
-       kernelAudio::JackState jackState = kernelAudio::jackTransportQuery();
+       kernelAudio::JackState jackStateCurr = kernelAudio::jackTransportQuery();
+
+       if (jackStateCurr != jackStatePrev_) {  
 
-       if (jackState.running != jackStatePrev_.running) {
-               if (jackState.running) {
-                       if (!isRunning())
-                               mh::startSequencer();
+               if (jackStateCurr.frame != jackStatePrev_.frame && jackStateCurr.frame == 0) {
+G_DEBUG("JackState received - rewind to frame 0");
+                       sequencer::rewind();
                }
-               else {
-                       if (isRunning())
-                               mh::stopSequencer();
+
+               if (jackStateCurr.bpm != jackStatePrev_.bpm && jackStateCurr.bpm > 1.0f) {  // 0 bpm if Jack does not send that info
+G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
+                       c::main::setBpm(jackStateCurr.bpm);
                }
-       }
-       if (jackState.bpm != jackStatePrev_.bpm)
-               if (jackState.bpm > 1.0f)  // 0 bpm if Jack does not send that info
-                       c::main::setBpm(jackState.bpm);
 
-       if (jackState.frame == 0 && jackState.frame != jackStatePrev_.frame)
-               mh::rewindSequencer();
+               if (jackStateCurr.running != jackStatePrev_.running) {
+G_DEBUG("JackState received - running=" << jackStateCurr.running);                     
+                       jackStateCurr.running ? sequencer::start() : sequencer::stop();
+               }
+       }
 
-       jackStatePrev_ = jackState;
+       jackStatePrev_ = jackStateCurr;
 }
 
 #endif
@@ -426,7 +428,6 @@ bool canQuantize()
        model::ClockLock lock(model::clock);
        
        const model::Clock* c = model::clock.get();
-       
        return c->quantize > 0 && c->status == ClockStatus::RUNNING;
 }
 
@@ -434,16 +435,26 @@ bool canQuantize()
 /* -------------------------------------------------------------------------- */
 
 
-int         getCurrentFrame() { return currentFrame_.load(); }
-int         getCurrentBeat()  { return currentBeat_.load(); }
-int         getQuanto()       { return quanto_; }
-ClockStatus getStatus()       { model::ClockLock lock(model::clock); return model::clock.get()->status; }
-int         getFramesInLoop() { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; }
-int         getFramesInBar()  { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; }
-int         getFramesInBeat() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; }
-int         getFramesInSeq()  { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; }
-int         getQuantize()     { model::ClockLock lock(model::clock); return model::clock.get()->quantize; }
-float       getBpm()          { model::ClockLock lock(model::clock); return model::clock.get()->bpm; }
-int         getBeats()        { model::ClockLock lock(model::clock); return model::clock.get()->beats; }
-int         getBars()         { model::ClockLock lock(model::clock); return model::clock.get()->bars; }
+Frame quantize(Frame f)
+{
+       if (!canQuantize()) return f;
+       return u::math::quantize(f, quantizerStep_) % getFramesInLoop(); // No overflow
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int         getCurrentFrame()   { return currentFrame_.load(); }
+int         getCurrentBeat()    { return currentBeat_.load(); }
+int         getQuantizerStep()  { return quantizerStep_; }
+ClockStatus getStatus()         { model::ClockLock lock(model::clock); return model::clock.get()->status; }
+int         getFramesInLoop()   { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; }
+int         getFramesInBar()    { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; }
+int         getFramesInBeat()   { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; }
+int         getFramesInSeq()    { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; }
+int         getQuantizerValue() { model::ClockLock lock(model::clock); return model::clock.get()->quantize; }
+float       getBpm()            { model::ClockLock lock(model::clock); return model::clock.get()->bpm; }
+int         getBeats()          { model::ClockLock lock(model::clock); return model::clock.get()->beats; }
+int         getBars()           { model::ClockLock lock(model::clock); return model::clock.get()->bars; }
 }}}; // giada::m::clock::
index 670e4ce6b1c7ac960fc131217150eea705d9b20a..40aaaf0712fe71ba41d9a232d2a9e362fa79fd7d 100644 (file)
@@ -53,7 +53,7 @@ Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */
 
 void sendMIDIrewind();
 
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) || defined(G_OS_MAC)
 void recvJackSync();
 #endif
 
@@ -66,25 +66,30 @@ int getFramesInBar();
 int getFramesInBeat();
 int getFramesInLoop();
 int getFramesInSeq();
-int getQuantize();
-int getQuanto();
+int getQuantizerValue();
+int getQuantizerStep();
 ClockStatus getStatus();
 
 /* incrCurrentFrame
-Increases current frame of a single step (+1). */
+Increases current frame by a single step (+1). */
 
 void incrCurrentFrame();
 
 /* quantoHasPassed
-Tells whether a quanto unit has passed yet. */
+Tells whether a quantizer unit has passed yet. */
 
 bool quantoHasPassed();
 
-/* quantoHasPassed
-Whether the quantizer value is > 0 and the clock is running. */
+/* 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);
index 7d8b000f1d31a77fb7d63d31841d13d8299e4139..f68be90c5dde91ba3008c0323f75f3c52ae7eeee 100644 (file)
@@ -147,7 +147,6 @@ bool read()
        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.recsStopOnChanHalt    =  j.value(CONF_KEY_RECS_STOP_ON_CHAN_HALT, conf.recsStopOnChanHalt);
        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);
@@ -253,7 +252,6 @@ bool write()
        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_RECS_STOP_ON_CHAN_HALT]    = conf.recsStopOnChanHalt;
        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;
index c750c7b10c4c70dd6ba2a1652e8489c90a83c714..d7dc210188156ee5f40353924426ac0746c52d8e 100644 (file)
@@ -60,7 +60,6 @@ struct Conf
        int         midiSync    = MIDI_SYNC_NONE;
        float       midiTCfps   = 25.0f;
 
-       bool recsStopOnChanHalt    = false;
        bool chansStopOnSeqHalt    = false;
        bool treatRecsAsLoops      = false;
        bool inputMonitorDefaultOn = false;
index 8547fbe5809d9dbf2e6e22921b32746884b9a26a..21999d62c48858afd603c35ac309a8019131aacb 100644 (file)
 #define G_CONST_H
 
 
+#include <cstdint>
+
+
+/* -- debug ----------------------------------------------------------------- */
+#ifndef NDEBUG
+    #define G_DEBUG_MODE
+    #define G_DEBUG(x) std::cerr << __FILE__ << "::" << __func__  << "() - " << x << "\n";
+#else
+    #define G_DEBUG(x) do {} while (0)
+#endif
+
+
 /* -- environment ----------------------------------------------------------- */
 #if defined(_WIN32)
        #define G_OS_WINDOWS
 
 /* -- version --------------------------------------------------------------- */
 constexpr auto G_APP_NAME      = "Giada";
-constexpr auto G_VERSION_STR   = "0.16.2";
+constexpr auto G_VERSION_STR   = "0.16.3";
 constexpr int  G_VERSION_MAJOR = 0;
 constexpr int  G_VERSION_MINOR = 16;
-constexpr int  G_VERSION_PATCH = 2;
+constexpr int  G_VERSION_PATCH = 3;
 
 constexpr auto CONF_FILENAME = "giada.conf";
 
@@ -65,8 +77,8 @@ constexpr auto CONF_FILENAME = "giada.conf";
 
 
 /* -- GUI ------------------------------------------------------------------- */
-constexpr float G_GUI_REFRESH_RATE   = 0.05;
-constexpr float G_GUI_PLUGIN_RATE    = 0.05;   // refresh rate for plugin GUI
+constexpr float G_GUI_REFRESH_RATE   = 1 / 30.0; // 30 fps
+constexpr float G_GUI_PLUGIN_RATE    = 1 / 30.0; // 30 fps
 constexpr int   G_GUI_FONT_SIZE_BASE = 12;
 constexpr int   G_GUI_INNER_MARGIN   = 4;
 constexpr int   G_GUI_OUTER_MARGIN   = 8;
@@ -88,28 +100,31 @@ constexpr int   G_GUI_ZOOM_FACTOR    = 2;
 
 
 /* -- MIN/MAX values -------------------------------------------------------- */
-constexpr float  G_MIN_BPM          = 20.0f;
-constexpr auto   G_MIN_BPM_STR      = "20.0";
-constexpr float  G_MAX_BPM          = 999.0f;
-constexpr auto   G_MAX_BPM_STR      = "999.0";
-constexpr int    G_MAX_BEATS        = 32;
-constexpr int    G_MAX_BARS         = 32;
-constexpr int    G_MAX_QUANTIZE     = 8;
-constexpr float  G_MIN_DB_SCALE     = 60.0f;
-constexpr int    G_MIN_COLUMN_WIDTH = 140;
-constexpr float  G_MAX_BOOST_DB     = 20.0f;
-constexpr float  G_MIN_PITCH        = 0.1f;
-constexpr float  G_MAX_PITCH        = 4.0f;
-constexpr float  G_MAX_VOLUME       = 1.0f;
-constexpr int    G_MAX_GRID_VAL     = 64;
-constexpr int    G_MIN_BUF_SIZE     = 8;
-constexpr int    G_MAX_BUF_SIZE     = 4096;
-constexpr int    G_MIN_GUI_WIDTH    = 816;
-constexpr int    G_MIN_GUI_HEIGHT   = 510;
-constexpr int    G_MAX_IO_CHANS     = 2;
-constexpr int    G_MAX_VELOCITY     = 0x7F;
-constexpr int    G_MAX_MIDI_CHANS   = 16;
-constexpr int    G_MAX_POLYPHONY    = 32;
+constexpr float  G_MIN_BPM            = 20.0f;
+constexpr auto   G_MIN_BPM_STR        = "20.0";
+constexpr float  G_MAX_BPM            = 999.0f;
+constexpr auto   G_MAX_BPM_STR        = "999.0";
+constexpr int    G_MAX_BEATS          = 32;
+constexpr int    G_MAX_BARS           = 32;
+constexpr int    G_MAX_QUANTIZE       = 8;
+constexpr float  G_MIN_DB_SCALE       = 60.0f;
+constexpr int    G_MIN_COLUMN_WIDTH   = 140;
+constexpr float  G_MAX_BOOST_DB       = 20.0f;
+constexpr float  G_MIN_PITCH          = 0.1f;
+constexpr float  G_MAX_PITCH          = 4.0f;
+constexpr float  G_MAX_PAN            = 1.0f;
+constexpr float  G_MAX_VOLUME         = 1.0f;
+constexpr int    G_MAX_GRID_VAL       = 64;
+constexpr int    G_MIN_BUF_SIZE       = 8;
+constexpr int    G_MAX_BUF_SIZE       = 4096;
+constexpr int    G_MIN_GUI_WIDTH      = 816;
+constexpr int    G_MIN_GUI_HEIGHT     = 510;
+constexpr int    G_MAX_IO_CHANS       = 2;
+constexpr int    G_MAX_VELOCITY       = 0x7F;
+constexpr int    G_MAX_MIDI_CHANS     = 16;
+constexpr int    G_MAX_POLYPHONY      = 32;
+constexpr int    G_MAX_QUEUE_EVENTS   = 32;
+constexpr int    G_MAX_QUANTIZER_SIZE = 8;
 
 
 
@@ -143,28 +158,30 @@ constexpr int G_MIDI_API_ALSA = 0x02;  // 0000 0010
        #define G_DEFAULT_SOUNDSYS      G_SYS_API_CORE
 #endif
 
-constexpr int   G_DEFAULT_SOUNDDEV_OUT      = 0;      // FIXME - please override with rtAudio::getDefaultDevice (or similar)
-constexpr int   G_DEFAULT_SOUNDDEV_IN       = -1;     // no recording by default: input disabled
-constexpr int   G_DEFAULT_MIDI_SYSTEM       = 0;
-constexpr int   G_DEFAULT_MIDI_PORT_IN      = -1;
-constexpr int   G_DEFAULT_MIDI_PORT_OUT     = -1;
-constexpr int   G_DEFAULT_SAMPLERATE        = 44100;
-constexpr int   G_DEFAULT_BUFSIZE           = 1024;
-constexpr int   G_DEFAULT_BIT_DEPTH         = 32;     // float
-constexpr float G_DEFAULT_VOL               = 1.0f;
-constexpr float G_DEFAULT_PITCH             = 1.0f;
-constexpr float G_DEFAULT_BPM               = 120.0f;
-constexpr int   G_DEFAULT_BEATS             = 4;
-constexpr int   G_DEFAULT_BARS              = 1;
-constexpr int   G_DEFAULT_QUANTIZE          = 0;      // quantizer off
-constexpr float G_DEFAULT_FADEOUT_STEP      = 0.01f;  // micro-fadeout speed
-constexpr int   G_DEFAULT_COLUMN_WIDTH      = 380;
-constexpr auto  G_DEFAULT_PATCH_NAME        = "(default patch)";
-constexpr int   G_DEFAULT_ACTION_SIZE       = 8192;  // frames
-constexpr int   G_DEFAULT_ZOOM_RATIO        = 128;
-constexpr float G_DEFAULT_REC_TRIGGER_LEVEL = -10.0f;
-constexpr int   G_DEFAULT_SUBWINDOW_W       = 640;
-constexpr int   G_DEFAULT_SUBWINDOW_H       = 480;
+constexpr int   G_DEFAULT_SOUNDDEV_OUT        = 0;      // FIXME - please override with rtAudio::getDefaultDevice (or similar)
+constexpr int   G_DEFAULT_SOUNDDEV_IN         = -1;     // no recording by default: input disabled
+constexpr int   G_DEFAULT_MIDI_SYSTEM         = 0;
+constexpr int   G_DEFAULT_MIDI_PORT_IN        = -1;
+constexpr int   G_DEFAULT_MIDI_PORT_OUT       = -1;
+constexpr int   G_DEFAULT_SAMPLERATE          = 44100;
+constexpr int   G_DEFAULT_BUFSIZE             = 1024;
+constexpr int   G_DEFAULT_BIT_DEPTH           = 32;     // float
+constexpr float G_DEFAULT_VOL                 = 1.0f;
+constexpr float G_DEFAULT_PAN                 = 0.5f;
+constexpr float G_DEFAULT_PITCH               = 1.0f;
+constexpr float G_DEFAULT_BPM                 = 120.0f;
+constexpr int   G_DEFAULT_BEATS               = 4;
+constexpr int   G_DEFAULT_BARS                = 1;
+constexpr int   G_DEFAULT_QUANTIZE            = 0;      // quantizer off
+constexpr float G_DEFAULT_FADEOUT_STEP        = 0.01f;  // micro-fadeout speed
+constexpr int   G_DEFAULT_COLUMN_WIDTH        = 380;
+constexpr auto  G_DEFAULT_PATCH_NAME          = "(default patch)";
+constexpr int   G_DEFAULT_ACTION_SIZE         = 8192;  // frames
+constexpr int   G_DEFAULT_ZOOM_RATIO          = 128;
+constexpr float G_DEFAULT_REC_TRIGGER_LEVEL   = -10.0f;
+constexpr int   G_DEFAULT_SUBWINDOW_W         = 640;
+constexpr int   G_DEFAULT_SUBWINDOW_H         = 480;
+constexpr int   G_DEFAULT_VST_MIDIBUFFER_SIZE = 1024;  // TODO - not 100% sure about this size
 
 
 
@@ -259,8 +276,8 @@ constexpr int G_MIDI_OUT_L_SOLO     = 5;
 Channel voices messages - controller (0xB0) is a special subset of this family:
 it drives knobs, volume, faders and such. */
 
-#define MIDI_CONTROLLER     0xB0 << 24
-#define MIDI_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16)
+constexpr uint32_t G_MIDI_CONTROLLER    = 0xB0 << 24;
+constexpr uint32_t G_MIDI_ALL_NOTES_OFF = (G_MIDI_CONTROLLER) | (0x7B << 16);
 
 /* system common / real-time messages. Single bytes */
 
@@ -273,15 +290,6 @@ it drives knobs, volume, faders and such. */
 #define MIDI_STOP           0xFC
 #define MIDI_EOX            0xF7  // end of sysex
 
-/* Channels */
-
-constexpr int G_MIDI_CHANS[G_MAX_MIDI_CHANS] = {
-       0x00 << 24,  0x01 << 24,  0x02 << 24,  0x03 << 24,
-       0x04 << 24,  0x05 << 24,  0x06 << 24,  0x07 << 24,
-       0x08 << 24,  0x09 << 24,  0x0A << 24,  0x0B << 24,
-       0x0C << 24,  0x0D << 24,  0x0E << 24,  0x0F << 24
-};
-
 /* midi sync constants */
 
 #define MIDI_SYNC_NONE      0x00
@@ -406,7 +414,6 @@ constexpr auto CONF_KEY_MIDI_IN_VOLUME_IN        = "midi_in_volume_in";
 constexpr auto CONF_KEY_MIDI_IN_VOLUME_OUT       = "midi_in_volume_out";
 constexpr auto CONF_KEY_MIDI_IN_BEAT_DOUBLE      = "midi_in_beat_doble";
 constexpr auto CONF_KEY_MIDI_IN_BEAT_HALF        = "midi_in_beat_half";
-constexpr auto CONF_KEY_RECS_STOP_ON_CHAN_HALT   = "recs_stop_on_chan_halt";
 constexpr auto CONF_KEY_CHANS_STOP_ON_SEQ_HALT   = "chans_stop_on_seq_halt";
 constexpr auto CONF_KEY_TREAT_RECS_AS_LOOPS      = "treat_recs_as_loops";
 constexpr auto CONF_KEY_INPUT_MONITOR_DEFAULT_ON = "input_monitor_default_on";
index d43a5612907bd8ca50dccaa6dd5ae00af60796ff..42a10fdc53dc409a4e7294e27a1b236d0e32ce5e 100644 (file)
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/warnings.h"
 #include "glue/main.h"
-#include "core/channels/channel.h"
+#include "core/model/storage.h"
 #include "core/channels/channelManager.h"
 #include "core/mixer.h"
 #include "core/wave.h"
 #include "core/const.h"
 #include "core/clock.h"
 #include "core/mixerHandler.h"
+#include "core/sequencer.h"
 #include "core/patch.h"
 #include "core/conf.h"
 #include "core/waveManager.h"
@@ -83,6 +84,8 @@ void initConf_()
        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");
@@ -100,6 +103,7 @@ void initAudio_()
        kernelAudio::openDevice();
        clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
        mh::init();
+       sequencer::init();
        recorder::init();
        recorderHandler::init();
 
@@ -260,6 +264,7 @@ void reset()
        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());
@@ -278,6 +283,8 @@ void shutdown()
 {
        shutdownGUI_();
 
+       model::store(conf::conf);
+
        if (!conf::write())
                u::log::print("[init] error while saving configuration file!\n");
        else
index 4df1bd4a1408b480100021a15d4cb1e7b2201d0b..0068c0c1f7521ea8128c795279803c8107c4b7bd 100644 (file)
@@ -32,6 +32,7 @@
 #include "glue/main.h"
 #include "core/model/model.h"
 #include "conf.h"
+#include "const.h"
 #include "mixer.h"
 #include "const.h"
 #include "kernelAudio.h"
@@ -49,11 +50,11 @@ bool     inputEnabled = false;
 unsigned realBufsize  = 0;     // Real buffer size from the soundcard
 int      api          = 0;
 
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
 
 JackState jackState;
 
-jack_client_t* jackGetHandle()
+jack_client_t* jackGetHandle_()
 {
        return static_cast<jack_client_t*>(rtSystem->HACK__getJackClient());
 }
@@ -67,10 +68,22 @@ jack_client_t* jackGetHandle()
 /* -------------------------------------------------------------------------- */
 
 
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+
+bool JackState::operator!=(const JackState& o) const
+{
+       return !(running == o.running && bpm == o.bpm && frame == o.frame);
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
 bool isReady()
 {
        model::KernelLock lock(model::kernel);
-
        return model::kernel.get()->audioReady;
 }
 
@@ -165,7 +178,7 @@ int openDevice()
 
        realBufsize = conf::conf.buffersize;
 
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
 
        if (api == G_SYS_API_JACK) {
                conf::conf.samplerate = getFreq(conf::conf.soundDeviceOut, 0);
@@ -185,10 +198,9 @@ int openDevice()
                        nullptr,                                          // user data (unused)
                        &options);
                
-               std::unique_ptr<model::Kernel> k = model::kernel.clone();
-               k->audioReady = true;
-               model::kernel.swap(std::move(k));
-               
+               model::onSwap(model::kernel, [](model::Kernel& k) {
+                       k.audioReady = true;
+               });
                return 1;
        }
        catch (RtAudioError &e) {
@@ -432,19 +444,21 @@ int getAPI() { return api; }
 /* -------------------------------------------------------------------------- */
 
 
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
 
-
-const JackState &jackTransportQuery()
+JackState jackTransportQuery()
 {
        if (api != G_SYS_API_JACK)
                return jackState;
-       jack_position_t position;
-       jack_transport_state_t ts = jack_transport_query(jackGetHandle(), &position);
-       jackState.running = ts != JackTransportStopped;
-       jackState.bpm     = position.beats_per_minute;
-       jackState.frame   = position.frame;
-       return jackState;
+
+       jack_position_t        position;
+       jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position);
+
+       return {
+               ts != JackTransportStopped,
+               position.beats_per_minute,
+               position.frame
+       };
 }
 
 
@@ -454,7 +468,7 @@ const JackState &jackTransportQuery()
 void jackStart()
 {
        if (api == G_SYS_API_JACK)
-               jack_transport_start(jackGetHandle());
+               jack_transport_start(jackGetHandle_());
 }
 
 
@@ -466,9 +480,9 @@ void jackSetPosition(uint32_t frame)
        if (api != G_SYS_API_JACK)
        return;
        jack_position_t position;
-       jack_transport_query(jackGetHandle(), &position);
+       jack_transport_query(jackGetHandle_(), &position);
        position.frame = frame;
-       jack_transport_reposition(jackGetHandle(), &position);
+       jack_transport_reposition(jackGetHandle_(), &position);
 }
 
 
@@ -480,13 +494,13 @@ void jackSetBpm(double bpm)
        if (api != G_SYS_API_JACK)
                return;
        jack_position_t position;
-       jack_transport_query(jackGetHandle(), &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);
+       jack_transport_reposition(jackGetHandle_(), &position);
 }
 
 
@@ -496,7 +510,7 @@ void jackSetBpm(double bpm)
 void jackStop()
 {
        if (api == G_SYS_API_JACK)
-               jack_transport_stop(jackGetHandle());
+               jack_transport_stop(jackGetHandle_());
 }
 
 #endif  // defined(__linux__) || defined(__FreeBSD__)
index 942a13967ff759cc748c88c54ab2bcac24fb5f6a..f0d5595354a3d441432b57b0d21f8f9306f9b55d 100644 (file)
@@ -45,9 +45,11 @@ namespace kernelAudio
 
 struct JackState
 {
-  bool running;
-  double bpm;
-  uint32_t frame;
+       bool     running;
+       double   bpm;
+       uint32_t frame;
+
+       bool operator!=(const JackState& o) const;
 };
 
 #endif
@@ -82,9 +84,10 @@ void jackStart();
 void jackStop();
 void jackSetPosition(uint32_t frame);
 void jackSetBpm(double bpm);
-const JackState &jackTransportQuery();
+JackState jackTransportQuery();
 
 #endif
 }}}; // giada::m::kernelAudio::
 
+
 #endif
index 93e8da24dbc802a7855e9af58590a42bd582207f..3347356ed19c7832c27194d1bcec674dc6fb377f 100644 (file)
@@ -72,11 +72,12 @@ void sendMidiLightningInitMsgs_()
        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);
-                       send(m.value | G_MIDI_CHANS[m.channel]);
+                       MidiEvent e(m.value);
+                       e.setChannel(m.channel);
+                       send(e.getRaw());
                }
        }
 }
-
 } // {anonymous}
 
 
@@ -224,7 +225,7 @@ void send(uint32_t data)
        msg.push_back(getB3(data));
 
        midiOut_->sendMessage(&msg);
-       u::log::print("[KM] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
+       u::log::print("[KM::send] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
 }
 
 
@@ -244,30 +245,30 @@ void send(int b1, int b2, int b3)
                msg.push_back(b3);
 
        midiOut_->sendMessage(&msg);
-       //u::log::print("[KM] send msg=(%X %X %X)\n", b1, b2, b3);
+       u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void sendMidiLightning(uint32_t learn, const midimap::Message& m)
+void sendMidiLightning(uint32_t learnt, const midimap::Message& m)
 {
        // Skip lightning message if not defined in midi map
 
        if (!midimap::isDefined(m))
        {
-               u::log::print("[KM] message skipped (not defined in midimap)");
+               u::log::print("[KM::sendMidiLightning] message skipped (not defined in midimap)");
                return;
        }
 
-       u::log::print("[KM] learn=%#X, chan=%d, msg=%#X, offset=%d\n", learn, m.channel
-               m.value, m.offset);
+       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 = ((learn & 0x00FF0000) >> 16) << m.offset;
+       uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
 
        /* Merge the previously prepared channel into final message, and finally send 
        it. */
@@ -284,6 +285,7 @@ unsigned countInPorts()  { return numInPorts_; }
 unsigned countOutPorts() { return numOutPorts_; }
 bool getStatus()         { return status_; }
 
+
 /* -------------------------------------------------------------------------- */
 
 
@@ -296,15 +298,4 @@ uint32_t getIValue(int b1, int b2, int b3)
 {
        return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
 }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-uint32_t setChannel(uint32_t iValue, int channel)
-{
-       uint32_t chanMask = 0xF << 24;
-       return (iValue & (~chanMask)) | (channel << 24);
-}
-
 }}}; // giada::m::kernelMidi::
index f185b0c1b1f6398e7dce877a60640f71d14016ec..2ec6b351f6948c097c9e15570751a58981183378 100644 (file)
@@ -44,12 +44,6 @@ int getB3(uint32_t iValue);
 
 uint32_t getIValue(int b1, int b2, int b3);
 
-/* setChannel
-Changes MIDI channel number inside iValue. Returns new message with updated
-channel. */
-
-uint32_t setChannel(uint32_t iValue, int channel);
-
 /* send
 Sends a MIDI message 's' as uint32_t or as separate bytes. */
 
@@ -59,7 +53,7 @@ void send(int b1, int b2=-1, int b3=-1);
 /* sendMidiLightning
 Sends a MIDI lightning message defined by 'msg'. */
 
-void sendMidiLightning(uint32_t learn, const midimap::Message& msg);
+void sendMidiLightning(uint32_t learnt, const midimap::Message& msg);
 
 /* setApi
 Sets the Api in use for both in & out messages. */
index 09227fc28294a9de186f670507ae970f4d1f5b92..269cb7fdebeb003bcb4a6a17e57e2c57d97a3e7c 100644 (file)
 #include <cassert>
 #include <vector>
 #include "glue/plugin.h"
-#include "glue/io.h"
-#include "glue/channel.h"
-#include "glue/main.h"
+#include "glue/events.h"
 #include "utils/log.h"
 #include "utils/math.h"
 #include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/conf.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
@@ -79,7 +74,7 @@ bool isMasterMidiInAllowed_(int c)
 bool isChannelMidiInAllowed_(ID channelId, int c)
 {
        model::ChannelsLock l(model::channels);
-       return model::get(model::channels, channelId).isMidiInAllowed(c);
+       return model::get(model::channels, channelId).midiLearner.state->isAllowed(c);
 }
 
 
@@ -101,11 +96,11 @@ void processPlugins_(const std::vector<ID>& ids, const MidiEvent& midiEvent)
        m::model::PluginsLock l(m::model::plugins);
 
        for (ID id : ids) {
-               m::Plugin& p = m::model::get(m::model::plugins, id);
+               const m::Plugin& p = m::model::get(m::model::plugins, id);
                for (unsigned k = 0; k < p.midiInParams.size(); k++) {
                        if (pure != p.midiInParams.at(k))
                                continue;
-                       c::plugin::setParameter(id, k, vf, /*gui=*/false);
+                       c::events::setPluginParameter(id, k, vf, /*gui=*/false);
                        u::log::print("  >>> [plugin %d parameter %d] (pure=0x%X, value=%d, float=%f)\n",
                                p.id, k, pure, midiEvent.getVelocity(), vf);
                }
@@ -122,98 +117,68 @@ void processChannels_(const MidiEvent& midiEvent)
 {
        uint32_t pure = midiEvent.getRawNoVelocity();
 
-       /* TODO - this is definitely not the best approach but it's necessary as
-       you can't call actions on m::model::channels while locking on a upper
-       level. Let's wait for a better async mechanism... */
+       model::ChannelsLock lock(model::channels);
 
-       std::vector<std::function<void()>> actions;
-
-       model::channels.lock();
-       for (Channel* ch : model::channels) {
+       for (const Channel* c : model::channels) {
 
                /* Do nothing on this channel if MIDI in is disabled or filtered out for
                the current MIDI channel. */
 
-               if (!ch->midiIn || !ch->isMidiInAllowed(midiEvent.getChannel()))
+               if (!c->midiLearner.state->isAllowed(midiEvent.getChannel()))
                        continue;
 
-               if      (pure == ch->midiInKeyPress) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> keyPress, ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::io::keyPress(ch->id, false, false, midiEvent.getVelocity());
-                       });
+               if (pure == c->midiLearner.state->keyPress.load()) {
+                       u::log::print("  >>> keyPress, ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::pressChannel(c->id, midiEvent.getVelocity(), Thread::MIDI);
                }
-               else if (pure == ch->midiInKeyRel) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> keyRel ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::io::keyRelease(ch->id, false, false);
-                       });
+               else if (pure == c->midiLearner.state->keyRelease.load()) {
+                       u::log::print("  >>> keyRel ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::releaseChannel(c->id, Thread::MIDI);
                }
-               else if (pure == ch->midiInMute) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> mute ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::channel::toggleMute(ch->id);
-                       });
+               else if (pure == c->midiLearner.state->mute.load()) {
+                       u::log::print("  >>> mute ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::toggleMuteChannel(c->id, Thread::MIDI);
                }               
-               else if (pure == ch->midiInKill) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> kill ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::channel::kill(ch->id, /*record=*/false);
-                       });
+               else if (pure == c->midiLearner.state->kill.load()) {
+                       u::log::print("  >>> kill ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::killChannel(c->id, Thread::MIDI);
                }               
-               else if (pure == ch->midiInArm) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> arm ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::channel::toggleArm(ch->id);
-                       });
+               else if (pure == c->midiLearner.state->arm.load()) {
+                       u::log::print("  >>> arm ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::toggleArmChannel(c->id, Thread::MIDI);
                }
-               else if (pure == ch->midiInSolo) {
-                       actions.push_back([=] {
-                               u::log::print("  >>> solo ch=%d (pure=0x%X)\n", ch->id, pure);
-                               c::channel::toggleSolo(ch->id);
-                       });
+               else if (pure == c->midiLearner.state->solo.load()) {
+                       u::log::print("  >>> solo ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::toggleSoloChannel(c->id, Thread::MIDI);
                }
-               else if (pure == ch->midiInVolume) {
-                       actions.push_back([=] {
-                               float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); 
-                               u::log::print("  >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n",
-                                       ch->id, pure, midiEvent.getVelocity(), vf);
-                               c::channel::setVolume(ch->id, vf, /*gui=*/false);
-                       });
+               else if (pure == c->midiLearner.state->volume.load()) {
+                       float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); 
+                       u::log::print("  >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n", 
+                               c->id, pure, midiEvent.getVelocity(), vf);
+                       c::events::setChannelVolume(c->id, vf, Thread::MIDI);
                }
-               else {
-                       const SampleChannel* sch = static_cast<const SampleChannel*>(ch);
-                       if (pure == sch->midiInPitch) {
-                               actions.push_back([=] {
-                                       float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH); 
-                                       u::log::print("  >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n",
-                                               sch->id, pure, midiEvent.getVelocity(), vf);
-                                       c::channel::setPitch(sch->id, vf);
-                               });
-                       }
-                       else 
-                       if (pure == sch->midiInReadActions) {
-                               actions.push_back([=] {
-                                       u::log::print("  >>> toggle read actions ch=%d (pure=0x%X)\n", sch->id, pure);
-                                       c::channel::toggleReadingActions(sch->id);
-                               });
-                       }
+               else if (pure == c->midiLearner.state->pitch.load()) {
+                       float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH); 
+                       u::log::print("  >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n",
+                               c->id, pure, midiEvent.getVelocity(), vf);
+                       c::events::setChannelPitch(c->id, vf, Thread::MIDI);
+               }
+               else if (pure == c->midiLearner.state->readActions.load()) {
+                       u::log::print("  >>> toggle read actions ch=%d (pure=0x%X)\n", c->id, pure);
+                       c::events::toggleReadActionsChannel(c->id, Thread::MIDI);
                }
-#ifdef WITH_VST
 
+#ifdef WITH_VST
                /* Process learned plugins parameters. */
-               processPlugins_(ch->pluginIds, midiEvent); 
-
+               processPlugins_(c->pluginIds, midiEvent); 
 #endif
 
-               /* Redirect full midi message (pure + velocity) to plugins. */
-               ch->receiveMidi(midiEvent.getRaw());
-       }
-       model::channels.unlock();
+               /* Redirect raw MIDI message (pure + velocity) to plug-ins in armed
+               channels. */
 
-       /* Apply all the collected actions. */
-       for (auto& action : actions)
-               action();
+               if (c->state->armed.load() == true)
+                       c::events::sendMidiToChannel(c->id, midiEvent, Thread::MIDI);
+       }
 }
 
 
@@ -228,43 +193,43 @@ void processMaster_(const MidiEvent& midiEvent)
        const model::MidiIn* midiIn = model::midiIn.get();
 
        if      (pure == midiIn->rewind) {
-               mh::rewindSequencer();
+               c::events::rewindSequencer(Thread::MIDI);
                u::log::print("  >>> rewind (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->startStop) {
-               mh::toggleSequencer();
+               c::events::toggleSequencer(Thread::MIDI);
                u::log::print("  >>> startStop (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->actionRec) {
-               recManager::toggleActionRec(conf::conf.recTriggerMode);
+               c::events::toggleActionRecording();
                u::log::print("  >>> actionRec (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->inputRec) {
-               c::main::toggleInputRec();
+               c::events::toggleInputRecording();
                u::log::print("  >>> inputRec (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->metronome) {
-               m::mixer::toggleMetronome();
+               c::events::toggleMetronome();
                u::log::print("  >>> metronome (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->volumeIn) {
                float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); 
-               c::main::setInVol(vf, /*gui=*/false);
+               c::events::setMasterInVolume(vf, Thread::MIDI);
                u::log::print("  >>> input volume (master) (pure=0x%X, value=%d, float=%f)\n",
                        pure, midiEvent.getVelocity(), vf);
        }
        else if (pure == midiIn->volumeOut) {
                float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); 
-               c::main::setOutVol(vf, /*gui=*/false);
+               c::events::setMasterOutVolume(vf, Thread::MIDI);
                u::log::print("  >>> output volume (master) (pure=0x%X, value=%d, float=%f)\n",
                        pure, midiEvent.getVelocity(), vf);
        }
        else if (pure == midiIn->beatDouble) {
-               c::main::beatsMultiply();
+               c::events::multiplyBeats();
                u::log::print("  >>> sequencer x2 (master) (pure=0x%X)\n", pure);
        }
        else if (pure == midiIn->beatHalf) {
-               c::main::beatsDivide();
+               c::events::divideBeats();
                u::log::print("  >>> sequencer /2 (master) (pure=0x%X)\n", pure);
        }
 }
@@ -280,18 +245,21 @@ void learnChannel_(MidiEvent e, int param, ID channelId, std::function<void()> d
 
        uint32_t raw = e.getRawNoVelocity();
 
-       model::onSwap(model::channels, channelId, [&](Channel& c)
+       model::onGet(model::channels, channelId, [param, raw](Channel& c)
        {       
                switch (param) {
-                       case G_MIDI_IN_KEYPRESS:     c.midiInKeyPress = raw; break;
-                       case G_MIDI_IN_KEYREL:       c.midiInKeyRel   = raw; break;
-                       case G_MIDI_IN_KILL:         c.midiInKill     = raw; break;
-                       case G_MIDI_IN_ARM:          c.midiInArm      = raw; break;
-                       case G_MIDI_IN_MUTE:         c.midiInVolume   = raw; break;
-                       case G_MIDI_IN_SOLO:         c.midiInMute     = raw; break;
-                       case G_MIDI_IN_VOLUME:       c.midiInVolume   = raw; break;
-                       case G_MIDI_IN_PITCH:        static_cast<SampleChannel&>(c).midiInPitch       = raw; break;
-                       case G_MIDI_IN_READ_ACTIONS: static_cast<SampleChannel&>(c).midiInReadActions = raw; break;
+                       case G_MIDI_IN_KEYPRESS:     c.midiLearner.state->keyPress.store(raw);    break;
+                       case G_MIDI_IN_KEYREL:       c.midiLearner.state->keyRelease.store(raw);  break;
+                       case G_MIDI_IN_KILL:         c.midiLearner.state->kill.store(raw);        break;
+                       case G_MIDI_IN_ARM:          c.midiLearner.state->arm.store(raw);         break;
+                       case G_MIDI_IN_MUTE:         c.midiLearner.state->mute.store(raw);        break;
+                       case G_MIDI_IN_SOLO:         c.midiLearner.state->solo.store(raw);        break;
+                       case G_MIDI_IN_VOLUME:       c.midiLearner.state->volume.store(raw);      break;
+                       case G_MIDI_IN_PITCH:        c.midiLearner.state->pitch.store(raw);       break;
+                       case G_MIDI_IN_READ_ACTIONS: c.midiLearner.state->readActions.store(raw); break;
+                       case G_MIDI_OUT_L_PLAYING:   c.midiLighter.state->playing.store(raw);     break;
+                       case G_MIDI_OUT_L_MUTE:      c.midiLighter.state->mute.store(raw);        break;
+                       case G_MIDI_OUT_L_SOLO:      c.midiLighter.state->solo.store(raw);        break;
                }
        });
 
@@ -307,7 +275,7 @@ void learnMaster_(MidiEvent e, int param, std::function<void()> doneCb)
 
        uint32_t raw = e.getRawNoVelocity();
 
-       model::onSwap(model::midiIn, [&](model::MidiIn& m)
+       model::onSwap(model::midiIn, [param, raw](model::MidiIn& m)
        {
                switch (param) {
                        case G_MIDI_IN_REWIND:      m.rewind     = raw; break;
index abdbea4bd43a052fd690aabaeb92126d9e721873..b845693fd389c8fcadc4a48092f2a7e1afc3b3c7 100644 (file)
 
 #include <cassert>
 #include "const.h"
+#include "utils/math.h"
 #include "midiEvent.h"
 
 
 namespace giada {
 namespace m
 {
-MidiEvent::MidiEvent()
-       : m_status  (0),
-         m_channel (0),
-         m_note    (0),
-         m_velocity(0),
-         m_delta   (0)
+namespace
 {
-}
+constexpr int FLOAT_FACTOR = 10000;
+} // {anonymous}
 
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 
 MidiEvent::MidiEvent(uint32_t raw)
-       : m_status  ((raw & 0xF0000000) >> 24),
-         m_channel ((raw & 0x0F000000) >> 24),
-         m_note    ((raw & 0x00FF0000) >> 16),
-         m_velocity((raw & 0x0000FF00) >> 8),
-         m_delta   (0)  // not used
+: 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
 {
 }
 
@@ -59,9 +58,20 @@ MidiEvent::MidiEvent(uint32_t raw)
 /* -------------------------------------------------------------------------- */
 
 
+/* static_cast to avoid ambiguity with MidiEvent(float). */
 MidiEvent::MidiEvent(int byte1, int byte2, int byte3)
-       : MidiEvent((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00))
+: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)))
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiEvent::MidiEvent(float v) 
+: MidiEvent(ENVELOPE, 0, 0)
 {
+       m_velocity = v * FLOAT_FACTOR;
 }
 
 
@@ -128,6 +138,12 @@ int MidiEvent::getVelocity() const
 }      
 
 
+float MidiEvent::getVelocityFloat() const
+{
+       return m_velocity / static_cast<float>(FLOAT_FACTOR);
+}
+
+
 bool MidiEvent::isNoteOnOff() const
 {
        return m_status == NOTE_ON || m_status == NOTE_OFF;
index 711c54d2a5e394acb4452b64491b65900a3ce4c8..eefeb6c8ffe36c6cf6afb8de9ea97090fd8c1c8f 100644 (file)
@@ -44,14 +44,25 @@ public:
        static const int NOTE_KILL = 0x70;
        static const int ENVELOPE  = 0xB0;
 
-       MidiEvent();
+       /* MidiEvent (1)
+       Creates and empty and invalid MIDI event. */
+
+       MidiEvent() = default;
+
        MidiEvent(uint32_t raw);
        MidiEvent(int byte1, int byte2, int byte3);
 
+       /* MidiEvent (4)
+       A constructor that takes a float parameter. Useful to build ENVELOPE events 
+       for automations, volume and pitch. */
+
+       MidiEvent(float v);
+
        int getStatus() const;  
        int getChannel() const; 
        int getNote() const;    
-       int getVelocity() const;        
+       int getVelocity() const;
+       float getVelocityFloat() const;
        bool isNoteOnOff() const;       
        int getDelta() const;
 
index fa4efc7bdfc4818e4bb8df03e64e457d23594791..ea428578867e2d58779383971aa9aaab47d4d438 100644 (file)
@@ -92,7 +92,7 @@ void parse_(Message& message)
 
        std::string input = message.valueStr;
 
-       size_t f = input.find("0x"); // check if "0x" is there
+       std::size_t f = input.find("0x"); // check if "0x" is there
        if (f != std::string::npos)
                input = message.valueStr.replace(f, 2, "");
 
index ebe49660dd6d3cb99c1abd15fbaff1f3c8f78cfb..a556b7a7693adbf972c0ad1bbd284d9c6589ca4c 100644 (file)
@@ -31,9 +31,6 @@
 #include "utils/log.h"
 #include "utils/math.h"
 #include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/wave.h"
 #include "core/kernelAudio.h"
 #include "core/recorder.h"
@@ -45,6 +42,7 @@
 #include "core/const.h"
 #include "core/audioBuffer.h"
 #include "core/action.h"
+#include "core/sequencer.h"
 #include "core/mixer.h"
 
 
@@ -54,56 +52,15 @@ namespace mixer
 {
 namespace
 {
-struct Metronome
-{
-       static constexpr Frame CLICK_SIZE = 38;
-
-       float beat[CLICK_SIZE] = {
-                0.059033,  0.117240,  0.173807,  0.227943,  0.278890,  0.325936,
-                0.368423,  0.405755,  0.437413,  0.462951,  0.482013,  0.494333,
-                0.499738,  0.498153,  0.489598,  0.474195,  0.452159,  0.423798,
-                0.389509,  0.349771,  0.289883,  0.230617,  0.173194,  0.118739,
-                0.068260,  0.022631, -0.017423, -0.051339,     -0.078721, -0.099345,
-               -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954,
-               -0.070862, -0.048844
-       };
-
-       float bar[CLICK_SIZE] = {
-                0.175860,  0.341914,  0.488904,  0.608633,  0.694426,  0.741500,
-                0.747229,  0.711293,  0.635697,  0.524656,  0.384362,  0.222636,
-                0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653,
-               -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160,
-               -0.201360, -0.067752,  0.052194,  0.151746,  0.226280,  0.273493,
-                0.293425,  0.288307,  0.262252,  0.220811,  0.170435,  0.117887,
-                0.069639,  0.031320
-       };
-
-       Frame tracker  = 0;
-       bool  running  = false;
-       bool  playBar  = false;
-       bool  playBeat = false;
-
-       void render(AudioBuffer& outBuf, bool& process, float* data, Frame f)
-       {
-               process = true;
-               for (int i=0; i<outBuf.countChannels(); i++)
-                       outBuf[f][i] += data[tracker];
-               if (++tracker > Metronome::CLICK_SIZE) {
-                       process = false;
-                       tracker = 0;
-               }       
-       }
-} metronome_;
-
-/* vChanInput_
-Virtual channel for input recording. */
+/* recBuffer_
+Working buffer for audio recording. */
 
-AudioBuffer vChanInput_;
+AudioBuffer recBuffer_;
 
-/* vChanInToOut_
-Virtual channel in->out bridge (hear what you're playin). */
+/* inBuffer_
+Working buffer for input channel. */
 
-AudioBuffer vChanInToOut_;
+AudioBuffer inBuffer_;
 
 /* inputTracker_
 Frame position while recording. */
@@ -118,16 +75,25 @@ std::function<void()> signalCb_ = nullptr;
 std::atomic<bool> processing_(false);
 std::atomic<bool> active_(false);
 
+/* eventBuffer_
+Buffer of events sent to channels for event parsing. This is filled with Events
+coming from the two event queues.*/
+
+EventBuffer eventBuffer_;
+
 
 /* -------------------------------------------------------------------------- */
 
 
-void computePeak_(const AudioBuffer& buf, std::atomic<float>& peak)
+bool isChannelAudible_(const Channel& c)
 {
-       for (int i=0; i<buf.countFrames(); i++)
-               for (int j=0; j<buf.countChannels(); j++)
-                       if (buf[i][j] > peak)
-                               peak = buf[i][j];
+    if (c.getType() == ChannelType::MASTER || c.getType() == ChannelType::PREVIEW)
+        return true;
+    if (c.state->mute.load() == true)
+        return false;
+    model::MixerLock ml(model::mixer);
+    bool hasSolos = model::mixer.get()->hasSolos;
+    return !hasSolos || (hasSolos && c.state->solo.load() == true);
 }
 
 
@@ -140,151 +106,105 @@ void lineInRec_(const AudioBuffer& inBuf)
 {
        if (!recManager::isRecordingInput() || !kernelAudio::isInputEnabled())
                return;
+       
+       float inVol        = mh::getInVol();
+       int   framesInLoop = clock::getFramesInLoop();
 
-       for (int i=0; i<inBuf.countFrames(); i++, inputTracker_++)
-               for (int j=0; j<inBuf.countChannels(); j++) {
-                       if (inputTracker_ >= clock::getFramesInLoop())
-                               inputTracker_ = 0;
-                       vChanInput_[inputTracker_][j] += inBuf[i][j] * mh::getInVol();  // adding: overdub!
-               }
+       for (int i = 0; i < inBuf.countFrames(); i++, inputTracker_++)
+               for (int j = 0; j < inBuf.countChannels(); j++)
+                       recBuffer_[inputTracker_ % framesInLoop][j] += inBuf[i][j] * inVol;  // adding: overdub!
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 /* processLineIn
-Computes line in peaks, plus handles "hear what you're playin'" thing. */
+Computes line in peaks, plus handles the internal working buffer for input. */
 
 void processLineIn_(const AudioBuffer& inBuf)
 {
        if (!kernelAudio::isInputEnabled())
                return;
 
-       computePeak_(inBuf, peakIn);
+       peakIn.store(inBuf.getPeak());
 
        if (signalCb_ != nullptr && u::math::linearToDB(peakIn) > conf::conf.recTriggerLevel) {
                signalCb_();
                signalCb_ = nullptr;
        }
 
-       /* "hear what you're playing" - process, copy and paste the input buffer onto 
-       the output buffer. */
+       /* Prepare the working buffer for input stream, which will be processed 
+       later on by the Master Input Channel with plug-ins.  */
 
        model::MixerLock lock(model::mixer);
-       
-       if (model::mixer.get()->inToOut)
-               for (int i=0; i<vChanInToOut_.countFrames(); i++)
-                       for (int j=0; j<vChanInToOut_.countChannels(); j++)
-                               vChanInToOut_[i][j] = inBuf[i][j] * mh::getInVol();
+       inBuffer_.copyData(inBuf, mh::getInVol());
 }
 
 
 /* -------------------------------------------------------------------------- */
 
-/* doQuantize
-Computes quantization on 'rewind' button. */
 
-void doQuantize_(unsigned frame)
+void fillEventBuffer_()
 {
-       /* Nothing to do if quantizer disabled or a quanto has not passed yet. */
+       eventBuffer_.clear();
 
-       if (clock::getQuantize() == 0 || !clock::quantoHasPassed())
-               return;
+       Event e;
+       while (UIevents.pop(e))   eventBuffer_.push_back(e);
+       while (MidiEvents.pop(e)) eventBuffer_.push_back(e);
 
-       if (rewindWait) {
-               rewindWait = false;
-               clock::rewind();
-               mh::rewindChannels();
-       }
+#ifdef G_DEBUG_MODE
+       for (const Event& e : eventBuffer_)
+               G_DEBUG("Event type=" << (int) e.type << ", channel=" << e.action.channelId 
+                       << ", delta=" << e.delta << ", globalFrame=" << clock::getCurrentFrame());
+#endif
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void renderMetronome_(AudioBuffer& outBuf, Frame f)
+void processChannels_(AudioBuffer& out, AudioBuffer& in)
 {
-       if (!metronome_.running)
-               return;
+       model::ChannelsLock lock(model::channels);
 
-       if (clock::isOnBar() || metronome_.playBar)
-               metronome_.render(outBuf, metronome_.playBar, metronome_.bar, f);
-       else
-       if (clock::isOnBeat() || metronome_.playBeat)
-               metronome_.render(outBuf, metronome_.playBeat, metronome_.beat, f);
+       for (const Channel* c : model::channels) {
+               bool audible = isChannelAudible_(*c);   
+               c->parse(eventBuffer_, audible); 
+               if (c->getType() != ChannelType::MASTER) {
+                       c->advance(out.countFrames());
+                       c->render(&out, &in, audible);
+               }
+       }
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void parseEvents_(Frame f)
+void processSequencer_(AudioBuffer& in)
 {
-       mixer::FrameEvents fe = {
-               .frameLocal   = f,
-               .frameGlobal  = clock::getCurrentFrame(),
-               .doQuantize   = clock::getQuantize() == 0 || !clock::quantoHasPassed(),
-               .onBar        = clock::isOnBar(),
-               .onFirstBeat  = clock::isOnFirstBeat(),
-               .quantoPassed = clock::quantoHasPassed(),
-               .actions      = recorder::getActionsOnFrame(clock::getCurrentFrame()),
-       };
-
-       model::ChannelsLock lock(model::channels);
-
-       /* TODO - channel->parseEvents alters things in Channel (i.e. it's mutable).
-       Refactoring needed ASAP. */
-
-       for (Channel* ch : model::channels)
-               ch->parseEvents(fe); 
+       sequencer::parse(eventBuffer_);
+       if (clock::isActive()) {
+               if (clock::isRunning())
+                       sequencer::run(in.countFrames());
+               lineInRec_(in);
+       }
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void render_(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut)
+void renderMasterIn_(AudioBuffer& in)
 {
-       bool running = clock::isRunning();
-
        model::ChannelsLock lock(model::channels);
-
-       /* TODO - channel->render alters things in Channel (i.e. it's mutable).
-       Refactoring needed ASAP. */
-
-       for (const Channel* ch : model::channels) {
-               if (ch == nullptr ||
-                       ch->id == mixer::MASTER_OUT_CHANNEL_ID ||
-                       ch->id == mixer::MASTER_IN_CHANNEL_ID)
-                       continue;
-               const_cast<Channel*>(ch)->render(out, in, inToOut, isChannelAudible(ch), running);
-       }
-
-       assert(model::channels.size() >= 3); // Preview channel included
-
-       /* Master channels are processed at the end, when the buffers have already 
-       been filled. */
-       
-       model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).render(out, in, inToOut, true, true);
-       model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).render(out, in, inToOut, true, true);
+       model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).render(nullptr, &in, true);
 }
 
-
-/* -------------------------------------------------------------------------- */
-
-
-void processSequencer_(AudioBuffer& out, const AudioBuffer& in)
+void renderMasterOut_(AudioBuffer& out)
 {
-       for (int j=0; j<out.countFrames(); j++) {
-               if (clock::isRunning()) {
-                       parseEvents_(j);
-                       doQuantize_(j);
-               }
-               clock::sendMIDIsync();
-               clock::incrCurrentFrame();
-               renderMetronome_(out, j);
-       }
-       lineInRec_(in);
+       model::ChannelsLock lock(model::channels);
+       model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).render(&out, nullptr, true);
 }
 
 
@@ -296,23 +216,20 @@ Cleans up every buffer. */
 void prepareBuffers_(AudioBuffer& outBuf)
 {
        outBuf.clear();
-       vChanInToOut_.clear();
+       inBuffer_.clear();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
-/* limitOutput
+/* limit_
 Applies a very dumb hard limiter. */
 
-void limitOutput_(AudioBuffer& outBuf)
+void limit_(AudioBuffer& outBuf)
 {
-       if (!conf::conf.limitOutput)
-               return;
        for (int i=0; i<outBuf.countFrames(); i++)
                for (int j=0; j<outBuf.countChannels(); j++)
-                       if      (outBuf[i][j] > 1.0f)  outBuf[i][j] = 1.0f;
-                       else if (outBuf[i][j] < -1.0f) outBuf[i][j] = -1.0f;    
+                       outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
 }
 
 
@@ -320,20 +237,22 @@ void limitOutput_(AudioBuffer& outBuf)
 
 /* finalizeOutput
 Last touches after the output has been rendered: apply inToOut if any, apply
-output volume. */
+output volume, compute peak. */
 
 void finalizeOutput_(AudioBuffer& outBuf)
 {
-       model::MixerLock lock(model::mixer);
-       
-       //printf("%f\n", mh::getOutVol());
+       bool  inToOut = mh::getInToOut();
+       float outVol  = mh::getOutVol();
 
-       for (int i=0; i<outBuf.countFrames(); i++)
-               for (int j=0; j<outBuf.countChannels(); j++) {
-                       if (model::mixer.get()->inToOut) // Merge vChanInToOut_, if enabled
-                               outBuf[i][j] += vChanInToOut_[i][j];
-                       outBuf[i][j] *= mh::getOutVol(); 
-               }
+       if (inToOut)
+               outBuf.addData(inBuffer_, outVol);
+       else
+               outBuf.applyGain(outVol);
+
+       if (conf::conf.limitOutput)
+               limit_(outBuf);
+       
+       peakOut.store(outBuf.getPeak());
 }
 }; // {anonymous}
 
@@ -343,26 +262,26 @@ void finalizeOutput_(AudioBuffer& outBuf)
 /* -------------------------------------------------------------------------- */
 
 
-std::atomic<bool>  rewindWait(false);
 std::atomic<float> peakOut(0.0);
 std::atomic<float> peakIn(0.0);
 
+Queue<Event, G_MAX_QUEUE_EVENTS> UIevents;
+Queue<Event, G_MAX_QUEUE_EVENTS> MidiEvents;
+
 
 /* -------------------------------------------------------------------------- */
 
 
 void init(Frame framesInSeq, Frame framesInBuffer)
 {
-       /* Allocate virtual inputs. vChanInput_ has variable size: it depends
+       /* Allocate virtual inputs. recBuffer_ has variable size: it depends
        on how many frames there are in sequencer. */
        
-       vChanInput_.alloc(framesInSeq, G_MAX_IO_CHANS);
-       vChanInToOut_.alloc(framesInBuffer, G_MAX_IO_CHANS);
+       recBuffer_.alloc(framesInSeq, G_MAX_IO_CHANS);
+       inBuffer_.alloc(framesInBuffer, G_MAX_IO_CHANS);
 
        u::log::print("[mixer::init] buffers ready - framesInSeq=%d, framesInBuffer=%d\n", 
-               framesInSeq, framesInBuffer);   
-
-       clock::rewind();
+               framesInSeq, framesInBuffer);
 }
 
 
@@ -388,21 +307,21 @@ void disable()
 /* -------------------------------------------------------------------------- */
 
 
-void allocVirtualInput(Frame frames)
+void allocRecBuffer(Frame frames)
 {
-       vChanInput_.alloc(frames, G_MAX_IO_CHANS);
+       recBuffer_.alloc(frames, G_MAX_IO_CHANS);
 }
 
 
-void clearVirtualInput()
+void clearRecBuffer()
 {
-       vChanInput_.clear();
+       recBuffer_.clear();
 }
 
 
-const AudioBuffer& getVirtualInput()
+const AudioBuffer& getRecBuffer()
 {
-       return vChanInput_;
+       return recBuffer_;
 }
 
 
@@ -417,11 +336,9 @@ int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize,
 
        processing_.store(true);
 
-#if defined(__linux__) || defined(__FreeBSD__)
-
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
        if (kernelAudio::getAPI() == G_SYS_API_JACK)
                clock::recvJackSync();
-
 #endif
 
        AudioBuffer out, in;
@@ -437,22 +354,28 @@ int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize,
        prepareBuffers_(out);
        processLineIn_(in);
 
-       /* Process model. */
-
 //out[0][0] = 3.0f;
 
-       if (clock::isActive()) 
-               processSequencer_(out, in);
-       render_(out, in, vChanInToOut_);
+       renderMasterIn_(inBuffer_);
+
+       fillEventBuffer_();
+       processSequencer_(inBuffer_);
+       processChannels_(out, inBuffer_);
+
+       renderMasterOut_(out);
+
+       /* Advance sequencer only when rendering is done. */
+
+       if (clock::isActive())
+               sequencer::advance(out);
 
        /* Post processing. */
 
        finalizeOutput_(out);
-       limitOutput_(out);
-       computePeak_(out, peakOut);
 
        /* Unset data in buffers. If you don't do this, buffers go out of scope and
        destroy memory allocated by RtAudio ---> havoc. */
+
        out.setData(nullptr, 0, 0);
        in.setData (nullptr, 0, 0);
 
@@ -474,20 +397,6 @@ void close()
 /* -------------------------------------------------------------------------- */
 
 
-bool isChannelAudible(const Channel* ch)
-{
-       model::MixerLock l(model::mixer);
-
-       bool hasSolos = model::mixer.get()->hasSolos;
-       return !hasSolos || (hasSolos && ch->solo);
-}
-
-bool isMetronomeOn() { return metronome_.running; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
 void startInputRec()
 {
        /* Start inputTracker_ from the current frame, not the beginning. */
@@ -504,23 +413,17 @@ void stopInputRec()
 /* -------------------------------------------------------------------------- */
 
 
-void toggleMetronome()
+void setSignalCallback(std::function<void()> f)
 {
-       metronome_.running = !metronome_.running;
-}
-
-
-void setMetronome(bool v) 
-{ 
-       metronome_.running = v; 
+       signalCb_ = f;
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void setSignalCallback(std::function<void()> f)
+void pumpEvent(Event e)
 {
-       signalCb_ = f;
+       eventBuffer_.push_back(e);
 }
 }}}; // giada::m::mixer::
index 1ced3451976cdae16e97ec076331bfdb68d0caaf..ea830e4f646893f0405c46debe788377a34fe8df 100644 (file)
 #include <functional>
 #include <vector>
 #include "deps/rtaudio/RtAudio.h"
+#include "core/ringBuffer.h"
 #include "core/recorder.h"
 #include "core/types.h"
+#include "core/queue.h"
+#include "core/midiEvent.h"
 
 
 namespace giada {
@@ -42,28 +45,61 @@ namespace m
 {
 struct Action;
 class Channel;
+class Channel;
 class AudioBuffer;
 
 namespace mixer
 {
-struct FrameEvents
+enum class EventType 
+{
+       KEY_PRESS, 
+       KEY_RELEASE, 
+       KEY_KILL,
+       SEQUENCER_FIRST_BEAT, // 3
+       SEQUENCER_BAR,        // 4
+       SEQUENCER_START,      // 5
+       SEQUENCER_STOP,       // 6
+       SEQUENCER_REWIND,     // 7
+       SEQUENCER_REWIND_REQ, // 8
+       MIDI, 
+       ACTION, 
+       CHANNEL_TOGGLE_READ_ACTIONS,
+       CHANNEL_TOGGLE_ARM,
+       CHANNEL_MUTE,
+       CHANNEL_SOLO,
+       CHANNEL_VOLUME,
+       CHANNEL_PITCH,
+       CHANNEL_PAN
+};
+
+struct Event
 {
-       Frame frameLocal;
-       Frame frameGlobal;
-       bool  doQuantize;
-       bool  onBar;
-       bool  onFirstBeat;
-       bool  quantoPassed;
-       const std::vector<Action>* actions;
+       EventType type;
+       Frame     delta;
+       Action    action;
 };
 
+/* 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_QUEUE_EVENTS * 2>;
+
 constexpr int MASTER_OUT_CHANNEL_ID = 1;
 constexpr int MASTER_IN_CHANNEL_ID  = 2;
 constexpr int PREVIEW_CHANNEL_ID    = 3;
 
-extern std::atomic<bool>  rewindWait;    // rewind guard, if quantized
-extern std::atomic<float> peakOut;
-extern std::atomic<float> peakIn;
+extern std::atomic<float> peakOut; // TODO - move to model::
+extern std::atomic<float> peakIn;  // TODO - move to model::
+
+/* Channel Event queues
+Collect events coming from the UI or MIDI devices to be sent to channels. 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! */
+
+extern Queue<Event, G_MAX_QUEUE_EVENTS> UIevents;
+extern Queue<Event, G_MAX_QUEUE_EVENTS> MidiEvents;
 
 void init(Frame framesInSeq, Frame framesInBuffer);
 
@@ -75,22 +111,22 @@ called. */
 void enable();
 void disable();
 
-/* allocVirtualInput
+/* allocRecBuffer
 Allocates new memory for the virtual input channel. Call this whenever you 
 shrink or resize the sequencer. */
 
-void allocVirtualInput(Frame frames);
+void allocRecBuffer(Frame frames);
 
-/* clearVirtualInput
+/* clearRecBuffer
 Clears internal virtual channel. */
 
-void clearVirtualInput();
+void clearRecBuffer();
 
-/* getVirtualInput
+/* getRecBuffer
 Returns a read-only reference to the internal virtual channel. Use this to
 merge data into channel after an input recording session. */
 
-const AudioBuffer& getVirtualInput(); 
+const AudioBuffer& getRecBuffer(); 
 
 void close();
 
@@ -100,19 +136,21 @@ Core method (callback) */
 int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize, double streamTime,
        RtAudioStreamStatus status, void* userData);
 
-bool isChannelAudible(const Channel* ch);
-
 /* startInputRec, stopInputRec
 Starts/stops input recording on frame clock::getCurrentFrame(). */
 
 void startInputRec();
 void stopInputRec();
 
-void toggleMetronome();
-bool isMetronomeOn();
-void setMetronome(bool v);
-
 void setSignalCallback(std::function<void()> f);
+
+/* pumpEvent
+Pumps a new mixer::Event into the event vector. Use this function when you want
+to inject a new event for the **current** block. Push the event in the two 
+queues UIevents and MIDIevents above if the event can be processed in the next 
+block instead. */
+
+void pumpEvent(Event e);
 }}} // giada::m::mixer::;
 
 
index 9f315054468e4c40edb0c245e8782731e5976a6c..6d864d788a19073e04bc0eead2425b2e468b85ac 100644 (file)
@@ -35,9 +35,6 @@
 #include "glue/main.h"
 #include "glue/channel.h"
 #include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/channels/channelManager.h"
 #include "core/kernelMidi.h"
 #include "core/mixer.h"
@@ -76,7 +73,7 @@ std::unique_ptr<Channel> createChannel_(ChannelType type, ID columnId, ID channe
                ch->id = channelId;
        }
        
-       return ch;      
+       return ch;
 }
 
 
@@ -102,7 +99,7 @@ waveManager::Result createWave_(const std::string& fname)
 /* -------------------------------------------------------------------------- */
 
 
-bool channelHas_(std::function<bool(const Channel*)> f)
+bool anyChannel_(std::function<bool(const Channel*)> f)
 {
        model::ChannelsLock lock(model::channels);
        return std::any_of(model::channels.begin(), model::channels.end(), f);
@@ -112,29 +109,51 @@ bool channelHas_(std::function<bool(const Channel*)> f)
 /* -------------------------------------------------------------------------- */
 
 
-bool canInputRec_(size_t chanIndex)
+template <typename F>
+std::vector<ID> getChannelsIf_(F f)
 {
        model::ChannelsLock l(model::channels);
-       return model::channels.get(chanIndex)->canInputRec();
+
+       std::vector<ID> ids;
+       for (const Channel* c : model::channels)
+               if (f(c)) ids.push_back(c->id);
+       
+       return ids;     
+}
+
+
+std::vector<ID> getChannelsWithWave_()
+{
+       return getChannelsIf_([] (const Channel* c)
+       {
+               return c->samplePlayer && c->samplePlayer->hasWave();
+       });
+}
+
+
+std::vector<ID> getRecordableChannels_()
+{
+       return getChannelsIf_([] (const Channel* c)
+       {
+               return c->canInputRec();
+       });
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 /* pushWave_
-Pushes a new wave into Sample Channel 'ch' and into the corresponding Wave list.
+Pushes a new wave into Channel 'ch' and into the corresponding Wave list.
 Use this when modifying a local model, before swapping it. */
 
-void pushWave_(SampleChannel& ch, std::unique_ptr<Wave>&& w, bool clone)
+void pushWave_(Channel& ch, std::unique_ptr<Wave>&& w)
 {
-       if (ch.hasWave && !clone) // Don't pop if cloning a channel
-               model::waves.pop(model::getIndex(model::waves, ch.waveId));
-       
-       ID    id   = w->id;
-       Frame size = w->getSize();
+       assert(ch.getType() == ChannelType::SAMPLE);
 
        model::waves.push(std::move(w));
-       ch.pushWave(id, size);  
+
+       model::WavesLock l(model::waves);
+       ch.samplePlayer->loadWave(model::waves.back());
 }
 }; // {anonymous}
 
@@ -172,31 +191,9 @@ void close()
 /* -------------------------------------------------------------------------- */
 
 
-bool uniqueSamplePath(ID channelToSkip, const std::string& path)
-{
-       model::ChannelsLock cl(model::channels);
-       model::WavesLock    wl(model::waves);
-
-       for (const Channel* c : model::channels) {
-               if (c->id == channelToSkip || c->type != ChannelType::SAMPLE)
-                       continue;
-               const SampleChannel* sc = static_cast<const SampleChannel*>(c);
-               if (sc->hasWave && model::get(model::waves, sc->waveId).getPath() == path)
-                       return false;
-       }
-       return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-ID addChannel(ChannelType type, ID columnId)
+void addChannel(ChannelType type, ID columnId)
 {
-       std::unique_ptr<Channel> c  = createChannel_(type, columnId);
-       ID                       id = c->id;
-       model::channels.push(std::move(c));
-       return id;
+       model::channels.push(std::move(createChannel_(type, columnId)));
 }
 
 
@@ -210,11 +207,20 @@ int loadChannel(ID channelId, const std::string& fname)
        if (res.status != G_RES_OK) 
                return res.status;
 
+       ID oldWaveId;
+
        model::onSwap(model::channels, channelId, [&](Channel& c)
        {
-               pushWave_(static_cast<SampleChannel&>(c), std::move(res.wave), /*clone=*/false);
+               oldWaveId = c.samplePlayer->getWaveId();
+               pushWave_(c, std::move(res.wave));
        });
 
+       /* Remove old wave, if any. It is safe to do it now: the channel already
+       points to the new one. */
+
+       if (oldWaveId != 0)
+               model::waves.pop(model::getIndex(model::waves, oldWaveId));
+
        return res.status;
 }
 
@@ -233,10 +239,9 @@ int addAndLoadChannel(ID columnId, const std::string& fname)
 
 void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w)
 {
-       std::unique_ptr<Channel> ch = createChannel_(ChannelType::SAMPLE, 
-               columnId);
+       std::unique_ptr<Channel> ch = createChannel_(ChannelType::SAMPLE, columnId);
 
-       pushWave_(static_cast<SampleChannel&>(*ch.get()), std::move(w), /*clone=*/false);
+       pushWave_(*ch.get(), std::move(w));
 
        /* Then add new channel to Channel list. */
 
@@ -258,14 +263,14 @@ void cloneChannel(ID channelId)
        /* Clone plugins, actions and wave first in their own lists. */
        
 #ifdef WITH_VST
-       pluginHost::clonePlugins(oldChannel, *newChannel.get());
+       newChannel->pluginIds = pluginHost::clonePlugins(oldChannel.pluginIds);
 #endif
        recorderHandler::cloneActions(channelId, newChannel->id);
        
-       if (newChannel->hasData()) {
-               SampleChannel* sch  = static_cast<SampleChannel*>(newChannel.get());
-               Wave&          wave = model::get(model::waves, sch->waveId);
-               pushWave_(*sch, waveManager::createFromWave(wave, 0, wave.getSize()), /*clone=*/true);
+       if (newChannel->samplePlayer && newChannel->samplePlayer->hasWave()) 
+       {
+               Wave& wave = model::get(model::waves, newChannel->samplePlayer->getWaveId());
+               pushWave_(*newChannel, waveManager::createFromWave(wave, 0, wave.getSize()));
        }
 
        /* Then push the new channel in the channels list. */
@@ -279,22 +284,19 @@ void cloneChannel(ID channelId)
 
 void freeChannel(ID channelId)
 {
-       bool hasWave;   
-       ID   waveId;
+       ID waveId;
        
        /* Remove Wave reference from Channel. */
        
        model::onSwap(model::channels, channelId, [&](Channel& c)
        {
-               SampleChannel& sc = static_cast<SampleChannel&>(c);
-               hasWave = sc.hasWave;
-               waveId  = sc.waveId;
-               sc.empty();
+               waveId = c.samplePlayer->getWaveId();
+               c.samplePlayer->loadWave(nullptr);
        });
 
        /* Then remove the actual Wave, if any. */
        
-       if (hasWave)
+       if (waveId != 0)
                model::waves.pop(model::getIndex(model::waves, waveId)); 
 }
 
@@ -304,8 +306,13 @@ void freeChannel(ID channelId)
 
 void freeAllChannels()
 {
-       for (size_t i = 0; i < model::channels.size(); i++)
-               model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.empty(); });
+       for (ID id : getChannelsWithWave_()) {
+               model::onSwap(model::channels, id, [](Channel& c) 
+               { 
+                       c.samplePlayer->loadWave(nullptr);
+               });
+       }
+
        model::waves.clear();
 }
 
@@ -315,27 +322,22 @@ void freeAllChannels()
 
 void deleteChannel(ID channelId)
 {
-       bool            hasWave = false;
        ID              waveId;
 #ifdef WITH_VST
        std::vector<ID> pluginIds;
 #endif
 
-       model::onGet(model::channels, channelId, [&](Channel& c)
+       model::onGet(model::channels, channelId, [&](const Channel& c)
        {
 #ifdef WITH_VST
                pluginIds = c.pluginIds;
 #endif
-               if (c.type != ChannelType::SAMPLE)
-                       return;
-               SampleChannel& sc = static_cast<SampleChannel&>(c);
-               hasWave   = sc.hasWave;
-               waveId    = sc.waveId;
+               waveId    = c.samplePlayer ? c.samplePlayer->getWaveId() : 0;
        });
        
        model::channels.pop(model::getIndex(model::channels, channelId));
 
-       if (hasWave)
+       if (waveId != 0)
                model::waves.pop(model::getIndex(model::waves, waveId)); 
 
 #ifdef WITH_VST
@@ -349,69 +351,10 @@ void deleteChannel(ID channelId)
 
 void renameChannel(ID channelId, const std::string& name)
 {
-       model::onSwap(model::channels, channelId, [&](Channel& c) { c.name = name; });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startSequencer()
-{
-       switch (clock::getStatus()) {
-               case ClockStatus::STOPPED:
-                       clock::setStatus(ClockStatus::RUNNING); 
-                       break;
-               case ClockStatus::WAITING:
-                       clock::setStatus(ClockStatus::RUNNING); 
-                       recManager::stopActionRec();
-                       break;
-               default: 
-                       break;
-       }
-
-#ifdef __linux__
-       kernelAudio::jackStart();
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopSequencer()
-{
-       clock::setStatus(ClockStatus::STOPPED);
-
-       /* Stop channels with explicit locks. The RAII version would trigger a
-       deadlock if recManager::stopInputRec() is called down below. */
-
-       model::channels.lock();
-       for (Channel* c : model::channels)
-               c->stopBySeq(conf::conf.chansStopOnSeqHalt);
-       model::channels.unlock();
-
-#ifdef __linux__
-       kernelAudio::jackStop();
-#endif
-
-       /* 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();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleSequencer()
-{
-       clock::isRunning() ? stopSequencer() : startSequencer();
+       model::onGet(model::channels, channelId, [&](Channel& c) 
+       { 
+               c.state->name = name; 
+       }, /*rebuild=*/true);
 }
 
 
@@ -420,9 +363,11 @@ void toggleSequencer()
 
 void updateSoloCount()
 {
-       model::onSwap(model::mixer, [&](model::Mixer& m)
+       model::onSwap(model::mixer, [](model::Mixer& m)
        {
-               m.hasSolos = channelHas_([](const Channel* ch) { return ch->solo; });
+               m.hasSolos = anyChannel_([](const Channel* ch) {
+                   return !ch->isInternal() && ch->state->solo.load() == true;
+               });
        });
 }
 
@@ -430,24 +375,6 @@ void updateSoloCount()
 /* -------------------------------------------------------------------------- */
 
 
-void setInVol(float v)
-{
-       model::onGet(model::channels, mixer::MASTER_IN_CHANNEL_ID, [&](Channel& c)
-       {
-               c.volume = v;
-       });
-}
-
-
-void setOutVol(float v)
-{
-       model::onGet(model::channels, mixer::MASTER_OUT_CHANNEL_ID, [&](Channel& c)
-       {
-               c.volume = v;
-       });
-}
-
-
 void setInToOut(bool v)
 {
        model::onSwap(model::mixer, [&](model::Mixer& m)
@@ -463,14 +390,14 @@ void setInToOut(bool v)
 float getInVol()
 {
        model::ChannelsLock l(model::channels); 
-       return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).volume;
+       return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).state->volume.load();
 }
 
 
 float getOutVol()
 {
        model::ChannelsLock l(model::channels); 
-       return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).volume;
+       return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).state->volume.load();
 }
 
 
@@ -482,58 +409,13 @@ bool getInToOut()
 
 /* -------------------------------------------------------------------------- */
 
-
-void rewindSequencer()
-{
-       if (clock::getQuantize() > 0 && clock::isRunning())   // quantize rewind
-               mixer::rewindWait = true;
-       else {
-               clock::rewind();
-               rewindChannels();
-       }
-
-       /* FIXME - potential desync when Quantizer is enabled from this point on.
-       Mixer would wait, while the following calls would be made regardless of its
-       state. */
-
-#ifdef __linux__
-       kernelAudio::jackSetPosition(0);
-#endif
-
-       if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
-               kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindChannels()
-{
-       for (size_t i = 3; i < model::channels.size(); i++)
-               model::onSwap(model::channels, model::getId(model::channels, i), [&](Channel& c) { c.rewindBySeq();     });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
 /* Push a new Wave into each recordable channel. Warning: this algorithm will 
 require some changes when we will allow overdubbing (the previous existing Wave
 has to be overwritten somehow). */
 
 void finalizeInputRec()
 {
-       const AudioBuffer& virtualInput = mixer::getVirtualInput();
-
-       /* Can't loop with foreach, as it would require a lock on model::channels
-       list which would deadlock during the model::channels::swap() call below. 
-       Also skip channels 0, 1 and 2: they are MASTER_IN, MASTER_OUT and PREVIEW. */
-
-       for (size_t i = 3; i < model::channels.size(); i++) {
-
-               if (!canInputRec_(i))
-                       continue;
+       for (ID id : getRecordableChannels_()) {
 
                /* Create a new Wave with audio coming from Mixer's virtual input. */
 
@@ -542,22 +424,21 @@ void finalizeInputRec()
                std::unique_ptr<Wave> wave = waveManager::createEmpty(clock::getFramesInLoop(), 
                        G_MAX_IO_CHANS, conf::conf.samplerate, filename);
 
-               wave->copyData(virtualInput[0], virtualInput.countFrames());
+               wave->copyData(mixer::getRecBuffer());
 
                /* Update Channel with the new Wave. The function pushWave_ will take
-               take of pushing it into the stack first. Also start all channels in
+               care of pushing it into the stack first. Also start all channels in
                LOOP mode. */
 
-               model::onSwap(model::channels, model::getId(model::channels, i), [&](Channel& c)
+               model::onSwap(model::channels, id, [&](Channel& c)
                {
-                       SampleChannel& sc = static_cast<SampleChannel&>(c);
-                       pushWave_(sc, std::move(wave), /*clone=*/false);
-                       if (sc.isAnyLoopMode())
-                               sc.playStatus = ChannelStatus::PLAY;
-               });
+                       pushWave_(c, std::move(wave));
+                       if (c.samplePlayer->state->isAnyLoopMode())
+                               c.samplePlayer->kickIn(clock::getCurrentFrame());
+               });                     
        }
 
-       mixer::clearVirtualInput();
+       mixer::clearRecBuffer();
 }
 
 
@@ -566,30 +447,39 @@ void finalizeInputRec()
 
 bool hasRecordableSampleChannels()
 {
-       return channelHas_([](const Channel* ch) { return ch->canInputRec(); });
+       return anyChannel_([](const Channel* ch) { return ch->canInputRec(); });
 }
 
 
 bool hasLogicalSamples()
 {
-       return channelHas_([](const Channel* ch) { return ch->hasLogicalData(); });
+       return anyChannel_([](const Channel* ch) 
+       { 
+               return ch->samplePlayer && ch->samplePlayer->hasLogicalWave(); }
+       );
 }
 
 
 bool hasEditedSamples()
 {
-       return channelHas_([](const Channel* ch) { return ch->hasEditedData(); });
+       return anyChannel_([](const Channel* ch) 
+       { 
+               return ch->samplePlayer && ch->samplePlayer->hasEditedWave(); 
+       });
 }
 
 
 bool hasActions()
 {
-       return channelHas_([](const Channel* ch) { return ch->hasActions; });
+       return anyChannel_([](const Channel* ch) { return ch->state->hasActions; });
 }
 
 
 bool hasAudioData()
 {
-       return channelHas_([](const Channel* ch) { return ch->hasData(); });
+       return anyChannel_([](const Channel* ch)
+       { 
+               return ch->samplePlayer && ch->samplePlayer->hasWave(); 
+       });
 }
 }}}; // giada::m::mh::
index bbf84a9d6b3e83236f7a1339a73155e724112cc8..659877252d33580c597a3a11b3d6171ee34b9c0e 100644 (file)
@@ -57,7 +57,7 @@ void close();
 Adds a new channel of type 'type' into the channels stack. Returns the new
 channel ID. */
 
-ID addChannel(ChannelType type, ID columnId);
+void addChannel(ChannelType type, ID columnId);
 
 /* loadChannel
 Loads a new Wave inside a Sample Channel. */
@@ -88,15 +88,7 @@ void cloneChannel(ID channelId);
 void renameChannel(ID channelId, const std::string& name);
 void freeAllChannels();
 
-void startSequencer();
-void stopSequencer();
-void toggleSequencer();
-void rewindSequencer();
-void rewindChannels();
-
 void setInToOut(bool v);
-void setInVol(float f);
-void setOutVol(float f);
 
 /* updateSoloCount
 Updates the number of solo-ed channels in mixer. */
@@ -109,12 +101,6 @@ session. */
 
 void finalizeInputRec();
 
-/* uniqueSamplePath
-Returns true if path 'p' is unique. Requires SampleChannel 'skip' in order
-to skip check against itself. */
-
-bool uniqueSamplePath(ID channelToSkip, const std::string& p);
-
 /* hasLogicalSamples
 True if 1 or more samples are logical (memory only, such as takes) */
 
index 2fff2e6c016ad24c4e6c937fe6da181510afb647..3de8e8c82a2383c7374a3bb1e9d52980cc1f3696 100644 (file)
@@ -27,9 +27,7 @@
 
 #include <cassert>
 #include "core/model/model.h"
-#ifndef NDEBUG
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
+#ifdef G_DEBUG_MODE
 #include "core/channels/channelManager.h"
 #endif
 
@@ -44,7 +42,7 @@ RCUList<Kernel>   kernel(std::make_unique<Kernel>());
 RCUList<Recorder> recorder(std::make_unique<Recorder>());
 RCUList<MidiIn>   midiIn(std::make_unique<MidiIn>());
 RCUList<Actions>  actions(std::make_unique<Actions>());
-RCUList<Channel>  channels;
+RCUList<Channel> channels;
 RCUList<Wave>     waves;
 #ifdef WITH_VST
 RCUList<Plugin>   plugins;
@@ -60,7 +58,7 @@ Actions::Actions(const Actions& o) : map(o.map)
 }
 
 
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
 
 void debug()
 {
@@ -78,14 +76,17 @@ void debug()
 
        int i = 0;
        for (const Channel* c : channels) {
-               printf("    %d) %p - ID=%d name='%s' columnID=%d\n", i++, (void*)c, c->id, c->name.c_str(), c->columnId);
+               printf("\t%d) %p - ID=%d name='%s' type=%d columnID=%d\n",
+                       i++, (void*) c, c->state->id, c->state->name.c_str(), (int) c->getType(), c->getColumnId());
+/*
                if (c->hasData())
-                       printf("        wave: ID=%d\n", static_cast<const SampleChannel*>(c)->waveId);
+                       printf("\t\twave: ID=%d\n", static_cast<const SampleChannel*>(c)->waveId);
+*/
 #ifdef WITH_VST
                if (c->pluginIds.size() > 0) {
-                       puts("        plugins:");
+                       puts("\t\tplugins:");
                        for (ID id : c->pluginIds)
-                               printf("            ID=%d\n", id);
+                               printf("\t\t\tID=%d\n", id);
                }
 #endif
        }
@@ -94,7 +95,7 @@ void debug()
 
        i = 0;
        for (const Wave* w : waves) 
-               printf("    %d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str());
+               printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str());
                
 #ifdef WITH_VST
        puts("model::plugins");
@@ -102,31 +103,31 @@ void debug()
        i = 0;
        for (const Plugin* p : plugins) {
                if (p->valid)
-                       printf("    %d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str());
+                       printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str());
                else
-                       printf("    %d) %p - ID=%d INVALID\n", i++, (void*)p, p->id); 
+                       printf("\t%d) %p - ID=%d INVALID\n", i++, (void*)p, p->id); 
        }
 #endif
 
        puts("model::clock");
 
-       printf("    clock.status   = %d\n", static_cast<int>(clock.get()->status));
-       printf("    clock.bars     = %d\n", clock.get()->bars);
-       printf("    clock.beats    = %d\n", clock.get()->beats);
-       printf("    clock.bpm      = %f\n", clock.get()->bpm);
-       printf("    clock.quantize = %d\n", clock.get()->quantize);
+       printf("\tclock.status   = %d\n", static_cast<int>(clock.get()->status));
+       printf("\tclock.bars     = %d\n", clock.get()->bars);
+       printf("\tclock.beats    = %d\n", clock.get()->beats);
+       printf("\tclock.bpm      = %f\n", clock.get()->bpm);
+       printf("\tclock.quantize = %d\n", clock.get()->quantize);
 
        puts("model::actions");
 
        for (auto& kv : actions.get()->map) {
-               printf("    frame: %d\n", kv.first);
+               printf("\tframe: %d\n", kv.first);
                for (const Action& a : kv.second)
-                       printf("        (%p) - ID=%d, frame=%d, channel=%d, value=0x%X, prevId=%d, prev=%p, nextId=%d, next=%p\n", 
+                       printf("\t\t(%p) - ID=%d, frame=%d, channel=%d, value=0x%X, prevId=%d, prev=%p, nextId=%d, next=%p\n", 
                                (void*) &a, a.id, a.frame, a.channelId, a.event.getRaw(), a.prevId, (void*) a.prev, a.nextId, (void*) a.next);  
        }
        
        puts("===============================");
 }
 
-#endif
+#endif // G_DEBUG_MODE
 }}} // giada::m::model::
index 15dd75f9b2ee32c7c45e55bfaf87799ee2fb9857..ace0ce5d898b8bc12b3bcf6530113d9de4ea1c7b 100644 (file)
@@ -30,8 +30,9 @@
 
 
 #include <algorithm>
-#include <type_traits>
+#include "core/model/traits.h"
 #include "core/channels/channel.h"
+#include "core/channels/state.h"
 #include "core/const.h"
 #include "core/wave.h"
 #include "core/plugin.h"
@@ -43,6 +44,44 @@ namespace giada {
 namespace m {
 namespace model
 {
+namespace
+{
+/* getIter_
+Returns an iterator of an element from list 'list' with given ID. */
+
+template<typename L>
+auto getIter_(L& list, ID id)
+{
+       static_assert(has_id<typename L::value_type>(), "This type has no ID");
+       auto it = std::find_if(list.begin(), list.end(), [&](auto* t)
+       {
+               return t->id == id;
+       });
+       assert(it != list.end());
+       return it;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* onSwapByIndex_
+Swaps i-th element from list with a new one and applies a function f to it. */
+
+template<typename L>
+void onSwapByIndex_(L& list, std::size_t i, std::function<void(typename L::value_type&)> f)
+{
+       std::unique_ptr<typename L::value_type> o = list.clone(i);
+       f(*o.get());
+       list.swap(std::move(o), i);
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
 struct Clock
 {      
        ClockStatus status       = ClockStatus::STOPPED;
@@ -127,53 +166,45 @@ extern RCUList<Plugin>   plugins;
 #endif
 
 
-/* ---------------------------------------------------------------------------*/ 
-
-
-template <typename T> struct has_id : std::false_type {};
-template <> struct has_id<Channel>  : std::true_type {};
-template <> struct has_id<Wave>     : std::true_type {};
-#ifdef WITH_VST
-template <> struct has_id<Plugin>   : std::true_type {};
-#endif
-
-template <typename T> struct is_copyable : std::true_type {};
-template <> struct is_copyable<Channel>  : std::false_type {};
-
-
 /* -------------------------------------------------------------------------- */
 
 
 template<typename L>
-auto getIter(L& list, ID id)
+bool exists(L& list, ID id)
 {
-       static_assert(has_id<typename L::value_type>(), "This type has no ID");
+       static_assert(has_id<typename L::value_type>(), "This type has no ID"); 
+       typename L::Lock l(list);
        auto it = std::find_if(list.begin(), list.end(), [&](auto* t)
        {
                return t->id == id;
        });
-       assert(it != list.end());
-       return it;
+       return it != list.end();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
+/* getIndex (thread safe)
+Returns the index of element with ID from a list. */
+
 template<typename L>
-size_t getIndex(L& list, ID id)
+std::size_t getIndex(L& list, ID id)
 {
        static_assert(has_id<typename L::value_type>(), "This type has no ID");
        typename L::Lock l(list);
-       return std::distance(list.begin(), getIter(list, id));
+       return std::distance(list.begin(), getIter_(list, id));
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
+/* getIndex (thread safe)
+Returns the element ID of the i-th element of a list. */
+
 template<typename L>
-ID getId(L& list, size_t i)
+ID getId(L& list, std::size_t i)
 {
        static_assert(has_id<typename L::value_type>(), "This type has no ID");
        typename L::Lock l(list);
@@ -188,26 +219,28 @@ template<typename L>
 typename L::value_type& get(L& list, ID id)
 {
        static_assert(has_id<typename L::value_type>(), "This type has no ID");
-       return **getIter(list, id);
+       return **getIter_(list, id);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-/* onGet (1)
+/* onGet (1) (thread safe)
 Utility function for reading ID-based things from a RCUList. */
 
 template<typename L>
-void onGet(L& list, ID id, std::function<void(typename L::value_type&)> f)
+void onGet(L& list, ID id, std::function<void(typename L::value_type&)> f, bool rebuild=false)
 {
        static_assert(has_id<typename L::value_type>(), "This type has no ID");
        typename L::Lock l(list);
-       f(**getIter(list, id));
+       f(**getIter_(list, id));
+       if (rebuild)
+               list.changed.store(true);
 }
 
 
-/* onGet (2)
+/* onGet (2) (thread safe)
 Same as (1), for non-ID-based things. */
 
 template<typename L>
@@ -222,60 +255,18 @@ void onGet(L& list, std::function<void(typename L::value_type&)> f)
 /* ---------------------------------------------------------------------------*/ 
 
 
-template<typename L>
-void onSwapByIndex_(L& list, size_t i, std::function<void(typename L::value_type&)> f)
-{
-       std::unique_ptr<typename L::value_type> o = list.clone(i);
-       f(*o.get());
-       list.swap(std::move(o), i);
-}
-
-/* onSwapById_ (1)
-Regular version for copyable types. */
-
-template<typename L>
-void onSwapById_(L& list, ID id, std::function<void(typename L::value_type&)> f, 
-       const std::true_type& /*is_copyable=true*/)
-{
-       static_assert(has_id<typename L::value_type>(), "This type has no ID");
-       onSwapByIndex_(list, getIndex(list, id), f); 
-}
-
-
-/* onSwapById_ (2)
-Custom version for non-copyable types, e.g. Channel types. Let's wait for the
-no-virtual channel refactoring... */
-
-template<typename L>
-void onSwapById_(L& list, ID id, std::function<void(typename L::value_type&)> f,
-       const std::false_type& /*is_copyable=false*/)
-{      
-       static_assert(has_id<typename L::value_type>(), "This type has no ID");
-       
-       size_t i = getIndex(list, id);
-       
-       list.lock();
-       std::unique_ptr<typename L::value_type> o(list.get(i)->clone());
-       list.unlock();
-
-       f(*o.get());
-
-       channels.swap(std::move(o), i);
-}
-
-
-/* onSwap (1)
-Utility function for swapping things in a RCUList. */
+/* onSwap (1) (thread safe)
+Utility function for swapping ID-based things in a RCUList. */
 
 template<typename L>
 void onSwap(L& list, ID id, std::function<void(typename L::value_type&)> f)
 {
        static_assert(has_id<typename L::value_type>(), "This type has no ID");
-       onSwapById_(list, id, f, is_copyable<typename L::value_type>());
+       onSwapByIndex_(list, getIndex(list, id), f); 
 }
 
 
-/* onSwap (2)
+/* onSwap (2) (thread safe)
 Utility function for swapping things in a RCUList when the list contains only
 a single element (and so with no ID). */
 
@@ -287,10 +278,10 @@ void onSwap(L& list, std::function<void(typename L::value_type&)> f)
 }
 
 
-/* ---------------------------------------------------------------------------*/ 
+/* ---------------------------------------------------------------------------*/
 
 
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
 
 void debug();
 
index dbbda735ca2ac96c3186be44956e48a418ebf340..3fc6d39fa258b5245bf78c82206c9b464ecc6019 100644 (file)
 #include <cassert>
 #include "core/model/model.h"
 #include "core/channels/channelManager.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/kernelAudio.h"
 #include "core/patch.h"
 #include "core/conf.h"
 #include "core/pluginManager.h"
 #include "core/recorderHandler.h"
 #include "core/waveManager.h"
+#include "core/sequencer.h"
 #include "core/model/storage.h"
 
 
@@ -43,6 +42,16 @@ namespace giada {
 namespace m {
 namespace model
 {
+namespace
+{
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
 void store(patch::Patch& patch)
 {
 #ifdef WITH_VST
@@ -57,7 +66,7 @@ void store(patch::Patch& patch)
        patch.beats      = clock.get()->beats;
        patch.bpm        = clock.get()->bpm;
        patch.quantize   = clock.get()->quantize;
-       patch.metronome  = mixer::isMetronomeOn();  // TODO - not here
+       patch.metronome  = sequencer::isMetronomeOn();  // TODO - not here
 
 #ifdef WITH_VST
        for (const Plugin* p : plugins) 
@@ -113,20 +122,35 @@ void load(const patch::Patch& patch)
        {
                a.map = std::move(recorderHandler::deserializeActions(patch.actions));
        });
+
 #ifdef WITH_VST
     for (const patch::Plugin& pplugin : patch.plugins)
         plugins.push(pluginManager::deserializePlugin(pplugin));
 #endif
     
-    for (const patch::Wave& pwave : patch.waves)
-        waves.push(std::move(waveManager::deserializeWave(pwave)));    
-
-    for (const patch::Channel& pchannel : patch.channels) {
-               if (pchannel.type == ChannelType::MASTER || pchannel.type == ChannelType::PREVIEW)
-            onSwap(channels, pchannel.id, [&](Channel& ch) { ch.load(pchannel); });
+       for (const patch::Wave& pwave : patch.waves) {
+               std::unique_ptr<Wave> w = waveManager::deserializeWave(pwave);
+               if (w != nullptr)
+                       waves.push(std::move(w));       
+       }
+
+       channels.clear();
+    for (const patch::Channel& pchannel : patch.channels)
+               channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize()));
+       
+       /* Load Waves into Channels. */
+
+       ChannelsLock cl(channels);
+       WavesLock    wl(waves);
+       
+       for (Channel* c : channels) {
+               if (!c->samplePlayer)
+                       continue;
+               if (exists(waves, c->samplePlayer->getWaveId()))
+                       c->samplePlayer->setWave(get(waves, c->samplePlayer->getWaveId()));
                else
-                       channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize()));
-    }
+                       c->samplePlayer->setInvalidWave();
+       }
 }
 
 
diff --git a/src/core/model/traits.h b/src/core/model/traits.h
new file mode 100644 (file)
index 0000000..d5cccef
--- /dev/null
@@ -0,0 +1,51 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_MODEL_TRAITS_H
+#define G_MODEL_TRAITS_H
+
+
+#include <type_traits>
+#include "core/wave.h"
+#include "core/plugin.h"
+#include "core/channels/channel.h"
+
+
+namespace giada {
+namespace m {
+namespace model
+{
+template <typename T> struct has_id : std::false_type {};
+template <> struct has_id<Channel>  : std::true_type {};
+template <> struct has_id<Wave>     : std::true_type {};
+#ifdef WITH_VST
+template <> struct has_id<Plugin>   : std::true_type {};
+#endif
+}}} // giada::m::model::
+
+
+#endif
index 671d7cf540a7ef210a99af7e36d13d46d077a913..84ad90e985f2eca19c2a2b9a5c07776834d3ab03 100644 (file)
@@ -149,11 +149,11 @@ void readChannels_(const nl::json& j)
        if (!j.contains(PATCH_KEY_CHANNELS))    
                return;
 
-       ID id = mixer::PREVIEW_CHANNEL_ID;
+       ID defaultId = mixer::PREVIEW_CHANNEL_ID;
 
        for (const auto& jchannel : j[PATCH_KEY_CHANNELS]) {
                Channel c;
-               c.id                = jchannel.value(PATCH_KEY_CHANNEL_ID, ++id);
+               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);          
                c.height            = jchannel.value(PATCH_KEY_CHANNEL_SIZE, G_GUI_UNIT);
@@ -178,7 +178,7 @@ void readChannels_(const nl::json& j)
                c.midiOutLmute      = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, 0);
                c.midiOutLsolo      = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, 0);
                c.armed             = jchannel.value(PATCH_KEY_CHANNEL_ARMED, false);
-               c.mode              = static_cast<ChannelMode>(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1));
+               c.mode              = static_cast<SamplePlayerMode>(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1));
                c.waveId            = jchannel.value(PATCH_KEY_CHANNEL_WAVE_ID, 0);
                c.begin             = jchannel.value(PATCH_KEY_CHANNEL_BEGIN, 0);
                c.end               = jchannel.value(PATCH_KEY_CHANNEL_END, 0);
@@ -366,6 +366,32 @@ void writeChannels_(nl::json& j)
                j[PATCH_KEY_CHANNELS].push_back(jchannel);
        }
 }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void modernize_()
+{
+       /* 0.16.3 
+       - Make sure that ChannelType is correct: ID 1, 2 are MASTER channels, 
+         ID 3 is PREVIEW channel;
+       - set panning to default (0.5) and waveId to 0 for non-Sample Channels. */
+
+       for (Channel& c : patch.channels) {
+
+               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)
+                       c.type = ChannelType::PREVIEW;
+               
+               if (c.type != ChannelType::SAMPLE) {
+                       c.pan    = G_DEFAULT_PAN;
+                       c.waveId = 0;
+               }
+       }       
+}
 }; // {anonymous}
 
 
@@ -460,6 +486,7 @@ int read(const std::string& file, const std::string& basePath)
                readWaves_(j, basePath);
                readActions_(j);
                readChannels_(j);
+               modernize_();
        }
        catch (nl::json::exception& e) {
                u::log::print("[patch::read] Exception thrown: %s\n", e.what());
index 57ab057899e7a06996542e466dcbe19b80c5478c..7e0fa13ce5a76f19df2b271ef1082a7939d71f22 100644 (file)
@@ -69,7 +69,7 @@ struct Channel
        bool        mute;
        bool        solo;
        float       volume = G_DEFAULT_VOL;
-       float       pan    = 0.5f;
+       float       pan    = G_DEFAULT_PAN;
        bool        hasActions;
        bool        armed;
        bool        midiIn;
@@ -86,17 +86,17 @@ struct Channel
        uint32_t    midiOutLmute;
        uint32_t    midiOutLsolo;
        // sample channel
-       ID          waveId;
-       ChannelMode mode;
-       Frame       begin;
-       Frame       end;
-       Frame       shift;
-       bool        readActions;
-       float       pitch = G_DEFAULT_PITCH;
-       bool        inputMonitor;
-       bool        midiInVeloAsVol;
-       uint32_t    midiInReadActions;
-       uint32_t    midiInPitch;
+       ID               waveId = 0;
+       SamplePlayerMode mode;
+       Frame            begin;
+       Frame            end;
+       Frame            shift;
+       bool             readActions;
+       float            pitch = G_DEFAULT_PITCH;
+       bool             inputMonitor;
+       bool             midiInVeloAsVol;
+       uint32_t         midiInReadActions;
+       uint32_t         midiInPitch;
        // midi channel
        bool        midiOut;
        int         midiOutChan;
index 32644b486a98c5ce477d5fc68accab0c0915c2b3..458eee8e47150fafd58811139fc1777d44851d95 100644 (file)
@@ -148,7 +148,6 @@ void processStack(AudioBuffer& outBuf, const std::vector<ID>& pluginIds,
        else {
                audioBuffer_.clear();
                processPlugins_(pluginIds, *events);
-
        }
        juceToGiadaOutBuf_(outBuf);
 }
@@ -209,11 +208,12 @@ void freePlugins(const std::vector<ID>& pluginIds)
 /* -------------------------------------------------------------------------- */
 
 
-void clonePlugins(const Channel& oldChannel, Channel& newChannel)
+std::vector<ID> clonePlugins(std::vector<ID> pluginIds)
 {
-       newChannel.pluginIds.clear();
-       for (ID id : oldChannel.pluginIds)
-               newChannel.pluginIds.push_back(clonePlugin_(id));
+       std::vector<ID> out;
+       for (ID id : pluginIds)
+               out.push_back(clonePlugin_(id));
+       return out;
 }
 
 
index c3625624f993c0ecf47ecf8e333aa3a731c0766a..b8683cc6410261d7ff52f8afd24b9a3d94e560c2 100644 (file)
@@ -41,9 +41,7 @@ namespace giada {
 namespace m 
 {
 class Plugin;
-class Channel;
 class AudioBuffer;
-
 namespace pluginHost
 {
 using Stack = std::vector<std::shared_ptr<Plugin>>;
@@ -78,9 +76,10 @@ Unloads multiple plugins. Useful when freeing or deleting a channel. */
 void freePlugins(const std::vector<ID>& pluginIds);
 
 /* clonePlugins
-Clones all the plug-ins from the current channel to the new one. */
+Clones all the plug-ins from 'pluginIds' vector coming from the old channel
+and returns new IDs. */
 
-void clonePlugins(const Channel& oldChannel, Channel& newChannel);
+std::vector<ID> clonePlugins(std::vector<ID> pluginIds);
 
 void setPluginParameter(ID pluginId, int paramIndex, float value);
 
diff --git a/src/core/quantizer.cpp b/src/core/quantizer.cpp
new file mode 100644 (file)
index 0000000..7be2fa1
--- /dev/null
@@ -0,0 +1,97 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/clock.h"
+#include "quantizer.h"
+
+
+namespace giada {
+namespace m
+{
+void Quantizer::trigger(int id)
+{
+       assert(id >= 0);
+       assert(id < (int) m_callbacks.size());
+
+       m_performId = id;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::schedule(int id, std::function<void(Frame delta)> f)
+{
+       assert(id >= 0);
+       assert(id < (int) m_callbacks.size());
+
+       m_callbacks[id] = f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::advance(Range<Frame> block, Frame quantizerStep)
+{
+       /* Nothing to do if there's no action to perform. */
+
+       if (m_performId == -1)
+               return;
+
+       assert(m_callbacks[m_performId] != nullptr);
+
+       for (Frame global = block.getBegin(), local = 0; global < block.getEnd(); global++, local++) {
+
+               if (global % quantizerStep != 0) // Skip if it's not on a quantization unit. 
+                       continue;
+
+               m_callbacks[m_performId](local);
+               m_performId = -1;
+               return;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::clear()
+{
+       m_performId = -1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool Quantizer::isTriggered() const
+{
+       return m_performId != -1;
+}
+}} // giada::m::
\ No newline at end of file
diff --git a/src/core/quantizer.h b/src/core/quantizer.h
new file mode 100644 (file)
index 0000000..e9973b7
--- /dev/null
@@ -0,0 +1,83 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_QUANTIZER_H
+#define G_QUANTIZER_H
+
+
+#include <array>
+#include <functional>
+#include "core/const.h"
+#include "core/range.h"
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+class Quantizer
+{
+public:
+
+       /* schedule
+       Schedules a function in slot 'id' to be called at the right time. The 
+       function has a 'delta' parameter for the buffer offset. */
+
+       void schedule(int id, std::function<void(Frame)>);
+
+       /* trigger
+       Triggers the function in slot 'id'. Might start right away, or at the end 
+       of the quantization step. */
+
+       void trigger(int id);
+
+       /* advance
+       Computes the internal state. Wants a range of frames [currentFrame, 
+       currentFrame + bufferSize) and a quantization step. Call this function
+       on each block. */
+
+       void advance(Range<Frame> block, Frame quantizerStep);
+
+       /* clear
+       Disables quantized operations in progress, if any. */
+
+       void clear();
+
+       /* isTriggered
+       True if a quantizer function has been triggered(). */
+       
+       bool isTriggered() const;
+
+private:
+
+       std::array<std::function<void(Frame)>, G_MAX_QUANTIZER_SIZE> m_callbacks;
+       int m_performId = -1;
+};
+}} // giada::m::
+
+
+#endif
index 72c10eb2d1aaf9c165e0f62f3f1d4d617f877b2e..6edf1c55bc672fadd380657b924d04a24e7de3b5 100644 (file)
@@ -39,7 +39,7 @@ namespace m
 /* Queue
 Single producer, single consumer lock-free queue. */
 
-template<typename T, size_t size>
+template<typename T, std::size_t size>
 class Queue
 {
 public:
@@ -54,7 +54,7 @@ public:
 
     bool pop(T& item)
     {
-        size_t curr = m_head.load();
+        std::size_t curr = m_head.load();
         if (curr == m_tail.load())  // Queue empty, nothing to do
             return false;
 
@@ -66,8 +66,8 @@ public:
 
     bool push(const T& item)
     {
-        size_t curr = m_tail.load();
-        size_t next = increment(curr);
+        std::size_t curr = m_tail.load();
+        std::size_t next = increment(curr);
 
         if (next == m_head.load()) // Queue full, nothing to do
             return false;
@@ -79,15 +79,15 @@ public:
 
 private:
 
-    size_t increment(size_t i) const
+    std::size_t increment(std::size_t i) const
     {
         return (i + 1) % size;
     }
 
 
     std::array<T, size> m_data;
-    std::atomic<size_t> m_head;
-    std::atomic<size_t> m_tail;
+    std::atomic<std::size_t> m_head;
+    std::atomic<std::size_t> m_tail;
 };
 }} // giada::m::
 
index ae48fb3932b76389115ce681c49e33c24ca813b3..96e91066f4f560c253bca4199b7d547cc8f32f43 100644 (file)
@@ -37,11 +37,6 @@ namespace giada
 template<typename T>
 class Range
 {
-private:
-
-       T m_a;
-       T m_b;
-
 public:
 
        Range() : m_a(0), m_b(0) {}
@@ -50,6 +45,11 @@ public:
        T getBegin() const  { return m_a; }
        T getEnd() const    { return m_b; }
        T getLength() const { return m_b - m_a; }
+
+private:
+
+       T m_a;
+       T m_b;
 };
 } // giada::
 
index c5be2999f9c65306640ee0e647efd862df1514fd..00504890f96c3de6a1432414aba82d2c15052f25 100644 (file)
 namespace giada {
 namespace m
 {
+/* RCUList
+Single producer, multiple consumer (i.e. one writer, many readers) RCU-based 
+list. */
 template<typename T>
 class RCUList
 {
 public:
 
        /* Lock
-       Scoped lock structure. */
+       Scoped lock structure. Not copyable, not moveable, not copy-constructible. 
+       Same as std::scoped_lock: 
+       https://en.cppreference.com/w/cpp/thread/scoped_lock .*/
 
        struct Lock
        {
-               Lock(RCUList<T>& r) : rcu(r) 
-               { 
-                       rcu.lock(); 
-               }
-
-               ~Lock() 
-               { 
-                       rcu.unlock();
-               }
+               Lock(RCUList<T>& r) : rcu(r) { rcu.lock(); }
+               Lock(const Lock&) = delete;
+               Lock& operator=(const Lock&) = delete;
+               ~Lock() { rcu.unlock(); }
 
                RCUList<T>& rcu;
        };
@@ -176,14 +177,13 @@ public:
        void unlock()
        {
                m_readers[t_grace]--;
+               assert(m_readers[t_grace] >= 0 && "Negative reader");
        }
 
        /* get
        Returns a reference to the data held by node 'i'. */
-       // TODO - this will return a const ref with the non-virtual Channel
-       // refactoring. 
 
-       T* get(size_t i=0) const
+       T* get(std::size_t i=0) const
        {
                assert(i < size() && "Index overflow");
                assert(m_readers[t_grace].load() > 0 && "Forgot lock before reading");
@@ -192,18 +192,14 @@ public:
 
        /* Subscript operator []
        Same as above for the [] syntax. */
-       // TODO - this will return a const ref with the non-virtual Channel
-       // refactoring. 
 
-       T* operator[] (size_t i) const
+       T* operator[] (std::size_t i) const
        {
        return get(i);
     }
 
        /* back
        Return data held by the last node. */
-       // TODO - this will return a const ref with the non-virtual Channel
-       // refactoring. 
 
        T* back() const
        {
@@ -212,19 +208,13 @@ public:
        }
 
        /* clone
-       Returns a new copy of the data held by node 'i'. The template machinery
-       is required for when you declare a RCUList<Base> and later on want to clone
-       a derived object. Usage:
-       
-       RCUList<Base> list;
-       ...
-       std::unique_ptr<Derived> d = list.clone<Derived>(i);
-       */
+       Returns a new copy of the data held by node 'i'. */
 
-       template<typename C=T>
-       std::unique_ptr<C> clone(size_t i=0) const
+       std::unique_ptr<T> clone(std::size_t i=0) const
     {
-               return std::make_unique<C>(*static_cast<C*>(getNode(i)->data.get()));
+               /* Make sure no one is writing (swapping, popping, pushing). */
+               assert(m_writing.load() == false);
+               return std::make_unique<T>(*getNode(i)->data.get());
     }
 
        /* swap
@@ -233,7 +223,7 @@ public:
        multiple calls to swap() made by the same thread: the caller is blocked by 
        the spinlock below: no progress is made until m_readers[oldgrace] > 0. */
 
-       void swap(std::unique_ptr<T> data, size_t i=0)
+       void swap(std::unique_ptr<T> data, std::size_t i=0)
        {
                /* Never start two overlapping writing sessions. */
 
@@ -339,7 +329,7 @@ public:
        calls to pop() made by the same thread: the caller is blocked by the
        spinlock below: no progress is made while m_readers[oldgrace] > 0. */
 
-       void pop(size_t i)
+       void pop(std::size_t i)
        {
                /* Never start two overlapping writing sessions. */
 
@@ -449,7 +439,7 @@ public:
        /* size
        Returns the number of nodes in the list. */
 
-       size_t size() const
+       std::size_t size() const
        {
                return m_size.load();
        }
@@ -467,9 +457,9 @@ public:
 
 private:
 
-       Node* getNode(size_t i) const
+       Node* getNode(std::size_t i) const
        {
-               size_t p    = 0;
+               std::size_t p    = 0;
                Node*  curr = m_head.load();
                
                while (curr != nullptr && p < i) {
@@ -482,7 +472,7 @@ private:
 
        std::array<std::atomic<int>, 2> m_readers;
        std::atomic<std::int8_t>        m_grace;
-       std::atomic<size_t>             m_size;
+       std::atomic<std::size_t>             m_size;
        std::atomic<bool>               m_writing;
 
        /* m_head
index 29865b45adb3287d1ede90dfa1e2baf1f493b127..a66f7cfe8aa57be75df50228a386f1462e730123 100644 (file)
 
 
 #include "gui/dispatcher.h"
-#include "core/channels/channel.h"
 #include "core/model/model.h"
 #include "core/types.h"
 #include "core/clock.h"
 #include "core/kernelAudio.h"
 #include "core/conf.h"
 #include "core/mixer.h"
+#include "core/sequencer.h"
 #include "core/mixerHandler.h"
 #include "core/midiDispatcher.h"
 #include "core/recorder.h"
@@ -72,7 +72,7 @@ bool startActionRec_()
        if (!kernelAudio::isReady())
                return false;
        clock::setStatus(ClockStatus::RUNNING);
-       m::mh::startSequencer();
+       sequencer::start();
        return true;
 }
 
@@ -85,7 +85,7 @@ bool startInputRec_()
        if (!kernelAudio::isReady() || !mh::hasRecordableSampleChannels())
                return false;
        mixer::startInputRec();
-       mh::startSequencer();
+       sequencer::start();
        return true;
 }
 } // {anonymous}
@@ -147,7 +147,7 @@ void stopActionRec()
 
        if (clock::getStatus() == ClockStatus::WAITING) {
                clock::setStatus(ClockStatus::STOPPED);
-               m::midiDispatcher::setSignalCallback(nullptr);
+               midiDispatcher::setSignalCallback(nullptr);
                v::dispatcher::setSignalCallback(nullptr);
                return;
        }
@@ -158,11 +158,12 @@ void stopActionRec()
        actions. Start reading right away, without checking whether 
        conf::treatRecsAsLoops is enabled or not. */
 
-       for (ID id : channels)
-               m::model::onGet(m::model::channels, id, [](Channel& c)
+       for (ID id : channels) {
+               model::onGet(model::channels, id, [](Channel& c)
                {
-                       c.startReadingActions(/*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+                       c.state->readActions.store(true);
                });
+       }
 }
 
 
index 92855d122b10f726fc0f892ba4c732c3a4cb17ad..bbb205e983e7ace05a7e534375d644f01e2ee019 100644 (file)
@@ -30,7 +30,6 @@
 #include <cassert>
 #include "utils/log.h"
 #include "core/model/model.h"
-#include "core/channels/channel.h"
 #include "core/action.h"
 #include "core/idManager.h"
 #include "core/recorder.h"
@@ -50,8 +49,8 @@ IdManager actionId_;
 
 Action* findAction_(ActionMap& src, ID id)
 {
-       for (auto& kv : src)
-               for (Action& a : kv.second)
+       for (auto& [frame, actions] : src)
+               for (Action& a : actions)
                        if (a.id == id)
                                return &a;
        assert(false);
@@ -78,14 +77,31 @@ void removeIf_(std::function<bool(const Action&)> f)
 {
        model::onSwap(model::actions, [&](model::Actions& a)
        {
-               for (auto& kv : a.map) {
-                       std::vector<Action>& as = kv.second;
-                       as.erase(std::remove_if(as.begin(), as.end(), f), as.end());
-               }
+               for (auto& [frame, actions] : a.map)
+                       actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
                optimize_(a.map);
                updateMapPointers(a.map);
        });
 }
+
+/* -------------------------------------------------------------------------- */
+
+
+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)
+{
+       model::ActionsLock lock(model::actions);
+       return exists_(channelId, frame, event, model::actions.get()->map);
+}
 } // {anonymous}
 
 
@@ -156,7 +172,8 @@ void updateKeyFrames(std::function<Frame(Frame old)> f)
 {
        std::unique_ptr<model::Actions> ma = model::actions.clone();
        
-       /* Remove all existing actions: let's start from scratch. */
+       /* Remove all existing actions: let's start from scratch. 
+       TODO - so why cloning it?! */
 
        ma->map.clear();
 
@@ -166,14 +183,14 @@ void updateKeyFrames(std::function<Frame(Frame old)> f)
        {
                model::ActionsLock lock(model::actions);
 
-               for (const auto& kv : model::actions.get()->map) {
-                       Frame frame = f(kv.first);
-                       for (const Action& a : kv.second) {
+               for (const auto& [oldFrame, actions] : model::actions.get()->map) {
+                       Frame newFrame = f(oldFrame);
+                       for (const Action& a : actions) {
                                Action copy = a;
-                               copy.frame = frame;
-                               ma->map[frame].push_back(copy);
+                               copy.frame = newFrame;
+                               ma->map[newFrame].push_back(copy);
                        }
-                       u::log::print("[recorder::updateKeyFrames] %d -> %d\n", kv.first, frame);
+G_DEBUG(oldFrame << " -> " << newFrame);
                }
        }
 
@@ -230,8 +247,8 @@ bool hasActions(ID channelId, int type)
 {
        model::ActionsLock lock(model::actions);
        
-       for (const auto& kv : model::actions.get()->map)
-               for (const Action& a : kv.second)
+       for (const auto& [frame, actions] : model::actions.get()->map)
+               for (const Action& a : actions)
                        if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
                                return true;
        return false;
@@ -262,6 +279,11 @@ Action makeAction(const patch::Action& a)
 
 Action 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 
@@ -280,26 +302,16 @@ Action rec(ID channelId, Frame frame, MidiEvent event)
 /* -------------------------------------------------------------------------- */
 
 
-void rec(std::vector<Action>& as)
+void rec(std::vector<Action>& actions)
 {
-       if (as.size() == 0)
+       if (actions.size() == 0)
                return;
 
-       /* Generate new action ID and fix next and prev IDs. */
-
-       for (Action& a : as) {
-               int id = a.id;
-               a.id = actionId_.get();
-               for (Action& aa : as) {
-                       if (aa.prevId == id) aa.prevId = a.id;
-                       if (aa.nextId == id) aa.nextId = a.id;
-               }
-       }
-       
        model::onSwap(model::actions, [&](model::Actions& mas)
        {
-               for (const Action& a : as)
-                       mas.map[a.frame].push_back(a);
+               for (const Action& a : actions)
+                       if (!exists_(a.channelId, a.frame, a.event, mas.map))
+                               mas.map[a.frame].push_back(a);
                updateMapPointers(mas.map);
        });
 }
@@ -393,8 +405,17 @@ void forEachAction(std::function<void(const Action&)> f)
 {
        model::ActionsLock lock(model::actions);
        
-       for (auto& kv : model::actions.get()->map)
-               for (const Action& action : kv.second)
+       for (auto& [_, actions] : model::actions.get()->map)
+               for (const Action& action : actions)
                        f(action);
 }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ID getNewActionId()
+{
+       return actionId_.get();
+}
 }}}; // giada::m::recorder::
index 6369f90d323cb14bbac1b950ae880a961ad3eaba..971aff1cfbe952bc0cdb0dbf9ca2a95de5873240 100644 (file)
@@ -150,6 +150,12 @@ vector makes it reallocating the existing ones. Also needed in model::Data copy
 constructor. */
 
 void updateMapPointers(ActionMap& src); 
+
+/* getNewActionId
+Returns a new action ID, internally generated. */
+
+ID getNewActionId();
+
 }}}; // giada::m::recorder::
 
 
index 3b38d25248d0adfefb1e1a5819fd1395b1177f94..b63c596d80e6963bacf1caa2561792cb9e3381d6 100644 (file)
@@ -55,8 +55,8 @@ std::vector<Action> recs_;
 
 const Action* getActionPtrById_(int id, const recorder::ActionMap& source)
 {
-       for (auto& kv : source)
-               for (const Action& action : kv.second)
+       for (const auto& [_, actions] : source)
+               for (const Action& action : actions)
                        if (action.id == id)
                                return &action;
        return nullptr;
@@ -88,7 +88,7 @@ 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, size_t i)
+void consolidate_(const Action& a1, std::size_t i)
 {
        for (auto it = recs_.begin() + i; it != recs_.end(); ++it) {
 
@@ -201,14 +201,15 @@ bool cloneActions(ID channelId, ID newChannelId)
 /* -------------------------------------------------------------------------- */
 
 
-void liveRec(ID channelId, MidiEvent e)
+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(-1, channelId, clock::getCurrentFrame(), e));
+       recs_.push_back(recorder::makeAction(recorder::getNewActionId(), channelId, globalFrame, e));
 }
 
 
@@ -234,8 +235,13 @@ std::unordered_set<ID> consolidate()
 
 void clearAllActions()
 {
-       for (size_t i = 0; i < model::channels.size(); i++)
-               model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.hasActions = false; });
+       /* TODO - disgusting */
+       for (std::size_t i = 0; i < model::channels.size(); i++) {
+               model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) 
+               { 
+                       c.state->hasActions = false;
+               });
+       }
        recorder::clearAll();
 }
 
index 1a5691101810d264b9e166a9bdb650b9d652317b..181200a0007197c3cac90c0a8b1b6eec7fc9242b 100644 (file)
@@ -68,7 +68,7 @@ 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);
+void liveRec(ID channelId, MidiEvent e, Frame global);
 
 /* consolidate
 Records all live actions. Returns a set of channels IDs that have been 
diff --git a/src/core/ringBuffer.h b/src/core/ringBuffer.h
new file mode 100644 (file)
index 0000000..c2520c3
--- /dev/null
@@ -0,0 +1,86 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_RING_BUFFER_H
+#define G_RING_BUFFER_H
+
+
+#include <array>
+
+
+namespace giada
+{
+/* RingBuffer
+A non-thread-safe, fixed-size ring buffer implementation. It grows from 0 to S, 
+then items are overwritten starting from position 0. */
+
+template <typename T, std::size_t S>
+class RingBuffer
+{
+public:
+       using iterator       = typename std::array<T, S>::iterator;
+       using const_iterator = typename std::array<T, S>::const_iterator;
+
+       iterator       begin()        { return m_data.begin(); }
+       iterator       end()          { return m_data.begin() + m_end; }
+       const_iterator begin()  const { return m_data.begin(); }
+       const_iterator end()    const { return m_data.begin() + m_end; }
+       const_iterator cbegin() const { return m_data.begin(); }
+       const_iterator cend()   const { return m_data.begin() + m_end; }
+
+       void clear()
+       {
+               m_data.fill({});
+               m_index = 0;
+               m_end   = 0;
+       }
+
+       void push_back(T t)
+       {
+               m_data[m_index] = t;
+               m_index = (m_index + 1) % m_data.size(); // Wraps around at m_data.size() 
+               m_end   = std::max(m_index, m_end);      // Points to the greater index
+       }
+
+       constexpr std::size_t size() const noexcept
+       {
+               return m_data.size();
+       }
+
+private:
+
+       std::array<T, S> m_data;
+       std::size_t      m_index = 0;
+       std::size_t      m_end   = 0;
+};
+} // giada::
+
+
+#endif
+
+
+
diff --git a/src/core/sequencer.cpp b/src/core/sequencer.cpp
new file mode 100644 (file)
index 0000000..0657cd8
--- /dev/null
@@ -0,0 +1,299 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/model/model.h"
+#include "core/const.h"
+#include "core/mixer.h"
+#include "core/quantizer.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/recManager.h"
+#include "core/kernelAudio.h"
+#include "sequencer.h"
+
+
+namespace giada {
+namespace m {
+namespace sequencer
+{
+namespace
+{
+constexpr int Q_ACTION_REWIND = 0;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct Metronome
+{
+       static constexpr Frame CLICK_SIZE = 38;
+
+       float beat[CLICK_SIZE] = {
+                0.059033,  0.117240,  0.173807,  0.227943,  0.278890,  0.325936,
+                0.368423,  0.405755,  0.437413,  0.462951,  0.482013,  0.494333,
+                0.499738,  0.498153,  0.489598,  0.474195,  0.452159,  0.423798,
+                0.389509,  0.349771,  0.289883,  0.230617,  0.173194,  0.118739,
+                0.068260,  0.022631, -0.017423, -0.051339,     -0.078721, -0.099345,
+               -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954,
+               -0.070862, -0.048844
+       };
+
+       float bar[CLICK_SIZE] = {
+                0.175860,  0.341914,  0.488904,  0.608633,  0.694426,  0.741500,
+                0.747229,  0.711293,  0.635697,  0.524656,  0.384362,  0.222636,
+                0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653,
+               -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160,
+               -0.201360, -0.067752,  0.052194,  0.151746,  0.226280,  0.273493,
+                0.293425,  0.288307,  0.262252,  0.220811,  0.170435,  0.117887,
+                0.069639,  0.031320
+       };
+
+       Frame tracker  = 0;
+       bool  running  = false;
+       bool  playBar  = false;
+       bool  playBeat = false;
+
+       void render(AudioBuffer& outBuf, bool& process, float* data, Frame f)
+       {
+               process = true;
+               for (int i=0; i<outBuf.countChannels(); i++)
+                       outBuf[f][i] += data[tracker];
+               if (++tracker > Metronome::CLICK_SIZE) {
+                       process = false;
+                       tracker = 0;
+               }       
+       }
+} metronome_;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void renderMetronome_(AudioBuffer& outBuf, Frame f)
+{
+       if (!metronome_.running)
+               return;
+
+       if (clock::isOnBar() || metronome_.playBar)
+               metronome_.render(outBuf, metronome_.playBar, metronome_.bar, f);
+       else
+       if (clock::isOnBeat() || metronome_.playBeat)
+               metronome_.render(outBuf, metronome_.playBeat, metronome_.beat, f);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindQ_(Frame delta)
+{
+       clock::rewind();
+       mixer::pumpEvent({ mixer::EventType::SEQUENCER_REWIND, delta });        
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+       if (kernelAudio::getAPI() == G_SYS_API_JACK)
+               kernelAudio::jackStart();
+       else
+#endif
+       start(); 
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stop_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+       if (kernelAudio::getAPI() == G_SYS_API_JACK)
+               kernelAudio::jackStop();
+       else
+#endif
+       stop();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewind_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+       if (kernelAudio::getAPI() == G_SYS_API_JACK)
+               kernelAudio::jackSetPosition(0);
+       else
+#endif
+       rewind();       
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Quantizer quantizer_;
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void init()
+{
+       quantizer_.schedule(Q_ACTION_REWIND, rewindQ_);
+       clock::rewind();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void run(Frame bufferSize)
+{
+       Frame start = clock::getCurrentFrame();
+       Frame end   = start + bufferSize;
+       Frame total = clock::getFramesInLoop();
+       Frame bar   = clock::getFramesInBar();
+
+       for (Frame i = start, local = 0; i < end; i++, local++) {
+
+               Frame global = i % total; // wraps around 'total'
+
+               if (global == 0)
+                       mixer::pumpEvent({ mixer::EventType::SEQUENCER_FIRST_BEAT, local, { 0, 0, global } });
+               else
+               if (global % bar == 0)
+                       mixer::pumpEvent({ mixer::EventType::SEQUENCER_BAR, local, { 0, 0, global } });
+
+               const std::vector<Action>* as = recorder::getActionsOnFrame(global);
+               if (as != nullptr)
+                       for (const Action& a : *as)
+                               mixer::pumpEvent({ mixer::EventType::ACTION, local, a });
+       }
+
+       quantizer_.advance(Range<Frame>(start, end), clock::getQuantizerStep());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void parse(const mixer::EventBuffer& events)
+{
+       for (const mixer::Event& e : events) {
+               if (e.type == mixer::EventType::SEQUENCER_START) {
+                       start_(); break;
+               }
+               if (e.type == mixer::EventType::SEQUENCER_STOP) {
+                       stop_(); break;
+               }
+               if (e.type == mixer::EventType::SEQUENCER_REWIND_REQ) {
+                       rewind_(); break;
+               }
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void advance(AudioBuffer& outBuf)
+{
+       for (Frame i = 0; i < outBuf.countFrames(); i++) {
+               clock::sendMIDIsync(); 
+               clock::incrCurrentFrame();
+               renderMetronome_(outBuf, i);
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start()
+{
+       switch (clock::getStatus()) {
+               case ClockStatus::STOPPED:
+                       clock::setStatus(ClockStatus::RUNNING); 
+                       break;
+               case ClockStatus::WAITING:
+                       clock::setStatus(ClockStatus::RUNNING); 
+                       recManager::stopActionRec();
+                       break;
+               default: 
+                       break;
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stop()
+{
+       clock::setStatus(ClockStatus::STOPPED);
+
+       /* 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();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewind()
+{
+       if (clock::canQuantize())
+               quantizer_.trigger(Q_ACTION_REWIND);
+       else
+               rewindQ_(/*delta=*/0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isMetronomeOn()      { return metronome_.running; }
+void toggleMetronome()    { metronome_.running = !metronome_.running; }
+void setMetronome(bool v) { metronome_.running = v; }
+}}}; // giada::m::sequencer::
+
+
diff --git a/src/core/sequencer.h b/src/core/sequencer.h
new file mode 100644 (file)
index 0000000..19c8898
--- /dev/null
@@ -0,0 +1,61 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_SEQUENCER_H
+#define G_SEQUENCER_H
+
+
+#include "core/mixer.h"
+
+
+namespace giada {
+namespace m 
+{
+class AudioBuffer;
+namespace sequencer
+{
+void init();
+
+/* parse
+Parses sequencer events that might occur in a block and advances the internal 
+quantizer. */
+
+void run(Frame bufferSize);
+void parse(const mixer::EventBuffer& events); 
+void advance(AudioBuffer& outBuf);
+
+void start();
+void stop();
+void rewind();
+
+bool isMetronomeOn();
+void toggleMetronome();
+void setMetronome(bool v);
+}}}  // giada::m::sequencer::
+
+
+#endif
index 9d628e3127935041b711a6355434079838e24330..25ad080989fc8ee84beb4c3c6b4c06079add7e27 100644 (file)
@@ -35,7 +35,9 @@ using ID    = int;
 using Pixel = int;
 using Frame = int;
 
-enum class ClockStatus { STOPPED, WAITING, RUNNING };
+enum class Thread { MAIN, MIDI, AUDIO };
+
+enum class ClockStatus { STOPPED, WAITING, RUNNING, ON_BEAT, ON_BAR, ON_FIRST_BEAT, VOID };
 
 enum class ChannelType : int { SAMPLE = 1, MIDI, MASTER, PREVIEW };
 
@@ -44,7 +46,7 @@ enum class ChannelStatus : int
        ENDING = 1, WAIT, PLAY, OFF, EMPTY, MISSING, WRONG  
 };
 
-enum class ChannelMode : int
+enum class SamplePlayerMode : int
 {
        LOOP_BASIC = 1, LOOP_ONCE, LOOP_REPEAT, LOOP_ONCE_BAR,
        SINGLE_BASIC, SINGLE_PRESS, SINGLE_RETRIG, SINGLE_ENDLESS
@@ -52,7 +54,6 @@ enum class ChannelMode : int
 
 enum class RecTriggerMode : int { NORMAL = 0, SIGNAL };
 
-enum class PreviewMode : int { NONE = 0, NORMAL, LOOP };
 enum class EventType : int { AUTO = 0, MANUAL };
 };
 
index 7e8f8c02179d63c705d1d43287d6f32e57dff345..95859f74084b5608882c8d1e952bcc16d36e9f28 100644 (file)
@@ -161,6 +161,12 @@ void Wave::copyData(const float* data, int frames, int offset)
 }
 
 
+void Wave::copyData(const AudioBuffer& b)
+{
+       buffer.copyData(b);
+}
+
+
 /* -------------------------------------------------------------------------- */
 
 
index 030c4cb61e810ed6c35255cbf3d905b6bc212e8b..13c080dc14bda092b6c148f022affffe19f44aaf 100644 (file)
@@ -83,6 +83,7 @@ public:
        channels than m_channels. */
 
        void copyData(const float* data, int frames, int offset=0);
+       void copyData(const AudioBuffer& b);
 
        void alloc(int size, int channels, int rate, int bits, const std::string& path);
 
index d2cb8d1137dfc86e38e7efaaab7ff7081349a12a..df96d956e6c743e7a439ac1c1f93b8a8e9c7d0a0 100644 (file)
@@ -71,12 +71,15 @@ float getPeak_(const Wave& w, int a, int b)
 /* -------------------------------------------------------------------------- */
 
 
-void normalizeHard(ID waveId, int a, int b)
+constexpr int SMOOTH_SIZE = 32;
+
+
+void normalize(ID waveId, int a, int b)
 {
        model::onSwap(m::model::waves, waveId, [&](Wave& w)
        {
                float peak = getPeak_(w, a, b);
-               if (peak == 0.0f || peak > 1.0f)  // as in ::normalizeSoft
+               if (peak == 0.0f || peak > 1.0f)
                        return;
 
                for (int i=a; i<b; i++) {
@@ -215,7 +218,7 @@ void paste(const Wave& src, ID waveId, int a)
 /* -------------------------------------------------------------------------- */
 
 
-void fade(ID waveId, int a, int b, int type)
+void fade(ID waveId, int a, int b, Fade type)
 {
        u::log::print("[wfx::fade] fade from %d to %d (range = %d)\n", a, b, b-a);
 
@@ -224,7 +227,7 @@ void fade(ID waveId, int a, int b, int type)
 
        model::onSwap(m::model::waves, waveId, [&](Wave& w)
        {
-               if (type == FADE_IN)
+               if (type == Fade::IN)
                        for (int i=a; i<=b; i++, m+=d)
                                fadeFrame_(w, i, m);
                else
@@ -249,8 +252,8 @@ void smooth(ID waveId, int a, int b)
                return;
        }
 
-       fade(waveId, a, a+SMOOTH_SIZE, FADE_IN);
-       fade(waveId, b-SMOOTH_SIZE, b, FADE_OUT);
+       fade(waveId, a, a+SMOOTH_SIZE, Fade::IN);
+       fade(waveId, b-SMOOTH_SIZE, b, Fade::OUT);
 }
 
 
index 6e94d1ab572f5a1eaf03aed8b657f2c1595cab19..7fc1df257740febc286002674b225ba15282a423 100644 (file)
 #define G_WAVE_FX_H
 
 
+#include "core/types.h"
+
+
 namespace giada {
 namespace m 
 {
 class Wave;
-
 namespace wfx
 {
-static const int FADE_IN  = 0;
-static const int FADE_OUT = 1;
-static const int SMOOTH_SIZE = 32;
+enum class Fade { IN, OUT };
 
 /* monoToStereo
 Converts a 1-channel Wave to a 2-channels wave. It works on a free Wave object,
@@ -46,10 +46,10 @@ not yet added to the RCUList. */
 
 int monoToStereo(Wave& w);
 
-/* normalizeHard
+/* normalize
 Normalizes the wave in range a-b by altering values in memory. */
 
-void normalizeHard(ID waveId, int a, int b);
+void normalize(ID waveId, int a, int b);
 
 void silence(ID waveId, int a, int b);
 void cut(ID waveId, int a, int b);
@@ -61,9 +61,9 @@ Pastes Wave 'src' into Wave at 'waveIndex', starting from frame 'a'. */
 void paste(const Wave& src, ID waveId, int a);
 
 /* fade
-Fades in or fades out selection. Fade In = type 0, Fade Out = type 1 */
+Fades in or fades out selection. Can be Fade::IN or Fade::OUT. */
 
-void fade(ID waveId, int a, int b, int type);
+void fade(ID waveId, int a, int b, Fade type);
 
 /* smooth
 Smooth edges of selection. */
index 79ddfc29ef4cbc6995aca1260a7c8c9fed973921..e16629f54e8f49f0f2d5a3dedb67013088371007 100644 (file)
 
 #include <cassert>
 #include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/clock.h"
 #include "core/const.h"
 #include "core/recorderHandler.h"
 #include "core/recorder.h"
 #include "core/action.h"
-#include "recorder.h"
+#include "glue/events.h"
+#include "glue/recorder.h"
 #include "actionEditor.h"
 
 
@@ -63,6 +62,7 @@ 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); 
@@ -96,6 +96,7 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
        if (frame == -1) // Vertical points, nothing to do here
                return;
 
+       // TODO - use MidiEvent(float)
        m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
        const m::Action a2 = mr::rec(channelId, frame, e2);
 
@@ -109,10 +110,9 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
 bool isSinglePressMode_(ID channelId)
 {
        bool b;
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
        {
-               const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               b = sc.mode == ChannelMode::SINGLE_PRESS;
+               b = c.samplePlayer->state->mode == SamplePlayerMode::SINGLE_PRESS;
        });
        return b;
 }
@@ -124,6 +124,43 @@ bool isSinglePressMode_(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
+SampleData::SampleData(const m::SamplePlayer& s)
+: channelMode(s.state->mode.load())
+, isLoopMode (s.state->isAnyLoopMode())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Data::Data(const m::Channel& c)
+: channelId  (c.id)
+, channelName(c.state->name)
+, actions    (m::recorder::getActionsOnChannel(c.id))
+{
+       if (c.getType() == ChannelType::SAMPLE)
+               sample = std::make_optional<SampleData>(*c.samplePlayer);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       return Data(mm::get(mm::channels, channelId));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
 void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
 {
        namespace mr = m::recorder;
@@ -155,7 +192,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;
-       namespace cr = c::recorder;
 
        assert(a.isValid());
        assert(a.event.getStatus() == m::MidiEvent::NOTE_ON);
@@ -164,12 +200,7 @@ void deleteMidiAction(ID channelId, const m::Action& a)
        key_on/key_off sequence. Check if 'next' exist first: could be orphaned. */
        
        if (a.next != nullptr) {
-               m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-               {
-                       m::MidiChannel& mc = static_cast<m::MidiChannel&>(c);
-                       if (mc.isPlaying() && !mc.mute)
-                               mc.sendMidi(a.next->event, 0);
-               });
+               events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN);
                mr::deleteAction(a.id, a.next->id);
        }
        else
@@ -240,8 +271,9 @@ void deleteSampleAction(ID channelId, const m::Action& a)
        namespace cr = c::recorder;
 
        if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo
-               mr::deleteAction(a.next->id);
-       mr::deleteAction(a.id);
+               mr::deleteAction(a.id, a.next->id);
+       else
+               mr::deleteAction(a.id);
 
        recorder::updateChannel(channelId, /*updateActionEditor=*/false);
 }
@@ -283,13 +315,15 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a)
        /* 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 (a.isVolumeEnvelope()) {
+                       /*
                        m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
                        {
                                c.volume_i = 1.0;
                                c.volume_d = 0.0;
-                       });
+                       });*/
                }
                mr::clearActions(channelId, a.event.getStatus());
        }
index c1414c2399e4dbc71cb491172ec96f632b7b4181..4aed947fe091fdf9ed2ad3b04d95e2b7596eac67 100644 (file)
@@ -29,7 +29,9 @@
 #define G_GLUE_ACTION_EDITOR_H
 
 
+#include <optional>
 #include <vector>
+#include <string>
 #include "core/types.h"
 
 
@@ -37,13 +39,33 @@ namespace giada {
 namespace m
 {
 struct Action;
-class SampleChannel;
-class MidiChannel;
+class  Channel;
+class  SamplePlayer;
 }
 namespace c {
 namespace actionEditor 
 {
-std::vector<m::Action> getActions(ID channelId);
+struct SampleData
+{
+       SampleData(const m::SamplePlayer&); 
+
+    SamplePlayerMode channelMode;
+       bool             isLoopMode;
+};
+
+struct Data
+{
+    Data() = default;
+    Data(const m::Channel&);
+
+    ID                     channelId; 
+    std::string            channelName;
+       std::vector<m::Action> actions;
+
+    std::optional<SampleData> sample;
+};
+
+Data getData(ID channelId);
 
 /* MIDI actions.  */
 
index 30eaac13439aab663316efc6ff6aadd21cb3b175..c3ecb15339b20bcf777fbcac31911ca25d5babd1 100644 (file)
@@ -48,9 +48,6 @@
 #include "utils/gui.h"
 #include "utils/fs.h"
 #include "utils/log.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/model/model.h"
 #include "core/kernelAudio.h"
 #include "core/mixerHandler.h"
@@ -60,6 +57,7 @@
 #include "core/conf.h"
 #include "core/wave.h"
 #include "core/recorder.h"
+#include "core/recManager.h"
 #include "core/plugin.h"
 #include "core/waveManager.h"
 #include "main.h"
@@ -86,25 +84,107 @@ void printLoadError_(int res)
        else if (res == G_RES_ERR_NO_DATA)
                v::gdAlert("No file specified.");
 }
+} // {anonymous}
 
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 
-void onRefreshSampleEditor_(bool gui, std::function<void(v::gdSampleEditor*)> f)
+SampleData::SampleData(const m::SamplePlayer& s, const m::AudioReceiver& a)
+: waveId         (s.getWaveId())
+, mode           (s.state->mode.load())
+, isLoop         (s.state->isAnyLoopMode())
+, pitch          (s.state->pitch.load())
+, m_samplePlayer (&s)
+, m_audioReceiver(&a)
 {
-       v::gdSampleEditor* gdEditor = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
-       if (gdEditor == nullptr) 
-               return;
-       if (!gui) Fl::lock();
-       f(gdEditor);
-       if (!gui) Fl::unlock();
 }
-} // {anonymous}
 
 
+Frame SampleData::a_getTracker() const      { return a_get(m_samplePlayer->state->tracker); }
+Frame SampleData::a_getBegin() const        { return a_get(m_samplePlayer->state->begin); }
+Frame SampleData::a_getEnd() const          { return a_get(m_samplePlayer->state->end); }
+bool  SampleData::a_getInputMonitor() const { return a_get(m_audioReceiver->state->inputMonitor); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiData::MidiData(const m::MidiSender& m)
+: m_midiSender(&m)
+{
+}
+
+bool MidiData::a_isOutputEnabled() const { return a_get(m_midiSender->state->enabled); }
+int  MidiData::a_getFilter() const       { return a_get(m_midiSender->state->filter); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Data::Data(const m::Channel& c)
+: id         (c.id)
+, columnId   (c.getColumnId())
+#ifdef WITH_VST
+, pluginIds  (c.pluginIds)
+#endif
+, type       (c.getType())
+, height     (c.state->height)
+, name       (c.state->name)
+, volume     (c.state->volume.load())
+, pan        (c.state->pan.load())
+, key        (c.state->key.load())
+, hasActions (c.state->hasActions)
+, m_channel  (c)
+{
+       if (c.getType() == ChannelType::SAMPLE)
+               sample = std::make_optional<SampleData>(*c.samplePlayer, *c.audioReceiver);
+       else
+       if (c.getType() == ChannelType::MIDI)
+               midi   = std::make_optional<MidiData>(*c.midiSender);
+}
+
+
+bool          Data::a_getSolo() const           { return a_get(m_channel.state->solo); }
+bool          Data::a_getMute() const           { return a_get(m_channel.state->mute); }
+ChannelStatus Data::a_getPlayStatus() const     { return a_get(m_channel.state->playStatus); }
+ChannelStatus Data::a_getRecStatus() const      { return a_get(m_channel.state->recStatus); }
+bool          Data::a_getReadActions() const    { return a_get(m_channel.state->readActions); }
+bool          Data::a_isArmed() const           { return a_get(m_channel.state->armed); }
+bool          Data::a_isRecordingInput() const  { return m::recManager::isRecordingInput(); }
+bool          Data::a_isRecordingAction() const { return m::recManager::isRecordingAction(); }
+
+
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       return Data(mm::get(mm::channels, channelId));
+}
+
+
+std::vector<Data> getChannels()
+{
+       namespace mm = m::model;
+       mm::ChannelsLock cl(mm::channels);
+
+       std::vector<Data> out;
+       for (const m::Channel* ch : mm::channels)
+               if (!ch->isInternal()) 
+                       out.push_back(Data(*ch));
+       
+       return out;
+}
+
+
 /* -------------------------------------------------------------------------- */
 
 
@@ -187,26 +267,11 @@ void freeChannel(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
-void setArm(ID channelId, bool value)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = value; });
-}
-
-
-void toggleArm(ID channelId)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = !c.armed; });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
 void setInputMonitor(ID channelId, bool value)
 {
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) 
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) 
        { 
-               static_cast<m::SampleChannel&>(c).inputMonitor = value;
+               c.audioReceiver->state->inputMonitor.store(value);
        });
 }
 
@@ -223,131 +288,29 @@ void cloneChannel(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
-void setVolume(ID channelId, float value, bool gui, bool editor)
+void setSamplePlayerMode(ID channelId, SamplePlayerMode m)
 {
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { c.volume = value; });
-
-       /* Changing channel volume? Update wave editor (if it's shown). */
-
-       if (editor) 
-               onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->volumeTool->rebuild(); });
-
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->keyboard->getChannel(channelId)->vol->value(value);
-               Fl::unlock();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setPitch(ID channelId, float val, bool gui)
-{      
        m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       { 
-               static_cast<m::SampleChannel&>(c).setPitch(val); 
-       });
-       
-       onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->pitchTool->rebuild(); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setPan(ID channelId, float val, bool gui)
-{
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { c.setPan(val); });
-
-       onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->panTool->rebuild(); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(ID channelId, bool value)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setMute(value); });
-}
-
-
-void toggleMute(ID channelId)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setMute(!ch.mute); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSampleMode(ID channelId, ChannelMode m)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
        {
-               static_cast<m::SampleChannel&>(ch).mode = m;
+               c.samplePlayer->state->mode.store(m);
        });
 
-       u::gui::refreshActionEditor();
-}
-
+       /* TODO - brutal rebuild! Just rebuild the specific channel instead */
+       G_MainWin->keyboard->rebuild();
 
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(ID channelId, bool value)
-{      
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(value); });
-       m::mh::updateSoloCount();
-}
-
-
-void toggleSolo(ID channelId)
-{      
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(!ch.solo); });
-       m::mh::updateSoloCount();
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(ID channelId, int velocity, bool record)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
-       {
-               if (record && !ch.recordStart(m::clock::canQuantize()))
-                       return;
-               ch.start(/*localFrame=*/0, m::clock::canQuantize(), velocity); // Frame 0: user-generated event
-       });
+       u::gui::refreshActionEditor();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void kill(ID channelId, bool record)
+void setHeight(ID channelId, Pixel p)
 {
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
-       {
-               if (record && !ch.recordKill())
-                       return;
-               ch.kill(/*localFrame=*/0); // Frame 0: user-generated event
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stop(ID channelId)
-{      
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               ch.recordStop();
-               ch.stop();
-       });
+               c.state->height = p;
+       });     
 }
 
 
@@ -358,56 +321,4 @@ void setName(ID channelId, const std::string& name)
 {
        m::mh::renameChannel(channelId, name);
 }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleReadingActions(ID channelId)
-{
-       /* When you call startReadingRecs with conf::treatRecsAsLoops, the
-       member value ch->readActions actually is not set to true immediately, because
-       the channel is in wait mode (REC_WAITING). ch->readActions will become true on
-       the next first beat. So a 'stop rec' command should occur also when
-       ch->readActions is false but the channel is in wait mode; this check will
-       handle the case of when you press 'R', the channel goes into REC_WAITING and
-       then you press 'R' again to undo the status. */
-
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
-       {
-               if (!ch.hasActions)
-                       return;
-               if (ch.readActions || (!ch.readActions && ch.recStatus == ChannelStatus::WAIT))
-                       ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops, 
-                               m::conf::conf.recsStopOnChanHalt);
-               else
-                       ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt);
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startReadingActions(ID channelId)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
-       {
-               ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt);
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopReadingActions(ID channelId)
-{
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
-       {
-               ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops, 
-                       m::conf::conf.recsStopOnChanHalt);
-       });
-}
-
 }}}; // giada::c::channel::
index 00b298df45f01e0df2db20e1b6692003ab382154..2018778868677bb38d5dec6ba82f6d60eaa5e105 100644 (file)
@@ -1,4 +1,4 @@
-    /* -----------------------------------------------------------------------------
+       /* -----------------------------------------------------------------------------
  *
  * Giada - Your Hardcore Loopmachine
  *
 #define G_GLUE_CHANNEL_H
 
 
+#include <optional>
+#include <atomic>
 #include <string>
 #include <vector>
+#include "core/model/model.h"
 #include "core/types.h"
 
 
@@ -38,10 +41,100 @@ namespace giada {
 namespace m
 {
 class Channel;
+class SamplePlayer;
 }
 namespace c {
 namespace channel 
 {
+struct SampleData
+{
+       SampleData() = delete;
+       SampleData(const m::SamplePlayer&, const m::AudioReceiver&);
+
+       Frame a_getTracker() const;
+       Frame a_getBegin() const;
+       Frame a_getEnd() const;
+       bool  a_getInputMonitor() const;
+
+       ID               waveId;
+       SamplePlayerMode mode;
+       bool             isLoop;
+       float            pitch;
+
+private:
+
+       const m::SamplePlayer*  m_samplePlayer;
+       const m::AudioReceiver* m_audioReceiver;
+};
+
+struct MidiData
+{
+       MidiData() = delete;
+       MidiData(const m::MidiSender&);
+
+       bool a_isOutputEnabled() const;
+       int  a_getFilter() const;
+
+private:
+
+       const m::MidiSender* m_midiSender;
+};
+
+struct Data
+{
+       Data() = default;
+       Data(const m::Channel&);
+
+       bool a_getMute() const;
+       bool a_getSolo() const;
+       ChannelStatus a_getPlayStatus() const;
+       ChannelStatus a_getRecStatus() const;
+       bool a_getReadActions() const;
+       bool a_isArmed() const;
+       bool a_isRecordingInput() const;
+       bool a_isRecordingAction() const;
+
+       ID              id;
+       ID              columnId;
+#ifdef WITH_VST
+       std::vector<ID> pluginIds;
+#endif
+       ChannelType     type;
+       Pixel           height;
+       std::string     name;
+       float           volume;
+       float           pan;
+       int             key;
+       bool            hasActions;
+
+       std::optional<SampleData> sample;
+       std::optional<MidiData>   midi;
+
+private:
+
+       const m::Channel& m_channel;
+};
+
+/* getChannels
+Returns a single viewModel object filled with data from a channel. */
+
+Data getData(ID channelId);
+
+/* getChannels
+Returns a vector of viewModel objects filled with data from channels. */
+
+std::vector<Data> getChannels();
+
+/* a_get
+Returns an atomic property from a Channel, by locking it first. */
+
+template <typename T>
+T a_get(const std::atomic<T>& a)
+{
+       m::model::ChannelsLock l(m::model::channels);
+       return a.load();
+}
+
 /* addChannel
 Adds an empty new channel to the stack. */
 
@@ -50,7 +143,7 @@ void addChannel(ID columnId, ChannelType type);
 /* loadChannel
 Fills an existing channel with a wave. */
 
-int loadChannel(ID channelId, const std::string& fname);
+int loadChannel(ID columnId, const std::string& fname);
 
 /* addAndLoadChannel
 Adds a new Sample Channel and fills it with a wave right away. */
@@ -73,38 +166,18 @@ Unloads the sample from a sample channel. */
 void freeChannel(ID channelId);
 
 /* cloneChannel
-Makes an exact copy of Channel *ch. */
+Makes an exact copy of a channel. */
 
 void cloneChannel(ID channelId);
 
 /* set*
 Sets several channel properties. */
 
-void setArm(ID channelId, bool value);
-void toggleArm(ID channelId);
 void setInputMonitor(ID channelId, bool value);
-void setMute(ID channelId, bool value);
-void toggleMute(ID channelId);
-void setSolo(ID channelId, bool value);
-void toggleSolo(ID channelId);
-void setVolume(ID channelId, float v, bool gui=true, bool editor=false);
 void setName(ID channelId, const std::string& name);
-void setPitch(ID channelId, float val, bool gui=true);
-void setPan(ID channelId, float val, bool gui=true);
-void setSampleMode(ID channelId, ChannelMode m);
-
-void start(ID channelId, int velocity, bool record);
-void kill(ID channelId, bool record);
-void stop(ID channelId);
-
-/* toggleReadingRecs
-Handles the 'R' button. If gui == true the signal comes from an user interaction
-on the GUI, otherwise it's a MIDI/Jack/external signal. */
-
-void toggleReadingActions(ID channelId);
-void startReadingActions(ID channelId);
-void stopReadingActions(ID channelId);
+void setHeight(ID channelId, Pixel p);
 
+void setSamplePlayerMode(ID channelId, SamplePlayerMode m);
 }}}; // giada::c::channel::
 
 #endif
diff --git a/src/glue/events.cpp b/src/glue/events.cpp
new file mode 100644 (file)
index 0000000..504088c
--- /dev/null
@@ -0,0 +1,297 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <FL/Fl.H>
+#include "core/model/model.h"
+#include "core/const.h"
+#include "core/clock.h"
+#include "core/mixer.h"
+#include "core/midiEvent.h"
+#include "core/pluginHost.h"
+#include "core/sequencer.h"
+#include "core/mixerHandler.h"
+#include "core/conf.h"
+#include "core/recManager.h"
+#include "utils/log.h"
+#include "gui/dialogs/sampleEditor.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dialogs/warnings.h"
+#include "gui/elems/mainWindow/mainIO.h"
+#include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/elems/basics/dial.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/keyboard/channel.h"
+#include "gui/elems/sampleEditor/volumeTool.h"
+#include "gui/elems/sampleEditor/pitchTool.h"
+#include "gui/elems/sampleEditor/panTool.h"
+#include "glue/sampleEditor.h"
+#include "glue/plugin.h"
+#include "glue/main.h"
+#include "events.h"
+
+
+extern giada::v::gdMainWindow* G_MainWin;
+
+
+namespace giada {
+namespace c {
+namespace events 
+{
+namespace
+{
+void pushEvent_(m::mixer::Event e, Thread t)
+{
+       bool res = true;
+       if (t == Thread::MAIN)
+               res = m::mixer::UIevents.push(e);
+       else
+       if (t == Thread::MIDI)
+               res = m::mixer::MidiEvents.push(e);
+       else
+               assert(false);
+       
+       if (!res)
+               G_DEBUG("[events] Queue full!\n");
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void pressChannel(ID channelId, int velocity, Thread t)
+{
+       m::MidiEvent e;
+       e.setVelocity(velocity);
+       pushEvent_({ m::mixer::EventType::KEY_PRESS, 0, {0, channelId, 0, e} }, t);
+}
+
+
+void releaseChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::KEY_RELEASE, 0, {0, channelId} }, t);
+}
+
+
+void killChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::KEY_KILL, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setChannelVolume(ID channelId, float v, Thread t)
+{
+       v = std::clamp(v, 0.0f, G_MAX_VOLUME);
+
+       pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, channelId, 0, {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);
+               Fl::unlock();
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setChannelPitch(ID channelId, float v,  Thread t)
+{      
+       v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH);
+
+       pushEvent_({ m::mixer::EventType::CHANNEL_PITCH, 0, { 0, channelId, 0, {v} } }, t);
+       
+       sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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::mixer::EventType::CHANNEL_PAN, 0, { 0, channelId, 0, {v} } }, Thread::MAIN);
+       
+       sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); });
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleMuteChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_MUTE, 0, {0, channelId} }, t);
+}
+
+
+void toggleSoloChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_SOLO, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleArmChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_ARM, 0, {0, channelId} }, t);
+}
+
+
+void toggleReadActionsChannel(ID channelId, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::MIDI, 0, {0, channelId, 0, e} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleMetronome()
+{
+       m::sequencer::toggleMetronome();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setMasterInVolume(float v, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_IN_CHANNEL_ID, 0, {v} }}, t);
+
+       if (t != Thread::MAIN) {
+               Fl::lock();
+               G_MainWin->mainIO->setInVol(v);
+               Fl::unlock();
+       }
+}
+
+
+void setMasterOutVolume(float v, Thread t)
+{
+       pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_OUT_CHANNEL_ID, 0, {v} }}, t);
+       
+       if (t != Thread::MAIN) {
+               Fl::lock();
+               G_MainWin->mainIO->setOutVol(v);
+               Fl::unlock();
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void multiplyBeats()
+{
+       main::setBeats(m::clock::getBeats() * 2, m::clock::getBars());
+}
+
+
+void divideBeats()
+{
+       main::setBeats(m::clock::getBeats() / 2, m::clock::getBars());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startSequencer(Thread t)
+{ 
+       pushEvent_({ m::mixer::EventType::SEQUENCER_START, 0 }, t);
+}
+
+
+void stopSequencer(Thread t)
+{ 
+       pushEvent_({ m::mixer::EventType::SEQUENCER_STOP, 0 }, t);
+}
+
+
+void toggleSequencer(Thread t)
+{ 
+       m::clock::isRunning() ? stopSequencer(t) : startSequencer(t);
+}
+
+
+void rewindSequencer(Thread t)
+{ 
+       pushEvent_({ m::mixer::EventType::SEQUENCER_REWIND_REQ, 0 }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleActionRecording()
+{
+       m::recManager::toggleActionRec(m::conf::conf.recTriggerMode);
+}
+
+
+void toggleInputRecording()
+{
+       if (!m::recManager::toggleInputRec(m::conf::conf.recTriggerMode))
+               v::gdAlert("No channels armed/available for audio recording.");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui)
+{
+       m::pluginHost::setPluginParameter(pluginId, paramIndex, value);
+       c::plugin::updateWindow(pluginId, gui);
+}
+#endif
+}}}; // giada::c::events::
diff --git a/src/glue/events.h b/src/glue/events.h
new file mode 100644 (file)
index 0000000..0949f90
--- /dev/null
@@ -0,0 +1,86 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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_GLUE_EVENTS_H
+#define G_GLUE_EVENTS_H
+
+
+#include "core/types.h"
+
+
+/* giada::c::events
+Functions that take care of live event dispatching. Every live gesture that 
+comes from the UI, MIDI thread or keyboard interaction and wants to change the
+internal engine state must call these functions. */
+
+namespace giada {
+namespace m
+{
+class MidiEvent;
+}
+namespace c {
+namespace events
+{
+/* Channel*
+Channel-related events. */
+
+void pressChannel            (ID channelId, int velocity, Thread t);
+void releaseChannel          (ID channelId, Thread t);
+void killChannel             (ID channelId, Thread t);
+void setChannelVolume        (ID channelId, float v, Thread t);
+void setChannelPitch         (ID channelId, float v, Thread t);
+void sendChannelPan          (ID channelId, float v);
+void toggleMuteChannel       (ID channelId, Thread t);
+void toggleSoloChannel       (ID channelId, Thread t);
+void toggleArmChannel        (ID channelId, Thread t);
+void toggleReadActionsChannel(ID channelId, Thread t);
+void sendMidiToChannel       (ID channelId, m::MidiEvent e, Thread t);
+
+/* Main*
+Master I/O, transport and other engine-related events. */
+
+void toggleMetronome      ();
+void setMasterInVolume    (float v, Thread t);
+void setMasterOutVolume   (float v, Thread t);
+void multiplyBeats        ();
+void divideBeats          ();
+void startSequencer       (Thread t);
+void stopSequencer        (Thread t);
+void toggleSequencer      (Thread t);
+void rewindSequencer      (Thread t);
+void toggleActionRecording();
+void toggleInputRecording ();
+
+/* Plug-ins. */
+
+#ifdef WITH_VST
+void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui); 
+#endif
+}}} // giada::c::events::
+
+
+#endif
index 60de69f6db0a9df1de5d09986f640c35199320c1..6a40297eb1dc966082ca3bc0547f1b66212f97a9 100644 (file)
@@ -40,9 +40,6 @@
 #include "utils/log.h"
 #include "utils/math.h"
 #include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/channel.h"
-#include "core/channels/midiChannel.h"
 #include "core/recorder.h"
 #include "core/conf.h"
 #include "core/recManager.h"
@@ -67,12 +64,10 @@ namespace io
 {
 namespace
 {
-void refreshMidiWindows_()
+void rebuildMidiWindows_()
 {
-       Fl::lock();
-       u::gui::refreshSubWindow(WID_MIDI_INPUT);
-       u::gui::refreshSubWindow(WID_MIDI_OUTPUT);
-       Fl::unlock();   
+       u::gui::rebuildSubWindow(WID_MIDI_INPUT);
+       u::gui::rebuildSubWindow(WID_MIDI_OUTPUT);
 }
 } // {anonymous}
 
@@ -81,64 +76,228 @@ void refreshMidiWindows_()
 /* -------------------------------------------------------------------------- */
 
 
-void keyPress(ID channelId, bool ctrl, bool shift, int velocity)
+Channel_InputData::Channel_InputData(const m::Channel& c)
+: channelId    (c.id)
+, channelType  (c.getType())
+, enabled      (c.midiLearner.state->enabled.load())
+, velocityAsVol(c.samplePlayer ? c.samplePlayer->state->velocityAsVol.load() : 0)
+, filter       (c.midiLearner.state->filter.load())
+, keyPress     (c.midiLearner.state->keyPress.load())
+, keyRelease   (c.midiLearner.state->keyRelease.load())
+, kill         (c.midiLearner.state->kill.load())
+, arm          (c.midiLearner.state->arm.load())
+, volume       (c.midiLearner.state->volume.load())
+, mute         (c.midiLearner.state->mute.load())
+, solo         (c.midiLearner.state->solo.load())
+, pitch        (c.midiLearner.state->pitch.load())
+, readActions  (c.midiLearner.state->readActions.load()) 
 {
-       if (ctrl)
-               c::channel::toggleMute(channelId);
-       else
-       if (shift)
-               c::channel::kill(channelId, /*record=*/true);
-       else
-               c::channel::start(channelId, velocity, /*record=*/true);
+#ifdef WITH_VST
+       for (ID id : c.pluginIds) {
+               m::Plugin& p = m::model::get(m::model::plugins, id);
+               
+               PluginData pd;
+               pd.id = p.id;
+               pd.name = p.getName();
+               for (int i = 0; i < p.getNumParameters(); i++)
+                       pd.params.push_back({ i, p.getParameterName(i), p.midiInParams.at(i) });
+
+               plugins.push_back(pd);
+       }
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s)
+: enabled(s.state->enabled.load())
+, filter (s.state->filter.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel_OutputData::Channel_OutputData(const m::Channel& c)
+: channelId       (c.id)
+, lightningEnabled(c.midiLighter.state->enabled.load())
+, lightningPlaying(c.midiLighter.state->playing.load())
+, lightningMute   (c.midiLighter.state->mute.load())
+, lightningSolo   (c.midiLighter.state->solo.load())
+{      
+       if (c.getType() == ChannelType::MIDI)
+               output = std::make_optional<MidiChannel_OutputData>(*c.midiSender);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Master_InputData::Master_InputData(const m::model::MidiIn& midiIn)
+: enabled   (midiIn.enabled)
+, filter    (midiIn.filter)
+, rewind    (midiIn.rewind)
+, startStop (midiIn.startStop)
+, actionRec (midiIn.actionRec)
+, inputRec  (midiIn.inputRec)
+, volumeIn  (midiIn.volumeIn)
+, volumeOut (midiIn.volumeOut)
+, beatDouble(midiIn.beatDouble)
+, beatHalf  (midiIn.beatHalf)
+, metronome (midiIn.metronome) 
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Channel_InputData channel_getInputData(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+#ifdef WITH_VST
+       mm::PluginsLock  ml(mm::plugins);
+#endif
+
+       return Channel_InputData(mm::get(mm::channels, channelId));     
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel_OutputData channel_getOutputData(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       return Channel_OutputData(mm::get(mm::channels, channelId));            
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Master_InputData master_getInputData()
+{
+       namespace mm = m::model;
+
+       mm::MidiInLock l(mm::midiIn);
+       return Master_InputData(*mm::midiIn.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_enableMidiLearn(ID channelId, bool v)
+{
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.midiLearner.state->enabled.store(v);
+       });
+       rebuildMidiWindows_();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void keyRelease(ID channelId, bool ctrl, bool shift)
+void channel_enableMidiLightning(ID channelId, bool v)
 {
-       if (!ctrl && !shift)
-               c::channel::stop(channelId);
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.midiLighter.state->enabled.store(v);
+       });
+       rebuildMidiWindows_();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void setSampleChannelKey(ID channelId, int k)
+void channel_enableMidiOutput(ID channelId, bool v)
 {
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               c.key = k;
+               c.midiSender->state->enabled.store(v);
+       });     
+       rebuildMidiWindows_();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_enableVelocityAsVol(ID channelId, bool v)
+{
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.samplePlayer->state->velocityAsVol.store(v);
        });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_setMidiInputFilter(ID channelId, int ch)
+{
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.midiLearner.state->filter.store(ch);
+       });
+}
+
 
-       Fl::lock();
-       G_MainWin->keyboard->getChannel(channelId)->mainButton->setKey(k);
-       Fl::unlock();
+void channel_setMidiOutputFilter(ID channelId, int ch)
+{
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.midiSender->state->filter.store(ch);
+       });     
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_setKey(ID channelId, int k)
+{
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       {
+               c.state->key.store(k);
+       }, /*rebuild=*/true);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void startChannelMidiLearn(int param, ID channelId)
+void channel_startMidiLearn(int param, ID channelId)
 {
-       m::midiDispatcher::startChannelLearn(param, channelId, refreshMidiWindows_);
+       m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_);
 }
 
 
-void startMasterMidiLearn(int param)
+void master_startMidiLearn(int param)
 {
-       m::midiDispatcher::startMasterLearn(param, refreshMidiWindows_);
+       m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_);
 }
 
 
 #ifdef WITH_VST
 
-void startPluginMidiLearn(int paramIndex, ID pluginId)
+void plugin_startMidiLearn(int paramIndex, ID pluginId)
 {
-       m::midiDispatcher::startPluginLearn(paramIndex, pluginId, refreshMidiWindows_);
+       m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
 }
 
 #endif
@@ -150,31 +309,56 @@ void startPluginMidiLearn(int paramIndex, ID pluginId)
 void stopMidiLearn()
 {
        m::midiDispatcher::stopLearn();
-       refreshMidiWindows_();
+       rebuildMidiWindows_();
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void clearChannelMidiLearn(int param, ID channelId)
+void channel_clearMidiLearn(int param, ID channelId)
 {
-       m::midiDispatcher::clearChannelLearn(param, channelId, refreshMidiWindows_);
+       m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_);
 }
 
 
-void clearMasterMidiLearn (int param)
+void master_clearMidiLearn (int param)
 {
-       m::midiDispatcher::clearMasterLearn(param, refreshMidiWindows_);
+       m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_);
 }
 
 
 #ifdef WITH_VST
 
-void clearPluginMidiLearn (int param, ID pluginId)
+void plugin_clearMidiLearn (int param, ID pluginId)
 {
-       m::midiDispatcher::clearPluginLearn(param, pluginId, refreshMidiWindows_);
+       m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_);
 }
 
 #endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void master_enableMidiLearn(bool v)
+{
+       m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
+       {
+               m.enabled = v;
+       });     
+       rebuildMidiWindows_();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void master_setMidiFilter(int c)
+{
+       m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
+       {
+               m.filter = c;
+       });
+}
 }}} // giada::c::io::
index 9c05b86a7f408cdb18a1dc8c77e2826c30572029..46cbd8903d441f81bcb2ef55eedd418eda229183 100644 (file)
@@ -43,26 +43,119 @@ class Channel;
 namespace c {
 namespace io 
 {
-/* keyPress / keyRelease
-Handle the key pressure, either via mouse/keyboard or MIDI. */
+struct PluginParamData
+{
+    int         index;
+    std::string name;
+    uint32_t    value;
+};
+
+struct PluginData
+{
+    ID          id;
+    std::string name;
+    std::vector<PluginParamData> params;
+};
+
+struct Channel_InputData
+{
+    Channel_InputData() = default;
+    Channel_InputData(const m::Channel&);
+
+    ID          channelId;
+    ChannelType channelType;
+    bool        enabled;
+    bool        velocityAsVol;
+    int         filter;
+
+    uint32_t    keyPress;
+    uint32_t    keyRelease;
+    uint32_t    kill;
+    uint32_t    arm;
+    uint32_t    volume;
+    uint32_t    mute;
+    uint32_t    solo;
+    uint32_t    pitch;
+    uint32_t    readActions;   
+
+    std::vector<PluginData> plugins;
+};
+
+struct Master_InputData
+{
+    Master_InputData() = default;
+    Master_InputData(const m::model::MidiIn&);
+
+    bool     enabled;
+    int      filter;
+
+       uint32_t rewind;
+       uint32_t startStop;
+       uint32_t actionRec;
+       uint32_t inputRec;
+       uint32_t volumeIn;
+       uint32_t volumeOut;
+       uint32_t beatDouble;
+       uint32_t beatHalf;
+       uint32_t metronome;     
+};
 
-void keyPress  (ID channelId, bool ctrl, bool shift, int velocity);
-void keyRelease(ID channelId, bool ctrl, bool shift);
+struct MidiChannel_OutputData
+{
+    MidiChannel_OutputData(const m::MidiSender&);
+
+    bool enabled;
+    int  filter;
+};
+
+struct Channel_OutputData
+{
+    Channel_OutputData() = default;
+    Channel_OutputData(const m::Channel&);
+
+    ID       channelId;
+    bool     lightningEnabled;
+    uint32_t lightningPlaying;
+    uint32_t lightningMute;
+    uint32_t lightningSolo;
+
+    std::optional<MidiChannel_OutputData> output;
+};
 
-/* setSampleChannelKey
+Channel_InputData  channel_getInputData(ID channelId);
+Channel_OutputData channel_getOutputData(ID channelId);
+Master_InputData   master_getInputData();
+
+/* Channel functions. */
+
+void channel_enableMidiLearn(ID channelId, bool v);
+void channel_enableMidiLightning(ID channelId, bool v);
+void channel_enableMidiOutput(ID channelId, bool v);
+void channel_enableVelocityAsVol(ID channelId, bool v);
+void channel_setMidiInputFilter(ID channelId, int c);
+void channel_setMidiOutputFilter(ID channelId, int c);
+
+/* channel_setKey
 Set key 'k' to Sample Channel 'channelId'. Used for keyboard bindings. */
 
-void setSampleChannelKey(ID channelId, int k);
+void channel_setKey(ID channelId, int k);
 
-void startChannelMidiLearn(int param, ID channelId);
-void startMasterMidiLearn (int param);
+/* MIDI Learning functions. */
+
+void channel_startMidiLearn(int param, ID channelId);
+void channel_clearMidiLearn(int param, ID channelId);
+void master_clearMidiLearn (int param);
+void master_startMidiLearn (int param);
 void stopMidiLearn();
-void clearChannelMidiLearn(int param, ID channelId);
-void clearMasterMidiLearn (int param);
 #ifdef WITH_VST
-void startPluginMidiLearn (int paramIndex, ID pluginId);
-void clearPluginMidiLearn (int param, ID pluginId);
+void plugin_startMidiLearn (int paramIndex, ID pluginId);
+void plugin_clearMidiLearn (int param, ID pluginId);
 #endif
+
+/* Master functions. */
+
+void master_enableMidiLearn(bool v);
+void master_setMidiFilter(int c);
 }}} // giada::c::io::
 
 #endif
index 22da7093f4f74e8f0f75181eb231036813235ba6..0e084b863ae61505f33d893bdb9890609c607ff6 100644 (file)
@@ -38,7 +38,6 @@
 #include "utils/string.h"
 #include "utils/log.h"
 #include "core/model/model.h"
-#include "core/channels/midiChannel.h"
 #include "core/mixerHandler.h"
 #include "core/mixer.h"
 #include "core/clock.h"
@@ -78,8 +77,8 @@ void setBpm_(float current, std::string s)
 
        float previous = m::clock::getBpm();
        m::clock::setBpm(current);
-       m::recorderHandler::updateBpm(previous, current, m::clock::getQuanto());
-       m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
+       m::recorderHandler::updateBpm(previous, current, m::clock::getQuantizerStep());
+       m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
 
        /* This function might get called by Jack callback BEFORE the UI is up
        and running, that is when G_MainWin == nullptr. */
@@ -99,6 +98,82 @@ void setBpm_(float current, std::string s)
 /* -------------------------------------------------------------------------- */
 
 
+Timer::Timer(const m::model::Clock& 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())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m)
+: masterOutVol       (out.state->volume.load())
+, masterInVol        (in.state->volume.load())
+#ifdef WITH_VST
+, masterOutHasPlugins(out.pluginIds.size() > 0)
+, masterInHasPlugins (in.pluginIds.size() > 0)
+#endif
+, inToOut            (m.inToOut)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float IO::a_getMasterOutPeak()
+{
+       return m::mixer::peakOut.load();
+}
+
+
+float IO::a_getMasterInPeak()
+{
+       return m::mixer::peakIn.load();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Timer getTimer()
+{
+       namespace mm = m::model;
+       
+       mm::ClockLock c(mm::clock);
+       return Timer(*mm::clock.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+IO getIO()
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       mm::MixerLock        ml(mm::mixer);
+
+       return IO(mm::get(mm::channels, m::mixer::MASTER_OUT_CHANNEL_ID), 
+                 mm::get(mm::channels, m::mixer::MASTER_IN_CHANNEL_ID),
+                         *mm::mixer.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
 void setBpm(const char* v1, const char* v2)
 {
        /* Never change this stuff while recording audio. */
@@ -152,7 +227,7 @@ void setBeats(int beats, int bars)
                return;
 
        m::clock::setBeats(beats, bars);
-       m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
+       m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
 
        G_MainWin->mainTimer->setMeter(m::clock::getBeats(), m::clock::getBars());
        u::gui::refreshActionEditor();  // in case the action editor is open
@@ -171,39 +246,9 @@ void quantize(int val)
 /* -------------------------------------------------------------------------- */
 
 
-void setOutVol(float v, bool gui)
-{
-       m::mh::setOutVol(v);
-       
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->mainIO->setOutVol(v);
-               Fl::unlock();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setInVol(float v, bool gui)
-{
-       m::mh::setInVol(v);
-
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->mainIO->setInVol(v);
-               Fl::unlock();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
 void clearAllSamples()
 {
-       if (!v::gdConfirmWin("Warning", "Clear all samples: are you sure?"))
+       if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?"))
                return;
        G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
        m::clock::setStatus(ClockStatus::STOPPED);
@@ -227,58 +272,20 @@ void clearAllActions()
 /* -------------------------------------------------------------------------- */
 
 
-void resetToInitState(bool createColumns)
-{
-       if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
-               return;
-       m::init::reset();       
-       m::mixer::enable();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void beatsMultiply()
-{
-       setBeats(m::clock::getBeats() * 2, m::clock::getBars());
-}
-
-void beatsDivide()
-{
-       setBeats(m::clock::getBeats() / 2, m::clock::getBars());
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleInputRec()
+void setInToOut(bool v)
 {
-       if (!m::recManager::toggleInputRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode)))
-               v::gdAlert("No channels armed/available for audio recording.");
+       m::mh::setInToOut(v);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void toggleActionRec()
+void closeProject(bool createColumns)
 {
-       m::recManager::isRecordingAction() ? stopActionRec() : startActionRec();
-}
-
-
-void startActionRec()
-{
-       m::recManager::startActionRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode));
-}
-
-
-void stopActionRec()
-{
-       m::recManager::stopActionRec();
-       u::gui::refreshActionEditor();  // If Action Editor window is open
+       if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
+               return;
+       m::init::reset();
+       m::mixer::enable();
 }
-
 }}} // giada::c::main::
index 7802a7200ed1840bcd4bd29044fd030c8cef5874..c0e849018ba0acbb340ec63a6300b21a9783f819 100644 (file)
 
 
 namespace giada {
+namespace m 
+{
+struct Channel;
+namespace model
+{ 
+struct Clock;
+struct Mixer;
+}}
 namespace c {
 namespace main
 {
+struct Timer
+{
+    Timer() = default;
+    Timer(const m::model::Clock& c);
+
+    float bpm;
+    int   beats;
+    int   bars;
+    int   quantize;
+    bool  isUsingJack;
+    bool  isRecordingInput;
+};
+
+struct IO
+{
+    IO() = default;
+    IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m);
+
+    float masterOutVol;
+    float masterInVol;
+#ifdef WITH_VST
+    bool  masterOutHasPlugins;
+    bool  masterInHasPlugins;
+#endif
+    bool  inToOut;
+
+    float a_getMasterOutPeak();
+    float a_getMasterInPeak();
+};
+
+/* get*
+Returns viewModel objects filled with data. */
+
+Timer getTimer();
+IO getIO();
+
 /* setBpm (1)
 Sets bpm value from string to float. */
 
@@ -45,46 +89,19 @@ void setBpm(float v);
 
 void setBeats(int beats, int bars);
 void quantize(int val);
-void setOutVol(float v, bool gui=true);
-void setInVol(float v, bool gui=true);
 void clearAllSamples();
 void clearAllActions();
 
-/* resetToInitState
-Resets Giada to init state. If resetGui also refresh all widgets. If 
-createColumns also build initial empty columns. */
-
-void resetToInitState(bool createColumns);
-
-/* beatsDivide/Multiply
-Shrinks or enlarges the number of beats by 2. */
-
-void beatsMultiply();
-void beatsDivide();
-
+/* setInToOut
+Enables the "hear what you playing" feature. */
 
+void setInToOut(bool v);
 
+/* closeProject
+Resets Giada to init state. If resetGui also refresh all widgets. If 
+createColumns also build initial empty columns. */
 
-
-
-
-
-
-void rewind();
-void play();
-
-/* toggleInputRec
-Handles the input recording.*/
-
-void toggleInputRec();
-
-void toggleActionRec();
-void startActionRec();
-void stopActionRec();
-
-
-void toggleMetronome();
-
+void closeProject(bool createColumns);
 }}} // giada::c::main::
 
 #endif
index 9afc65e3767d40d8e11009a3589dd11b12781042..9120b19dee0ea8ddb77eee48bc80ffe131f811ff 100644 (file)
@@ -31,7 +31,6 @@
 #include <cassert>
 #include <FL/Fl.H>
 #include "core/model/model.h"
-#include "core/channels/channel.h"
 #include "core/pluginManager.h"
 #include "core/pluginHost.h"
 #include "core/mixer.h"
@@ -55,9 +54,94 @@ namespace giada {
 namespace c {
 namespace plugin 
 {
-namespace
+Param::Param(const m::Plugin& p, int index)
+: index   (index)
+, pluginId(p.id)
+, name    (p.getParameterName(index))
+, text    (p.getParameterText(index))
+, label   (p.getParameterLabel(index))
+, value   (p.getParameter(index))
 {
-void updatePluginEditor_(ID pluginId, bool gui)
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugin::Plugin(const m::Plugin& p, ID channelId)
+: id            (p.id)
+, channelId     (channelId)
+, valid         (p.valid)
+, hasEditor     (p.hasEditor())
+, isBypassed    (p.isBypassed())
+, name          (p.getName())
+, uniqueId      (p.getUniqueId())
+, currentProgram(p.getCurrentProgram())
+, m_plugin      (p)
+{
+       for (int i = 0; i < p.getNumPrograms(); i++)
+               programs.push_back({ i, p.getProgramName(i) });
+       for (int i = 0; i < p.getNumParameters(); i++)
+               paramIndexes.push_back(i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+juce::AudioProcessorEditor* Plugin::createEditor() const
+{
+       m::model::PluginsLock l(m::model::plugins);
+       return m_plugin.createEditor();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugins::Plugins(const m::Channel& c)
+: channelId(c.id)
+, pluginIds(c.pluginIds) 
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugins getPlugins(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       return Plugins(mm::get(mm::channels, channelId));
+}
+
+
+Plugin getPlugin(ID pluginId, ID channelId)
+{
+       m::model::PluginsLock l(m::model::plugins);
+       return Plugin(m::model::get(m::model::plugins, pluginId), channelId);
+}
+
+
+Param getParam (int index, ID pluginId)
+{
+       m::model::PluginsLock l(m::model::plugins);
+       return Param(m::model::get(m::model::plugins, pluginId), index);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateWindow(ID pluginId, bool gui)
 {
        m::model::PluginsLock l(m::model::plugins);
        const m::Plugin& p = m::model::get(m::model::plugins, pluginId);
@@ -79,11 +163,8 @@ void updatePluginEditor_(ID pluginId, bool gui)
        child->updateParameters(!gui);
        if (!gui) Fl::unlock();
 }
-} // {anonymous}
 
 
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 
@@ -121,17 +202,7 @@ void freePlugin(ID pluginId, ID channelId)
 void setProgram(ID pluginId, int programIndex)
 {
        m::pluginHost::setPluginProgram(pluginId, programIndex); 
-       updatePluginEditor_(pluginId, /*gui=*/true); 
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setParameter(ID pluginId, int paramIndex, float value, bool gui)
-{
-       m::pluginHost::setPluginParameter(pluginId, paramIndex, value); 
-       updatePluginEditor_(pluginId, gui); 
+       updateWindow(pluginId, /*gui=*/true); 
 }
 
 
index 52d79a05ddb45435a87094bc3c3d1070f440e019..a5afe3c4ffb043e86c6bfc2b4a7b962bb8595bc0 100644 (file)
 #ifdef WITH_VST
 
 
+#include <vector>
+#include <string>
 #include "core/pluginHost.h"
 #include "core/types.h"
 
 
+namespace juce {
+class AudioProcessorEditor;
+}
+
+
 namespace giada {
 namespace m
 {
@@ -45,16 +52,75 @@ class Channel;
 namespace c {
 namespace plugin 
 {
-void addPlugin(int pluginListIndex, ID channelId);
+struct Program
+{
+    int         index;
+    std::string name;
+};
 
-void swapPlugins(ID pluginId1, ID pluginId2, ID channelId);
+struct Param
+{
+    Param() = default;
+    Param(const m::Plugin&, int index);
+
+    int         index;
+    ID          pluginId;
+    std::string name;
+    std::string text;
+    std::string label;
+    float       value;
+};
+
+struct Plugin
+{
+    Plugin() = default;
+    Plugin(const m::Plugin&, ID channelId);
 
-void freePlugin(ID pluginId, ID channelId);
+    juce::AudioProcessorEditor* createEditor() const;
 
-void setParameter(ID pluginId, int paramIndex, float value, bool gui=true); 
+    ID          id;
+    ID          channelId;
+    bool        valid;
+    bool        hasEditor;
+    bool        isBypassed;
+    std::string name;
+    std::string uniqueId;
+    int         currentProgram;
 
-void setProgram(ID pluginId, int programIndex);
+    std::vector<Program> programs;
+    std::vector<int>     paramIndexes;
+
+private:
+
+    const m::Plugin& m_plugin;
+};
 
+struct Plugins
+{
+    Plugins() = default;
+    Plugins(const m::Channel&);
+
+    ID channelId;
+    std::vector<ID> pluginIds; 
+};
+
+/* get*
+Returns ViewModel objects. */
+
+Plugins getPlugins(ID channelId);
+Plugin  getPlugin (ID pluginId, ID channelId);
+Param   getParam  (int index, ID pluginId);
+
+/* updateWindow
+Updates the editor-less plug-in window. This is useless if the plug-in has an
+editor. */
+
+void updateWindow(ID pluginId, bool gui);
+
+void addPlugin(int pluginListIndex, ID channelId);
+void swapPlugins(ID pluginId1, ID pluginId2, ID channelId);
+void freePlugin(ID pluginId, ID channelId);
+void setProgram(ID pluginId, int programIndex);
 void toggleBypass(ID pluginId);
 
 /* setPluginPathCb
index aacfc5afd1e3e04a047ffb9907f1a1e0a9f40f63..4eab2b260ab6490514883178e5c61b97ec676abd 100644 (file)
@@ -30,8 +30,6 @@
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/keyboard/sampleChannel.h"
 #include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/const.h"
 #include "core/clock.h"
 #include "core/model/model.h"
@@ -89,13 +87,9 @@ void clearStartStopActions(ID channelId)
 
 void updateChannel(ID channelId, bool updateActionEditor)
 {
-       /* TODO - optimization needed. This functions swaps a channel only to set a 
-       boolean flag. Query the channel first and swap it only if the flag has
-       actually changed. */
-
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               c.hasActions = m::recorder::hasActions(channelId);
+               c.state->hasActions = m::recorder::hasActions(channelId);
        });
                                
        if (updateActionEditor)
index f48219e6c877f34a8fea0af8ecaada40e98ea186..d4ae9654bc43c1f90bd59aa67be65ff65bd80cdc 100644 (file)
@@ -27,6 +27,7 @@
 
 #include <cassert>
 #include <FL/Fl.H>
+#include "glue/events.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/sampleEditor.h"
 #include "gui/dialogs/warnings.h"
@@ -42,8 +43,6 @@
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/waveFx.h"
 #include "core/wave.h"
 #include "core/waveManager.h"
 #include "core/mixerHandler.h"
@@ -68,6 +67,8 @@ A Wave used during cut/copy/paste operations. */
 
 std::unique_ptr<m::Wave> waveBuffer_;
 
+Frame previewTracker_ = 0;
+
 
 /* -------------------------------------------------------------------------- */
 
@@ -77,15 +78,31 @@ needed for the operation. */
 
 void resetBeginEnd_(ID channelId)
 {
-       Frame begin;
-       Frame end;
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
        {
-               begin = static_cast<m::SampleChannel&>(c).begin;
-               end   = static_cast<m::SampleChannel&>(c).end;
+               Frame begin = c.samplePlayer->state->begin.load();
+               Frame end   = c.samplePlayer->state->end.load();
+               setBeginEnd(channelId, begin, end);
        });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* updateWavePtr_
+Updates the Wave pointer in Channel::WaveReader. */
+
+void updateWavePtr_(ID channelId, ID waveId)
+{
+       namespace mm = m::model;
+
+       mm::WavesLock wl(mm::waves);
+       const m::Wave& wave = mm::get(mm::waves, waveId);
 
-       setBeginEnd(channelId, begin, end);
+       mm::onSwap(mm::channels, channelId, [&](m::Channel& c)
+       {
+               c.samplePlayer->loadWave(&wave);
+       });
 }
 }; // {anonymous}
 
@@ -95,6 +112,82 @@ void resetBeginEnd_(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
+Data::Data(const m::Channel& c, const m::Wave& w)
+: channelId   (c.id)
+, waveId      (w.id)
+, name        (c.state->name)
+, volume      (c.state->volume.load())
+, pan         (c.state->pan.load())
+, pitch       (c.samplePlayer->state->pitch.load())
+, begin       (c.samplePlayer->state->begin.load())
+, end         (c.samplePlayer->state->end.load())
+, shift       (c.samplePlayer->state->shift.load())
+, waveSize    (w.getSize())
+, waveBits    (w.getBits())
+, waveDuration(w.getDuration())
+, waveRate    (w.getRate())
+, wavePath    (w.getPath())
+, isLogical   (w.isLogical())
+{
+}
+
+/* TODO - use c::channel::a_get() */
+ChannelStatus Data::a_getPreviewStatus() const
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock l(mm::channels);
+       return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load();
+}
+
+/* TODO - use c::channel::a_get() */
+Frame Data::a_getPreviewTracker() const
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock l(mm::channels);
+       return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).samplePlayer->state->tracker.load();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       mm::WavesLock        wl(mm::waves);
+
+       const m::Channel& channel = mm::get(mm::channels, channelId);
+       const m::Wave&        wave    = mm::get(mm::waves, channel.samplePlayer->getWaveId());
+
+       /* Prepare the preview channel. */
+
+       m::Channel& preview = mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID);
+       preview.samplePlayer->loadWave(&wave);
+
+       return Data(channel, wave);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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));
+       if (se == nullptr) 
+               return;
+       if (!gui) Fl::lock();
+       f(*se);
+       if (!gui) Fl::unlock();
+}
+
+
 v::gdSampleEditor* getSampleEditorWindow()
 {
        v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
@@ -108,12 +201,20 @@ v::gdSampleEditor* getSampleEditorWindow()
 
 void setBeginEnd(ID channelId, int b, int e)
 {
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               static_cast<m::SampleChannel&>(c).setBegin(b);
-               static_cast<m::SampleChannel&>(c).setEnd(e);
+               b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1);
+               e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1);
+               if      (b >= e) b = e - 1;
+               else if (e < b)  e = b + 1;
+
+               c.samplePlayer->state->begin.store(b);
+               c.samplePlayer->state->end.store(e);
+               if (c.samplePlayer->state->tracker.load() < b)
+                       c.samplePlayer->state->tracker.store(b);
        });
 
+       /* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */
        getSampleEditorWindow()->rebuild();
 }
 
@@ -125,6 +226,7 @@ void cut(ID channelId, ID waveId, int a, int b)
 {
        copy(waveId, a, b);
        m::wfx::cut(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
        resetBeginEnd_(channelId);
 }
 
@@ -135,7 +237,6 @@ void cut(ID channelId, ID waveId, int a, int b)
 void copy(ID waveId, int a, int b)
 {
        m::model::WavesLock lock(m::model::waves);
-
        waveBuffer_ = m::waveManager::createFromWave(m::model::get(m::model::waves, waveId), a, b);
 }
 
@@ -151,6 +252,7 @@ void paste(ID channelId, ID waveId, int a)
        }
 
        m::wfx::paste(*waveBuffer_, waveId, a);
+       updateWavePtr_(channelId, waveId);
 
        /* Shift begin/end points to keep the previous position. */
 
@@ -160,8 +262,8 @@ void paste(ID channelId, ID waveId, int a)
 
        m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               begin = static_cast<m::SampleChannel&>(c).begin;
-               end   = static_cast<m::SampleChannel&>(c).end;
+               begin = c.samplePlayer->state->begin.load();
+               end   = c.samplePlayer->state->end.load();
        });
 
        if (a < begin && a < end)
@@ -177,45 +279,50 @@ void paste(ID channelId, ID waveId, int a)
 /* -------------------------------------------------------------------------- */
 
 
-void silence(ID waveId, int a, int b)
+void silence(ID channelId, ID waveId, int a, int b)
 {
        m::wfx::silence(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void fade(ID waveId, int a, int b, int type)
+void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type)
 {
        m::wfx::fade(waveId, a, b, type);
+       updateWavePtr_(channelId, waveId);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void smoothEdges(ID waveId, int a, int b)
+void smoothEdges(ID channelId, ID waveId, int a, int b)
 {
        m::wfx::smooth(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void reverse(ID waveId, int a, int b)
+void reverse(ID channelId, ID waveId, int a, int b)
 {
        m::wfx::reverse(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void normalizeHard(ID waveId, int a, int b)
+void normalize(ID channelId, ID waveId, int a, int b)
 {
-       m::wfx::normalizeHard(waveId, a, b);
+       m::wfx::normalize(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
 }
 
 
@@ -225,6 +332,7 @@ void normalizeHard(ID waveId, int a, int b)
 void trim(ID channelId, ID waveId, int a, int b)
 {
        m::wfx::trim(waveId, a, b);
+       updateWavePtr_(channelId, waveId);
        resetBeginEnd_(channelId);
 }
 
@@ -232,49 +340,58 @@ void trim(ID channelId, ID waveId, int a, int b)
 /* -------------------------------------------------------------------------- */
 
 
-void setPlayHead(ID channelId, Frame f)
-{
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               static_cast<m::SampleChannel&>(c).trackerPreview = f;
-       });
-       getSampleEditorWindow()->refresh();
+/* TODO - this arcane logic of keeping previewTracker_ will go away as soon as
+the One-shot pause mode is implemented: 
+       https://github.com/monocasual/giada/issues/88 */
+
+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);
 }
 
 
-/* -------------------------------------------------------------------------- */
+void stopPreview()
+{
+       /* Let the Sample Editor show the initial tracker position, then kill the
+       channel. */
+       setPreviewTracker(previewTracker_);
+       getSampleEditorWindow()->refresh();
+       events::killChannel(m::mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
+}
 
 
-void setPreview(ID channelId, PreviewMode mode)
+void setPreviewTracker(Frame f)
 {
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+       namespace mm = m::model;
+
+       mm::onGet(mm::channels, m::mixer::PREVIEW_CHANNEL_ID, [&](m::Channel& c)
        {
-               static_cast<m::SampleChannel&>(c).previewMode = mode;
+               c.samplePlayer->state->tracker.store(f);
        });
-}
 
+       previewTracker_ = f;
 
-/* -------------------------------------------------------------------------- */
+       getSampleEditorWindow()->refresh();
+}
 
 
-void rewindPreview(ID channelId)
+void cleanupPreview()
 {
-       setPlayHead(channelId, 0);
+       namespace mm = m::model;
+
+       mm::ChannelsLock cl(mm::channels);
+       mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).samplePlayer->loadWave(nullptr);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void toNewChannel(ID channelId, int a, int b)
+void toNewChannel(ID channelId, ID waveId, int a, int b)
 {
        ID columnId = G_MainWin->keyboard->getChannel(channelId)->getColumnId();
-       ID waveId;
-       
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               waveId = static_cast<m::SampleChannel&>(c).waveId;
-       });
 
        m::model::onGet(m::model::waves, waveId, [&](m::Wave& w)
        {
@@ -301,24 +418,16 @@ void reload(ID channelId, ID waveId)
                return;
 
        std::string wavePath;
-       Frame       waveSize;
-       m::model::onGet(m::model::waves, waveId, [&](m::Wave& w)
+       m::model::onGet(m::model::waves, waveId, [&](const m::Wave& w)
        {
                wavePath = w.getPath();
-               waveSize = w.getSize();
        });
 
-       if (channel::loadChannel(channelId, wavePath) != G_RES_OK)
+       if (channel::loadChannel(channelId, wavePath) != G_RES_OK) {
+               v::gdAlert("Unable to reload sample!");
                return;
+       }
 
-       ID newWaveId;
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               newWaveId = sc.waveId;
-       });
-
-       getSampleEditorWindow()->setWaveId(newWaveId);
        getSampleEditorWindow()->rebuild();
 }
 
@@ -329,17 +438,19 @@ void reload(ID channelId, ID waveId)
 void shift(ID channelId, ID waveId, int offset)
 {
        Frame shift;
-
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
        {
-               shift = static_cast<m::SampleChannel&>(c).shift;
+               shift = c.samplePlayer->state->shift.load();
        });
        
        m::wfx::shift(waveId, offset - shift);
+       updateWavePtr_(channelId, waveId);
 
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               static_cast<m::SampleChannel&>(c).shift = offset;
+               c.samplePlayer->state->shift.store(offset);
        });
+
+       getSampleEditorWindow()->shiftTool->update(offset);
 }
 }}}; // giada::c::sampleEditor::
index 8efc3d4456d07ce295cf99094169e80479bced88..6cc31b71bfcb640cde2ea2d1fb165563aa673bb2 100644 (file)
 #define G_GLUE_SAMPLE_EDITOR_H
 
 
+#include <functional>
+#include <string>
 #include "core/types.h"
+#include "core/waveFx.h"
 
 
 namespace giada {
+namespace m
+{
+class Channel;
+class Wave;
+}
+namespace v 
+{
+class gdSampleEditor;
+}
 namespace c {
 namespace sampleEditor 
 {
+struct Data
+{
+    Data() = default;
+    Data(const m::Channel&, const m::Wave&);
+
+    ChannelStatus a_getPreviewStatus() const;
+    Frame a_getPreviewTracker() const;
+
+    ID          channelId; 
+    ID          waveId; 
+    std::string name;
+    float       volume;
+    float       pan;
+    float       pitch;
+    Frame       begin;
+    Frame       end;
+    Frame       shift;
+    Frame       waveSize;
+    int         waveBits;
+    int         waveDuration;
+    int         waveRate;
+    std::string wavePath;
+    bool        isLogical;
+};
+
+/* onRefresh --- TODO - wrong name */
+
+void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f);
+
+/* getData
+Returns a Data object filled with data from a channel. */
+
+Data getData(ID channelId);
+
 /* setBeginEnd
 Sets start/end points in the sample editor. */
 
@@ -46,28 +92,25 @@ void copy(ID waveId, int a, int b);
 void paste(ID channelId, ID waveId, int a);
 
 void trim(ID channelId, ID waveId, int a, int b);
-void reverse(ID waveId, int a, int b);
-void normalizeHard(ID waveId, int a, int b);
-void silence(ID waveId, int a, int b);
-void fade(ID waveId, int a, int b, int type);
-void smoothEdges(ID waveId, int a, int b);
+void reverse(ID channelId, ID waveId, int a, int b);
+void normalize(ID channelId, ID waveId, int a, int b);
+void silence(ID channelId, ID waveId, int a, int b);
+void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type);
+void smoothEdges(ID channelId, ID waveId, int a, int b);
 void shift(ID channelId, ID waveId, int offset);
 void reload(ID channelId, ID waveId);
 
 bool isWaveBufferFull();
 
-/* setPlayHead
-Changes playhead's position. Used in preview. */
-
-void setPlayHead(ID channelId, Frame f);
-
-void setPreview(ID channelId, PreviewMode mode);
-void rewindPreview(ID channelId);
+void playPreview(bool loop);
+void stopPreview();
+void setPreviewTracker(Frame f);
+void cleanupPreview();
 
 /* toNewChannel
 Copies the selected range into a new sample channel. */
 
-void toNewChannel(ID channelId, int a, int b);
+void toNewChannel(ID channelId, ID waveId, int a, int b);
 }}}; // giada::c::sampleEditor::
 
 #endif
index 30c190491fe5346014c6a1dd4d343f2d5f494d62..f417d2ea19fbdfc0f9d939109fe1b17b95ed21cb 100644 (file)
@@ -28,9 +28,6 @@
 #include <cassert>
 #include "core/model/model.h"
 #include "core/model/storage.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/mixer.h"
 #include "core/wave.h"
 #include "core/mixerHandler.h"
@@ -92,6 +89,7 @@ std::string makeUniqueWavePath_(const std::string& base, const m::Wave& w)
        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))
@@ -125,14 +123,15 @@ bool savePatch_(const std::string& path, const std::string& name)
 /* -------------------------------------------------------------------------- */
 
 
-void saveWavesToProject_(const std::string& base)
+void saveWavesToProject_(const std::string& basePath)
 {
-       for (size_t i = 0; i < m::model::waves.size(); i++) {
-               m::model::onSwap(m::model::waves, m::model::getId(m::model::waves, i), [&](m::Wave& w)
-               {
-                       w.setPath(makeUniqueWavePath_(base, w));
-                       m::waveManager::save(w, w.getPath()); // TODO - error checking  
-               });
+       /* No need for a hard Wave swap here: nobody is reading the path data. */
+
+       m::model::WavesLock l(m::model::waves);
+
+       for (m::Wave* w : m::model::waves) {
+               w->setPath(makeUniqueWavePath_(basePath, *w));
+               m::waveManager::save(*w, w->getPath()); // TODO - error checking                        
        }
 }
 } // {anonymous}
@@ -145,7 +144,7 @@ void saveWavesToProject_(const std::string& base)
 
 void loadProject(void* data)
 {
-       v::gdBrowserLoad* browser = (v::gdBrowserLoad*) data;
+       v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
        std::string fullPath      = browser->getSelectedItem();
        bool isProject            = u::fs::isProject(browser->getSelectedItem());
 
@@ -190,10 +189,10 @@ void loadProject(void* data)
        the current samplerate != patch samplerate. Clock needs to update frames
        in sequencer. */
 
-       m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
        m::mh::updateSoloCount();
        m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate);
        m::clock::recomputeFrames();
+       m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
 
        /* Mixer is ready to go back online. */
 
@@ -221,11 +220,11 @@ void loadProject(void* data)
 
 void saveProject(void* data)
 {
-       v::gdBrowserSave* browser = (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);
+       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";
 
        if (name == "") {
                v::gdAlert("Please choose a project name.");
@@ -257,7 +256,7 @@ void saveProject(void* data)
 
 void loadSample(void* data)
 {
-       v::gdBrowserLoad* browser  = (v::gdBrowserLoad*) data;
+       v::gdBrowserLoad* browser  = static_cast<v::gdBrowserLoad*>(data);
        std::string       fullPath = browser->getSelectedItem();
 
        if (fullPath.empty())
@@ -278,9 +277,10 @@ void loadSample(void* data)
 
 void saveSample(void* data)
 {
-       v::gdBrowserSave* browser = (v::gdBrowserSave*) data;
+       v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
        std::string name          = browser->getName();
        std::string folderPath    = browser->getCurrentPath();
+       ID channelId              = browser->getChannelId();
 
        if (name == "") {
                v::gdAlert("Please choose a file name.");
@@ -293,12 +293,12 @@ void saveSample(void* data)
                return;
 
        ID waveId;
-       m::model::onGet(m::model::channels, browser->getChannelId(), [&](m::Channel& c)
+       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
        {
-               waveId = static_cast<m::SampleChannel&>(c).waveId;
+               waveId = c.samplePlayer->getWaveId();
        });
 
-       size_t waveIndex = m::model::getIndex(m::model::waves, waveId);
+       std::size_t waveIndex = m::model::getIndex(m::model::waves, waveId);
 
        std::unique_ptr<m::Wave> wave = m::model::waves.clone(waveIndex);
 
diff --git a/src/glue/transport.cpp b/src/glue/transport.cpp
deleted file mode 100644 (file)
index 905cffc..0000000
+++ /dev/null
@@ -1,163 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "../gui/elems/mainWindow/mainTransport.h"
-#include "../gui/dialogs/mainWindow.h"
-#include "../core/clock.h"
-#include "../core/conf.h"
-#include "../core/const.h"
-#include "../core/kernelAudio.h"
-#include "../core/kernelMidi.h"
-#include "../core/mixerHandler.h"
-#include "../core/mixer.h"
-#include "../core/recorder.h"
-#include "../core/recManager.h"
-#include "transport.h"
-
-
-extern gdMainWindow* G_MainWin;
-
-using namespace giada::m;
-
-
-namespace giada {
-namespace c {
-namespace transport 
-{
-void startStopSeq(bool gui)
-{
-       clock::isRunning() ? stopSeq(gui) : startSeq(gui);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startSeq(bool gui)
-{
-       switch (clock::getStatus()) {
-               case ClockStatus::STOPPED:
-                       clock::setStatus(ClockStatus::RUNNING); 
-                       break;
-               case ClockStatus::WAITING:
-                       clock::setStatus(ClockStatus::RUNNING); 
-                       m::recManager::stopActionRec();
-                       G_MainWin->mainTransport->setRecTriggerModeActive(true);
-                       G_MainWin->mainTransport->updateRecAction(0);
-                       G_MainWin->mainTransport->updateRecInput(0);
-                       break;
-               default: 
-                       break;
-       }
-
-#if defined(__linux__) || defined(__FreeBSD__)
-       kernelAudio::jackStart();
-#endif
-
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->mainTransport->updatePlay(1);
-               Fl::unlock();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopSeq(bool gui)
-{
-       mh::stopSequencer();
-
-#if defined(__linux__) || defined(__FreeBSD__)
-       kernelAudio::jackStop();
-#endif
-
-       /* What to do if we stop the sequencer and some action recs are active?
-       Deactivate the button and delete any 'rec on' status. */
-
-       if (recorder::isActive()) {
-               recorder::disable();
-               Fl::lock();
-               G_MainWin->mainTransport->updateRecAction(0);
-               Fl::unlock();
-       }
-
-       /* If input recs are active (who knows why) we must deactivate them. One 
-       might stop the sequencer while an input rec is running. */
-
-       if (mixer::recording) {
-               mh::stopInputRec();
-               Fl::lock();
-               G_MainWin->mainTransport->updateRecInput(0);
-               Fl::unlock();
-       }
-
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->mainTransport->updatePlay(0);
-               Fl::unlock();
-       }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindSeq(bool gui, bool notifyJack)
-{
-       mh::rewindSequencer();
-
-       /* FIXME - potential desync when Quantizer is enabled from this point on.
-       Mixer would wait, while the following calls would be made regardless of its
-       state. */
-
-#ifdef __linux__
-       if (notifyJack)
-               kernelAudio::jackSetPosition(0);
-#endif
-
-       if (conf::midiSync == MIDI_SYNC_CLOCK_M)
-               kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleMetronome(bool gui)
-{
-       mixer::toggleMetronome();
-       if (!gui) {
-               Fl::lock();
-               G_MainWin->mainTransport->updateMetronome(mixer::isMetronomeOn());
-               Fl::unlock();
-       }
-}
-
-}}} // giada::c::transport::
\ No newline at end of file
index c147abb8a83ec6253beba8e92dd5d75ac43862ca..7571c8e7b2f462e59ced448e60d68df52833d4aa 100644 (file)
 #include <FL/fl_draw.H>
 #include "utils/gui.h"
 #include "utils/string.h"
-#include "core/channels/channel.h"
-#include "core/model/model.h"
 #include "core/conf.h"
+#include "core/action.h"
 #include "core/const.h"
 #include "core/clock.h"
+#include "core/midiEvent.h"
+#include "glue/channel.h"
 #include "gui/elems/actionEditor/gridTool.h"
-#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/scrollPack.h"
 #include "gui/elems/basics/choice.h"
 #include "baseActionEditor.h"
 
@@ -46,9 +47,9 @@ namespace giada {
 namespace v
 {
 gdBaseActionEditor::gdBaseActionEditor(ID channelId)
-:      gdWindow (640, 284),
-       channelId(channelId),
-       ratio    (G_DEFAULT_ZOOM_RATIO)
+: gdWindow (640, 284)
+, channelId(channelId)
+, ratio    (G_DEFAULT_ZOOM_RATIO)
 {
        using namespace giada::m;
 
@@ -85,15 +86,6 @@ void gdBaseActionEditor::cb_zoomOut(Fl_Widget* w, void* p) { ((gdBaseActionEdito
 /* -------------------------------------------------------------------------- */
 
 
-std::vector<m::Action> gdBaseActionEditor::getActions() const
-{
-       return m_actions;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
 void gdBaseActionEditor::computeWidth()
 {
        fullWidth = frameToPixel(m::clock::getFramesInSeq());
@@ -198,12 +190,9 @@ void gdBaseActionEditor::prepareWindow()
 {
        u::gui::setFavicon(this);
 
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               std::string l = "Action Editor";
-               if (c.name != "") l += " - " + c.name;
-               copy_label(l.c_str());
-       });
+       std::string l = "Action Editor";
+       if (m_data.channelName != "") l += " - " + m_data.channelName;
+       copy_label(l.c_str());
 
        set_non_modal();
        size_range(640, 284);
index b874aa9d9ca411514c87ffb751fe344df9d257d7..f40e4b1ff57ee5af813d3bc7d3617f95a30ec9c8 100644 (file)
 
 
 #include "core/types.h"
+#include "glue/actionEditor.h"
 #include "gui/dialogs/window.h"
 
 
 class geChoice;
 class geButton;
-class geScroll;
 
 
 namespace giada {
@@ -47,8 +47,7 @@ struct Action;
 namespace v
 {
 class geGridTool;
-
-
+class geScrollPack;
 class gdBaseActionEditor : public gdWindow
 {
 public:
@@ -60,16 +59,15 @@ public:
        Pixel frameToPixel(Frame f) const;
        Frame pixelToFrame(Pixel p, bool snap=true) const;
        int getActionType() const;
-       std::vector<m::Action> getActions() const;
-
-       geChoice*   actionType;
-       geGridTool* gridTool;
-       geButton*   zoomInBtn;
-       geButton*   zoomOutBtn;
-       geScroll*   viewport;       // widget container
 
        ID channelId;
 
+       geChoice*     actionType;
+       geGridTool*   gridTool;
+       geButton*     zoomInBtn;
+       geButton*     zoomOutBtn;
+       geScrollPack* viewport;       // widget container
+
        float ratio;
        Pixel fullWidth;     // Full widgets width, i.e. scaled-down full sequencer
        Pixel loopWidth;         // Loop width, i.e. scaled-down sequencer range
@@ -81,8 +79,6 @@ protected:
        static constexpr float MIN_RATIO     = 25.0f;
        static constexpr float MAX_RATIO     = 40000.0f;
 
-       std::vector<m::Action> m_actions;
-
        gdBaseActionEditor(ID channelId);
 
        void zoomIn();
@@ -99,6 +95,8 @@ protected:
        void centerViewportOut();
 
        void prepareWindow();
+
+       c::actionEditor::Data m_data;
 };
 }} // giada::v::
 
index 5dc98c40183d0a93fc6f793afe97355cda8d5bdd..6b5ec7f5b64001180211ac3c2f83dd94d7f37324 100644 (file)
 
 
 #include <string>
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
 #include "core/graphics.h"
 #include "glue/actionEditor.h"
-#include "gui/elems/basics/scroll.h"
+#include "glue/channel.h"
+#include "gui/elems/basics/scrollPack.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/resizerBar.h"
 #include "gui/elems/basics/box.h"
@@ -47,39 +46,41 @@ namespace v
 gdMidiActionEditor::gdMidiActionEditor(ID channelId)
 : gdBaseActionEditor(channelId)
 {
-       computeWidth();
-
-       Fl_Group* upperArea = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w()-16, G_GUI_UNIT);
-
-       upperArea->begin();
+       end();
 
-               gridTool = new geGridTool(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+       computeWidth();
 
-               geBox *b1  = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, G_GUI_UNIT);    // padding actionType - zoomButtons
-               zoomInBtn  = new geButton(w()-8-40-4, G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
-               zoomOutBtn = new geButton(w()-8-20,   G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm);
-       
-       upperArea->end();
+       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);
 
-       zoomInBtn->callback(cb_zoomIn, (void*)this);
-       zoomOutBtn->callback(cb_zoomOut, (void*)this);
-
        /* Main viewport: contains all widgets. */
 
-       viewport = new geScroll(G_GUI_OUTER_MARGIN, upperArea->y()+upperArea->h()+G_GUI_OUTER_MARGIN, w()-16, h()-44);
-
-       m_ne  = new geNoteEditor(viewport->x(), viewport->y(), this);
-       m_ner = new geResizerBar(m_ne->x(), m_ne->y()+m_ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+       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);
-       
-       m_ve  = new geVelocityEditor(viewport->x(), m_ne->y()+m_ne->h()+RESIZER_BAR_H);
-       m_ver = new geResizerBar(m_ve->x(), m_ve->y()+m_ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
        viewport->add(m_ve);
        viewport->add(m_ver);
 
-       end();
+       zoomInBtn->callback(cb_zoomIn, (void*)this);
+       zoomOutBtn->callback(cb_zoomOut, (void*)this);
+
+       add(upperArea);
+       add(viewport);
+       resizable(upperArea);
+
        prepareWindow();
        rebuild();
 }
@@ -90,12 +91,12 @@ gdMidiActionEditor::gdMidiActionEditor(ID channelId)
 
 void gdMidiActionEditor::rebuild()
 {
-       m_actions = c::actionEditor::getActions(channelId);
+       m_data = c::actionEditor::getData(channelId);
 
        computeWidth();
-       m_ne->rebuild();
+       m_ne->rebuild(m_data);
        m_ner->size(m_ne->w(), m_ner->h());
-       m_ve->rebuild();
+       m_ve->rebuild(m_data);
        m_ver->size(m_ve->w(), m_ver->h());
 }
 }} // giada::v::
index e5b7a4424e5e01cae9cd296036af2c564dca01aa..6b47af2389c4166a0fcd773ca7f7175753f385b0 100644 (file)
 
 #include <string>
 #include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
 #include "core/const.h"
 #include "core/midiEvent.h"
 #include "core/graphics.h"
 #include "glue/actionEditor.h"
-#include "gui/elems/basics/scroll.h"
+#include "glue/channel.h"
+#include "gui/elems/basics/pack.h"
+#include "gui/elems/basics/scrollPack.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/resizerBar.h"
 #include "gui/elems/basics/choice.h"
@@ -49,50 +50,53 @@ namespace v
 gdSampleActionEditor::gdSampleActionEditor(ID channelId)
 : gdBaseActionEditor(channelId)
 {
+       end();
+
        computeWidth();
 
        /* Container with zoom buttons and the action type selector. Scheme of the 
        resizable boxes: |[--b1--][actionType][--b2--][+][-]| */
 
-       Fl_Group* upperArea = new Fl_Group(8, 8, w()-16, 20);
-
-       upperArea->begin();
-
-         actionType = new geChoice(8, 8, 80, 20);
-         gridTool   = new geGridTool(actionType->x()+actionType->w()+8, 8);
-               actionType->add("Key press");
-               actionType->add("Key release");
-               actionType->add("Kill chan");
-               actionType->value(0);
-
-               if (!canChangeActionType())
-                       actionType->deactivate();
-
-               geBox* b1  = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20);    // padding actionType - zoomButtons
-               zoomInBtn  = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
-               zoomOutBtn = new geButton(w()-8-20,   8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
-
-       upperArea->end();
+       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);
+       if (!canChangeActionType())
+               actionType->deactivate();
+
        zoomInBtn->callback(cb_zoomIn, (void*)this);
        zoomOutBtn->callback(cb_zoomOut, (void*)this);
 
        /* Main viewport: contains all widgets. */
 
-       viewport = new geScroll(8, 36, w()-16, h()-44);
-
-       m_ae  = new geSampleActionEditor(viewport->x(), viewport->y());
-       m_aer = new geResizerBar(m_ae->x(), m_ae->y()+m_ae->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+       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);
-       
-       m_ee  = new geEnvelopeEditor(viewport->x(), m_ae->y()+m_ae->h()+RESIZER_BAR_H, "volume");
-       m_eer = new geResizerBar(m_ee->x(), m_ee->y()+m_ee->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
        viewport->add(m_ee);
        viewport->add(m_eer);
 
-       end();
+       add(upperArea);
+       add(viewport);
+       resizable(upperArea);
+
        prepareWindow();
        rebuild();
 }
@@ -103,13 +107,8 @@ gdSampleActionEditor::gdSampleActionEditor(ID channelId)
 
 bool gdSampleActionEditor::canChangeActionType()
 {
-       bool res;
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               const m::SampleChannel& sc = static_cast<const m::SampleChannel&>(c); 
-               res = sc.mode != ChannelMode::SINGLE_PRESS && !sc.isAnyLoopMode();
-       });
-       return res;
+       return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS && 
+              m_data.sample->isLoopMode == false;
 }
 
 
@@ -118,14 +117,14 @@ bool gdSampleActionEditor::canChangeActionType()
 
 void gdSampleActionEditor::rebuild()
 {
-       m_actions = c::actionEditor::getActions(channelId);
+       m_data = c::actionEditor::getData(channelId);
 
        canChangeActionType() ? actionType->activate() : actionType->deactivate(); 
        computeWidth();
        
-       m_ae->rebuild();
+       m_ae->rebuild(m_data);
        m_aer->size(m_ae->w(), m_aer->h());
-       m_ee->rebuild();        
+       m_ee->rebuild(m_data);  
        m_eer->size(m_ee->w(), m_eer->h());
 }
 }} // giada::v::
index 02d987eea0332c96f7d51de9653d6128f0085fec..6f47840bed10c5c097c64ec9779e65390cb15c79 100644 (file)
@@ -40,7 +40,6 @@ namespace v
 {
 class geSampleActionEditor;
 class geEnvelopeEditor;
-
 class gdSampleActionEditor : public gdBaseActionEditor
 {  
 public:
@@ -51,13 +50,13 @@ public:
 
 private:
 
+       bool canChangeActionType();
+
        geSampleActionEditor* m_ae;
     geResizerBar*         m_aer;
 
        geEnvelopeEditor*     m_ee;
     geResizerBar*         m_eer;
-
-       bool canChangeActionType();
 };
 }} // giada::v::
 
index 764e267787789cc9141f092c24417fd95a297d0b..2d3a0cf1a0cbca4c385d60ab1100f820a550bd3a 100644 (file)
@@ -27,7 +27,6 @@
 
 #include "glue/channel.h"
 #include "utils/gui.h"
-#include "core/channels/channel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/conf.h"
@@ -40,9 +39,9 @@
 namespace giada {
 namespace v 
 {
-gdChannelNameInput::gdChannelNameInput(ID channelId)
-: gdWindow   (u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"),
-  m_channelId(channelId)
+gdChannelNameInput::gdChannelNameInput(const c::channel::Data& d)
+: gdWindow(u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"),
+  m_data  (d)
 {
        set_modal();
 
@@ -51,10 +50,7 @@ gdChannelNameInput::gdChannelNameInput(ID channelId)
        m_cancel = new geButton(m_ok->x() - 70 - G_GUI_OUTER_MARGIN, m_ok->y(), 70, G_GUI_UNIT, "Cancel");
        end();
 
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               m_name->value(c.name.c_str());
-       });
+       m_name->value(m_data.name.c_str());
 
        m_ok->shortcut(FL_Enter);
        m_ok->callback(cb_update, (void*)this);
@@ -88,7 +84,7 @@ void gdChannelNameInput::cb_cancel()
 
 void gdChannelNameInput::cb_update()
 {
-       c::channel::setName(m_channelId, m_name->value());
+       c::channel::setName(m_data.id, m_name->value());
        do_callback();
 }
 
index 1da6c7f9fcf3f9b4d6a78d86756599880ef4a706..9ba9b0b3143fe278a6e102d0c8a5c6f9f140f677 100644 (file)
@@ -43,7 +43,7 @@ class gdChannelNameInput : public gdWindow
 {
 public:
 
-       gdChannelNameInput(ID channelId);
+       gdChannelNameInput(const c::channel::Data& d);
 
 private:
 
@@ -52,7 +52,7 @@ private:
        void cb_update();
        void cb_cancel();
 
-       ID m_channelId;
+       const c::channel::Data& m_data;
 
        geInput*  m_name;
        geButton* m_ok;
index 7457f91e32de416dd86134c6fb618ad2a6ba18a1..71d8fa7f062f9a656ee087330ce045e23ede4c6d 100644 (file)
@@ -30,9 +30,6 @@
 #include "utils/string.h"
 #include "glue/io.h"
 #include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
 #include "core/conf.h"
 #include "utils/log.h"
 #include "gui/elems/basics/box.h"
@@ -50,9 +47,9 @@ extern giada::v::gdMainWindow* mainWin;
 namespace giada {
 namespace v 
 {
-gdKeyGrabber::gdKeyGrabber(ID channelId)
-: gdWindow   (300, 126, "Key configuration"), 
-  m_channelId(channelId)
+gdKeyGrabber::gdKeyGrabber(const c::channel::Data& d)
+: gdWindow(300, 126, "Key configuration")
+, m_data  (d)
 {
        begin();
        m_text   = new geBox(8, 8, 284, 80, "");
@@ -63,7 +60,7 @@ gdKeyGrabber::gdKeyGrabber(ID channelId)
        m_clear->callback(cb_clear, (void*)this);
        m_cancel->callback(cb_cancel, (void*)this);
 
-       rebuild();
+       updateText(m_data.key);
 
        u::gui::setFavicon(this);
        set_modal();
@@ -76,10 +73,7 @@ gdKeyGrabber::gdKeyGrabber(ID channelId)
 
 void gdKeyGrabber::rebuild()
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               updateText(c.key);
-       });
+       updateText(m_data.key);
 }
 
 
@@ -114,7 +108,7 @@ void gdKeyGrabber::cb_clear()
 
 void gdKeyGrabber::setButtonLabel(int key)
 {
-       c::io::setSampleChannelKey(m_channelId, key);
+       c::io::channel_setKey(m_data.id, key);
 }
 
 /* -------------------------------------------------------------------------- */
@@ -124,7 +118,7 @@ void gdKeyGrabber::updateText(int key)
 {
        std::string tmp = "Press a key.\n\nCurrent binding: ";
        if (key != 0)
-               tmp += static_cast<char>(key);
+               tmp += static_cast<wchar_t>(key);
        else
                tmp += "[none]";
        m_text->copy_label(tmp.c_str());
@@ -148,7 +142,7 @@ int gdKeyGrabber::handle(int e)
                            && x != FL_End
                            && x != ' ')
                        {
-                               u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_channelId);
+                               u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_data.id);
                                setButtonLabel(x);
                                updateText(x);
                                break;
index a258fae3845c9df4b4dd9fe7ae372c21f6b42c53..90b058cc6bcdc094d7c85f83a5bced96a6a7d4f7 100644 (file)
@@ -38,13 +38,18 @@ class geButton;
 
 
 namespace giada {
+namespace c {
+namespace channel
+{
+struct Data;
+}}
 namespace v 
 {
 class gdKeyGrabber : public gdWindow
 {
 public:
 
-       gdKeyGrabber(ID channelId);
+       gdKeyGrabber(const c::channel::Data& d);
 
        int handle(int e) override;
        void rebuild() override;
@@ -59,7 +64,7 @@ private:
        void setButtonLabel(int key);
        void updateText(int key);
        
-       ID m_channelId;
+       const c::channel::Data& m_data;
 
        geBox*    m_text;
        geButton* m_clear;
index 5ad9e149c512e57ae06a8602f0e14bacab9e66b7..49db4ad90952f8948062d03da0e7aafa939f154e 100644 (file)
@@ -63,7 +63,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg
 
        mainMenu      = new v::geMainMenu(8, 0);
 #if defined(WITH_VST)
-       mainIO        = new v::geMainIO(408, 8);
+       mainIO        = new v::geMainIO(412, 8);
 #else
        mainIO        = new v::geMainIO(476, 8);
 #endif
index 14d78dc6c6339772c0f8d947897110ba212749cd..1bd2cf8be9806b1b8911099332f3cc183864bc2e 100644 (file)
@@ -56,16 +56,6 @@ gdMidiInputBase::~gdMidiInputBase()
 /* -------------------------------------------------------------------------- */
 
 
-void gdMidiInputBase::refresh()
-{
-       for (geMidiLearnerBase* l : m_learners)
-               l->refresh();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
 void gdMidiInputBase::cb_close(Fl_Widget* w, void* p) { ((gdMidiInputBase*)p)->cb_close(); }
 
 
index 068e78b94d98460694143c2d6b0d66c8c84c4b7f..8836d32b594975b5ec10c197d36a4aba1558dfa7 100644 (file)
@@ -30,7 +30,7 @@
 
 
 #include "gui/dialogs/window.h"
-#include "gui/elems/midiIO/midiLearnerBase.h"
+#include "gui/elems/midiIO/midiLearner.h"
 
 
 class geButton;
@@ -47,19 +47,13 @@ public:
 
        virtual ~gdMidiInputBase();
 
-       void refresh() override;
-
 protected:
 
-       gdMidiInputBase(int x, int y, int w, int h, const char* title);
-
-       static const int LEARNER_WIDTH = 284;
+       gdMidiInputBase(int x, int y, int w, int h, const char* title="");
 
        static void cb_close(Fl_Widget* w, void* p);
        void cb_close();
 
-       std::vector<geMidiLearnerBase*> m_learners;
-
        geButton* m_ok;
        geCheck*  m_enable;
        geChoice* m_channel;
index 32d85df766ec4d3f9bd74164aa1827f71953f56b..434f758a1b1679352aa5bf1853fc56685875e697 100644 (file)
 #include <FL/Fl_Pack.H>
 #include "utils/gui.h"
 #include "utils/log.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
 #include "core/const.h"
 #include "core/conf.h"
 #ifdef WITH_VST
 #include "core/plugin.h"
 #endif
 #include "utils/string.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
-#include "gui/elems/midiIO/midiLearnerPlugin.h"
-#include "gui/elems/basics/scroll.h"
+#include "gui/elems/midiIO/midiLearner.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
+#include "gui/elems/basics/scrollPack.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/choice.h"
 #include "gui/elems/basics/check.h"
+#include "gui/elems/basics/group.h"
 #include "midiInputChannel.h"
 
 
 namespace giada {
 namespace v 
 {
-gdMidiInputChannel::gdMidiInputChannel(ID channelId)
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, m::conf::conf.midiInputW, 
-       m::conf::conf.midiInputH, "MIDI Input Setup"),
-  m_channelId    (channelId)
+geChannelLearnerPack::geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& channel)
+: geMidiLearnerPack(x, y, "Channel")
 {
-       m::model::ChannelsLock l(m::model::channels);
-       const m::Channel& c = m::model::get(m::model::channels, m_channelId);
+       setCallbacks(
+               [channelId=channel.channelId] (int param) { c::io::channel_startMidiLearn(param, channelId); },
+               [channelId=channel.channelId] (int param) { c::io::channel_clearMidiLearn(param, channelId); }
+       );
+       addMidiLearner("keyPress",     G_MIDI_IN_KEYPRESS);
+       addMidiLearner("key release",  G_MIDI_IN_KEYREL);
+       addMidiLearner("key kill",     G_MIDI_IN_KILL);
+       addMidiLearner("arm",          G_MIDI_IN_ARM);
+       addMidiLearner("mute",         G_MIDI_IN_MUTE);
+       addMidiLearner("solo",         G_MIDI_IN_SOLO);
+       addMidiLearner("volume",       G_MIDI_IN_VOLUME);
+       addMidiLearner("pitch",        G_MIDI_IN_PITCH,        /*visible=*/channel.channelType == ChannelType::SAMPLE);
+       addMidiLearner("read actions", G_MIDI_IN_READ_ACTIONS, /*visible=*/channel.channelType == ChannelType::SAMPLE);
+}
 
-       copy_label(std::string("MIDI Input Setup (channel " + std::to_string(c.id) + ")").c_str());
-       
-       int extra = c.type == ChannelType::SAMPLE ? 28 : 0;
 
-       Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20 + extra);
-       groupHeader->begin();
+/* -------------------------------------------------------------------------- */
 
-               m_enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, 
-                       "Enable MIDI input");
-               m_channel = new geChoice(m_enable->x()+m_enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT);
-               m_veloAsVol = new geCheck(G_GUI_OUTER_MARGIN, m_enable->y()+m_enable->h()+G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, 
-                       "Velocity drives volume (one-shot only)");
 
-       groupHeader->resizable(nullptr);
-       groupHeader->end();
+void geChannelLearnerPack::update(const c::io::Channel_InputData& d)
+{
+       learners[0]->update(d.keyPress);
+       learners[1]->update(d.keyRelease);
+       learners[2]->update(d.kill);
+       learners[3]->update(d.arm);
+       learners[4]->update(d.volume);
+       learners[5]->update(d.mute);
+       learners[6]->update(d.solo);
+       learners[7]->update(d.pitch);
+       learners[8]->update(d.readActions);
+       setEnabled(d.enabled);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 
-       m_container = new geScroll(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN, 
-               w()-16, h()-72-extra);
-       m_container->begin();
 
-               addChannelLearners();
 #ifdef WITH_VST
-               addPluginLearners();
+
+gePluginLearnerPack::gePluginLearnerPack(int x, int y, const c::io::PluginData& plugin)
+: geMidiLearnerPack(x, y, plugin.name)
+{
+       setCallbacks(
+               [pluginId=plugin.id] (int param) { c::io::plugin_startMidiLearn(param, pluginId); },
+               [pluginId=plugin.id] (int param) { c::io::plugin_clearMidiLearn(param, pluginId); }
+       );
+
+       for (const c::io::PluginParamData& param : plugin.params)
+               addMidiLearner(param.name, param.index);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginLearnerPack::update(const c::io::PluginData& d, bool enabled)
+{
+       std::size_t i = 0;
+       for (const c::io::PluginParamData& param : d.params)
+               learners[i++]->update(param.value);
+       setEnabled(enabled);
+}
+
 #endif
 
-       m_container->end();
 
-       for (auto* l : m_learners)
-               c.midiIn ? l->activate() : l->deactivate();
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiInputChannel::gdMidiInputChannel(ID channelId)
+: gdMidiInputBase(m::conf::conf.midiInputX, 
+                  m::conf::conf.midiInputY, 
+                                 m::conf::conf.midiInputW, 
+                     m::conf::conf.midiInputH)
+, m_channelId    (channelId)
+, m_data         (c::io::channel_getInputData(channelId))
+{
+       end();
+
+       copy_label(std::string("MIDI Input Setup (channel " + std::to_string(channelId) + ")").c_str());
+
+       /* Header */
+
+       geGroup* groupHeader = new geGroup(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+               m_enable    = new geCheck(0, 0, 120, G_GUI_UNIT, "Enable MIDI input");
+               m_channel   = new geChoice(m_enable->x() + m_enable->w() + 44, 0, 120, G_GUI_UNIT);
+               m_veloAsVol = new geCheck(0, m_enable->y() + m_enable->h() + G_GUI_OUTER_MARGIN, w() - 16, G_GUI_UNIT, 
+                       "Velocity drives volume (Sample Channels)");
+       groupHeader->add(m_enable);
+       groupHeader->add(m_channel);
+       groupHeader->add(m_veloAsVol);
+       groupHeader->resizable(nullptr);
+
+       /* Main scrollable content. */
 
-       Fl_Group* groupButtons = new Fl_Group(8, m_container->y()+m_container->h()+8, m_container->w(), 20);
-       groupButtons->begin();
+       m_container = new geScrollPack(G_GUI_OUTER_MARGIN, groupHeader->y() + groupHeader->h() + G_GUI_OUTER_MARGIN, 
+               w() - 16, h() - groupHeader->h() - 52);
+       m_container->add(new geChannelLearnerPack(0, 0, m_data));
+#ifdef WITH_VST
+       for (c::io::PluginData& plugin : m_data.plugins)
+               m_container->add(new gePluginLearnerPack(0, 0, plugin));
+#endif
 
-               geBox* spacer = new geBox(groupButtons->x(), groupButtons->y(), 100, 20);       // spacer window border <-> buttons
-               m_ok = new geButton(w()-88, groupButtons->y(), 80, 20, "Close");
+       /* Footer buttons. */
 
+       geGroup* groupButtons = new geGroup(G_GUI_OUTER_MARGIN, m_container->y() + m_container->h() + G_GUI_OUTER_MARGIN);
+               geBox* spacer = new geBox(0, 0, w() - 80, G_GUI_UNIT); // spacer window border <-> buttons
+               m_ok = new geButton(w() - 96, 0, 80, G_GUI_UNIT, "Close");
+       groupButtons->add(spacer);
+       groupButtons->add(m_ok);
        groupButtons->resizable(spacer);
-       groupButtons->end();
 
        m_ok->callback(cb_close, (void*)this);
-
-       m_enable->value(c.midiIn);
        m_enable->callback(cb_enable, (void*)this);
 
-       if (c.type == ChannelType::SAMPLE) {
-               m_veloAsVol->value(static_cast<const m::SampleChannel&>(c).midiInVeloAsVol);
-               m_veloAsVol->callback(cb_veloAsVol, (void*)this);       
-       }
-       else
-               m_veloAsVol->hide();
-
        m_channel->add("Channel (any)");
        m_channel->add("Channel 1");
        m_channel->add("Channel 2");
@@ -127,15 +188,18 @@ gdMidiInputChannel::gdMidiInputChannel(ID channelId)
        m_channel->add("Channel 14");
        m_channel->add("Channel 15");
        m_channel->add("Channel 16");
-       m_channel->value(c.midiInFilter == -1 ? 0 : c.midiInFilter + 1);
        m_channel->callback(cb_setChannel, (void*)this);
 
-       resizable(m_container);
+       m_veloAsVol->callback(cb_veloAsVol, (void*)this);       
 
-       end();
+       add(groupHeader);
+       add(m_container);
+       add(groupButtons);
+       resizable(m_container);
 
        u::gui::setFavicon(this);
        set_modal();
+       rebuild();
        show();
 }
 
@@ -143,66 +207,39 @@ gdMidiInputChannel::gdMidiInputChannel(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
-void gdMidiInputChannel::addChannelLearners()
+void gdMidiInputChannel::rebuild()
 {
-       m::model::ChannelsLock l(m::model::channels);
-       const m::Channel& c = m::model::get(m::model::channels, m_channelId);
-
-       Fl_Pack* pack = new Fl_Pack(m_container->x(), m_container->y(), LEARNER_WIDTH, 200);
-       pack->spacing(G_GUI_INNER_MARGIN);
-       pack->begin();
-               geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, "Channel");
-               header->box(FL_BORDER_BOX);
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key press",   G_MIDI_IN_KEYPRESS, c.midiInKeyPress, m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key release", G_MIDI_IN_KEYREL,   c.midiInKeyRel,   m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key kill",    G_MIDI_IN_KILL,     c.midiInKill,     m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "arm",         G_MIDI_IN_ARM,      c.midiInArm,      m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "mute",        G_MIDI_IN_MUTE,     c.midiInVolume,   m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "solo",        G_MIDI_IN_SOLO,     c.midiInMute,     m_channelId));
-               m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "volume",      G_MIDI_IN_VOLUME,   c.midiInSolo,     m_channelId));
-               if (c.type == ChannelType::SAMPLE) {
-                       const m::SampleChannel& sc = static_cast<const m::SampleChannel&>(c);
-                       m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "pitch",        G_MIDI_IN_PITCH,        sc.midiInPitch,       m_channelId));
-                       m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "read actions", G_MIDI_IN_READ_ACTIONS, sc.midiInReadActions, m_channelId));
-               }
-       pack->end();
-}
+       m_data = c::io::channel_getInputData(m_channelId);
 
+       m_enable->value(m_data.enabled);
 
-/* -------------------------------------------------------------------------- */
-
+       if (m_data.channelType == ChannelType::SAMPLE) {
+               m_veloAsVol->activate();
+               m_veloAsVol->value(m_data.velocityAsVol);
+       }
+       else
+               m_veloAsVol->deactivate();
 
+       int i = 0;
+       static_cast<geChannelLearnerPack*>(m_container->getChild(i++))->update(m_data);
 #ifdef WITH_VST
+       for (c::io::PluginData& plugin : m_data.plugins)
+               static_cast<gePluginLearnerPack*>(m_container->getChild(i++))->update(plugin, m_data.enabled);
+#endif
 
-void gdMidiInputChannel::addPluginLearners()
-{
-       m::model::ChannelsLock cl(m::model::channels);
-       m::model::PluginsLock  ml(m::model::plugins);
-
-       m::Channel& c = m::model::get(m::model::channels, m_channelId);
-       
-       int i = 1;
-       for (ID id : c.pluginIds) {
-
-               m::Plugin& p = m::model::get(m::model::plugins, id);
-
-               Fl_Pack* pack = new Fl_Pack(m_container->x() + (i++ * (LEARNER_WIDTH + G_GUI_OUTER_MARGIN)),
-                       m_container->y(), LEARNER_WIDTH, 200);
-               pack->spacing(G_GUI_INNER_MARGIN);
-               pack->begin();
-
-                       geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, p.getName().c_str());
-                       header->box(FL_BORDER_BOX);
-
-                       for (int k = 0; k < p.getNumParameters(); k++)
-                               m_learners.push_back(new geMidiLearnerPlugin(0, 0, LEARNER_WIDTH, p.getParameterName(k), k, p.midiInParams.at(k), p.id));
+       m_channel->value(m_data.filter == -1 ? 0 : m_data.filter + 1);
 
-               pack->end();
+       if (m_data.enabled) {
+               m_channel->activate();
+               if (m_data.channelType == ChannelType::SAMPLE)
+                       m_veloAsVol->activate();
+       }
+       else {
+               m_channel->deactivate();
+               m_veloAsVol->deactivate();
        }
 }
 
-#endif
-
 
 /* -------------------------------------------------------------------------- */
 
@@ -217,15 +254,7 @@ void gdMidiInputChannel::cb_veloAsVol(Fl_Widget* w, void* p) { ((gdMidiInputChan
 
 void gdMidiInputChannel::cb_enable()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               c.midiIn = m_enable->value();
-       });
-
-       m_enable->value() ? m_channel->activate() : m_channel->deactivate();
-
-       for (auto* l : m_learners)
-               m_enable->value() ? l->activate() : l->deactivate();
+       c::io::channel_enableMidiLearn(m_data.channelId, m_enable->value());
 }
 
 
@@ -234,10 +263,7 @@ void gdMidiInputChannel::cb_enable()
 
 void gdMidiInputChannel::cb_veloAsVol()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               static_cast<m::SampleChannel&>(c).midiInVeloAsVol = m_veloAsVol->value();
-       });
+       c::io::channel_enableVelocityAsVol(m_data.channelId, m_veloAsVol->value());
 }
 
 
@@ -246,10 +272,7 @@ void gdMidiInputChannel::cb_veloAsVol()
 
 void gdMidiInputChannel::cb_setChannel()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               c.midiInFilter = m_channel->value() == 0 ? -1 : m_channel->value() - 1;
-               u::log::print("[gdMidiInputChannel] Set MIDI channel to %d\n", c.midiInFilter);
-       });
+       c::io::channel_setMidiInputFilter(m_data.channelId, 
+               m_channel->value() == 0 ? -1 : m_channel->value() - 1);
 }
 }} // giada::v::
index c6f520d3d9077a446af822eb63db47fe138a4fc7..8fff86c98c2a9c44edda15a447de95b25a051a4b 100644 (file)
 #define GD_MIDI_INPUT_CHANNEL_H
 
 
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
 #include "midiInputBase.h"
 
 
-class geScroll;
 class geCheck;
 class geChoice;
 
@@ -42,13 +43,46 @@ class geChoice;
 namespace giada {
 namespace v 
 {
-class geMidiLearner;
+class geScrollPack;
+class geChannelLearnerPack : public geMidiLearnerPack
+{
+public:
+
+       geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& d);
+
+       void update(const c::io::Channel_InputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+class gePluginLearnerPack : public geMidiLearnerPack
+{
+public:
+
+       gePluginLearnerPack(int x, int y, const c::io::PluginData&);
+
+       void update(const c::io::PluginData&, bool enabled);
+};
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+
 class gdMidiInputChannel : public gdMidiInputBase
 {
 public:
 
        gdMidiInputChannel(ID channelId);
 
+       void rebuild() override;
+
 private:
 
        static void cb_enable(Fl_Widget* w, void* p);
@@ -58,18 +92,12 @@ private:
        void cb_setChannel();
        void cb_veloAsVol();
 
-       void addChannelLearners();
-
-#ifdef WITH_VST
-
-       void addPluginLearners();
-
-#endif
-
        ID m_channelId;
+       
+       c::io::Channel_InputData m_data;
 
-       geScroll* m_container;
-       geCheck*  m_veloAsVol;
+       geScrollPack* m_container;
+       geCheck*      m_veloAsVol;
 };
 }} // giada::v::
 
index 0ed0c7b8c8a565ce9923930ca1a4df65857e8135..54fea005895bf259390a4b422b1494a41d5318d6 100644 (file)
 #include "utils/gui.h"
 #include "core/conf.h"
 #include "core/const.h"
-#include "core/model/model.h"
-#include "gui/elems/midiIO/midiLearnerMaster.h"
+#include "gui/elems/midiIO/midiLearner.h"
+#include "gui/elems/basics/scrollPack.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
 #include "gui/elems/basics/choice.h"
+#include "gui/elems/basics/group.h"
 #include "midiInputMaster.h"
 
 
 namespace giada {
 namespace v 
 {
-gdMidiInputMaster::gdMidiInputMaster()
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+geMasterLearnerPack::geMasterLearnerPack(int x, int y)
+: geMidiLearnerPack(x, y)
 {
-       set_modal();
+       setCallbacks(
+               [] (int param) { c::io::master_startMidiLearn(param); },
+               [] (int param) { c::io::master_clearMidiLearn(param); }
+       );
+       addMidiLearner("rewind",           G_MIDI_IN_REWIND);
+       addMidiLearner("play/stop",        G_MIDI_IN_START_STOP);
+       addMidiLearner("action recording", G_MIDI_IN_ACTION_REC);
+       addMidiLearner("input recording",  G_MIDI_IN_INPUT_REC);
+       addMidiLearner("metronome",        G_MIDI_IN_METRONOME);
+       addMidiLearner("input volume",     G_MIDI_IN_VOLUME_IN);
+       addMidiLearner("output volume",    G_MIDI_IN_VOLUME_OUT);
+       addMidiLearner("sequencer Ã—2",     G_MIDI_IN_BEAT_DOUBLE);
+       addMidiLearner("sequencer Ã·2",     G_MIDI_IN_BEAT_HALF);
+}
 
-       m::model::midiIn.lock();
-       const m::model::MidiIn* midiIn = m::model::midiIn.get();
 
-       Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20);
-       groupHeader->begin();
+/* -------------------------------------------------------------------------- */
 
-               m_enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, 
-                       "Enable MIDI input");
-               m_channel = new geChoice(m_enable->x()+m_enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT);
 
-       groupHeader->resizable(nullptr);
-       groupHeader->end();
-
-       Fl_Pack* pack = new Fl_Pack(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN, 
-               LEARNER_WIDTH, 212);
-       pack->spacing(G_GUI_INNER_MARGIN);
-       pack->begin();
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "rewind",           G_MIDI_IN_REWIND,      midiIn->rewind));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "play/stop",        G_MIDI_IN_START_STOP,  midiIn->startStop));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "action recording", G_MIDI_IN_ACTION_REC,  midiIn->actionRec));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input recording",  G_MIDI_IN_INPUT_REC,   midiIn->inputRec));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "metronome",        G_MIDI_IN_METRONOME,   midiIn->volumeIn));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input volume",     G_MIDI_IN_VOLUME_IN,   midiIn->volumeOut));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "output volume",    G_MIDI_IN_VOLUME_OUT,  midiIn->beatDouble));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer Ã—2",     G_MIDI_IN_BEAT_DOUBLE, midiIn->beatHalf));
-               m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer Ã·2",     G_MIDI_IN_BEAT_HALF,   midiIn->metronome));
-       pack->end();
-       m_ok = new geButton(w()-88, pack->y()+pack->h()+G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
+void geMasterLearnerPack::update(const c::io::Master_InputData& d)
+{
+       learners[0]->update(d.rewind);
+       learners[1]->update(d.startStop);
+       learners[2]->update(d.actionRec);
+       learners[3]->update(d.inputRec);
+       learners[4]->update(d.metronome);       
+       learners[5]->update(d.volumeIn);
+       learners[6]->update(d.volumeOut);
+       learners[7]->update(d.beatDouble);
+       learners[8]->update(d.beatHalf);
+       setEnabled(d.enabled);
+}
+
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiInputMaster::gdMidiInputMaster()
+: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+{
        end();
 
-       for (geMidiLearnerBase* l : m_learners)
-               midiIn->enabled ? l->activate() : l->deactivate();
+       geGroup* groupHeader = new geGroup(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+               m_enable  = new geCheck(0, 0, 120, G_GUI_UNIT, "Enable MIDI input");
+               m_channel = new geChoice(m_enable->x() + m_enable->w() + 44, 0, 120, G_GUI_UNIT);
+       groupHeader->resizable(nullptr);
+       groupHeader->add(m_enable);
+       groupHeader->add(m_channel);
 
-       m_ok->callback(cb_close, (void*)this);
+       m_learners = new geMasterLearnerPack(G_GUI_OUTER_MARGIN, groupHeader->y() + groupHeader->h() + G_GUI_OUTER_MARGIN);
+       m_ok       = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
 
-       m_enable->value(midiIn->enabled);
+       add(groupHeader);
+       add(m_learners);
+       add(m_ok);
+
+       m_ok->callback(cb_close, (void*)this);
        m_enable->callback(cb_enable, (void*)this);
 
        m_channel->add("Channel (any)");
@@ -101,14 +122,12 @@ gdMidiInputMaster::gdMidiInputMaster()
        m_channel->add("Channel 14");
        m_channel->add("Channel 15");
        m_channel->add("Channel 16");
-       
-       m_channel->value(midiIn->filter - 1 ? 0 : midiIn->filter + 1);
        m_channel->callback(cb_setChannel, (void*)this);
-       midiIn->enabled ? m_channel->activate() : m_channel->deactivate();
-
-       m::model::midiIn.unlock();
 
        u::gui::setFavicon(this);
+
+       set_modal();
+       rebuild();
        show();
 }
 
@@ -116,6 +135,21 @@ gdMidiInputMaster::gdMidiInputMaster()
 /* -------------------------------------------------------------------------- */
 
 
+void gdMidiInputMaster::rebuild()
+{
+       m_data = c::io::master_getInputData();
+
+       m_enable->value(m_data.enabled);
+       m_channel->value(m_data.filter - 1 ? 0 : m_data.filter + 1);
+       m_learners->update(m_data);
+
+       m_data.enabled ? m_channel->activate() : m_channel->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
 void gdMidiInputMaster::cb_enable(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_enable(); }
 void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_setChannel(); }
 
@@ -125,15 +159,7 @@ void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMast
 
 void gdMidiInputMaster::cb_enable()
 {
-       m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
-       {
-               m.enabled = m_enable->value();
-       });
-       
-       m_enable->value() ? m_channel->activate() : m_channel->deactivate();
-
-       for (geMidiLearnerBase* l : m_learners)
-               m_enable->value() ? l->activate() : l->deactivate();
+       c::io::master_enableMidiLearn(m_enable->value());
 }
 
 
@@ -142,9 +168,6 @@ void gdMidiInputMaster::cb_enable()
 
 void gdMidiInputMaster::cb_setChannel()
 {
-       m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
-       {
-               m.filter = m_channel->value() == 0 ? -1 : m_channel->value() - 1;
-       });
+       c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1);
 }
 }} // giada::v::
index 7d0ef9688168b1a4d2d10b97105a1be100f908f5..b3655ec1dba69a20d8c92ab6bcfaa6a4bc02ece5 100644 (file)
@@ -29,7 +29,8 @@
 #define GD_MIDI_INPUT_MASTER_H
 
 
-#include "core/model/model.h"
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
 #include "midiInputBase.h"
 
 
@@ -40,18 +41,37 @@ class geChoice;
 namespace giada {
 namespace v 
 {
+class geMasterLearnerPack : public geMidiLearnerPack
+{
+public:
+
+       geMasterLearnerPack(int x, int y);
+
+       void update(const c::io::Master_InputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
 class gdMidiInputMaster : public gdMidiInputBase
 {
 public:
 
     gdMidiInputMaster();
 
+       void rebuild() override;
+
 private:
 
        static void cb_enable(Fl_Widget* w, void* p);
        static void cb_setChannel(Fl_Widget* w, void* p);
        void cb_enable();
        void cb_setChannel();
+
+       c::io::Master_InputData m_data;
+
+       geMasterLearnerPack* m_learners;
 };
 }} // giada::v::
 
index b559215c85a16a5f8e64ac3d3b27407eb566b99e..b8dfe656e2ea120748068069d78e4ae3fee3255c 100644 (file)
@@ -26,6 +26,7 @@
 
 
 #include "glue/io.h"
+#include "gui/elems/midiIO/midiLearner.h"
 #include "gui/elems/basics/check.h"
 #include "midiOutputBase.h"
 
 namespace giada {
 namespace v 
 {
-gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId)
-: gdWindow   (w, h, "Midi Output Setup"),
-  m_channelId(channelId)
+geLightningLearnerPack::geLightningLearnerPack(int x, int y, ID channelId)
+: geMidiLearnerPack(x, y)
 {
+       setCallbacks(
+               [channelId] (int param) { c::io::channel_startMidiLearn(param, channelId); },
+               [channelId] (int param) { c::io::channel_clearMidiLearn(param, channelId); }
+       );
+       addMidiLearner("playing", G_MIDI_OUT_L_PLAYING);
+       addMidiLearner("mute",    G_MIDI_OUT_L_MUTE);
+       addMidiLearner("solo",    G_MIDI_OUT_L_SOLO);   
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-gdMidiOutputBase::~gdMidiOutputBase()
+void geLightningLearnerPack::update(const c::io::Channel_OutputData& d)
 {
-       c::io::stopMidiLearn();
+       learners[0]->update(d.lightningPlaying);
+       learners[1]->update(d.lightningMute);
+       learners[2]->update(d.lightningSolo);
+       setEnabled(d.lightningEnabled);
 }
 
 
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 
 
-void gdMidiOutputBase::refresh()
+gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId)
+: gdWindow   (w, h, "Midi Output Setup")
+, m_channelId(channelId)
 {
-       for (geMidiLearnerBase* l : m_learners)
-               l->refresh();   
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiOutputBase::~gdMidiOutputBase()
+{
+       c::io::stopMidiLearn();
 }
 
 
@@ -80,23 +101,16 @@ void gdMidiOutputBase::cb_close()
 
 void gdMidiOutputBase::cb_enableLightning()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               c.midiOutL = m_enableLightning->value();
-       });
-
-       for (geMidiLearnerBase* l : m_learners)
-               m_enableLightning->value() ? l->activate() : l->deactivate();
+       c::io::channel_enableMidiLightning(m_channelId, m_enableLightning->value());
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void gdMidiOutputBase::setTitle(int chanNum)
+void gdMidiOutputBase::setTitle(ID channelId)
 {
-       std::string tmp = "MIDI Output Setup (channel " + std::to_string(chanNum) + ")"; 
+       std::string tmp = "MIDI Output Setup (channel " + std::to_string(channelId) + ")"; 
        copy_label(tmp.c_str());
 }
-
 }} // giada::v::
index de95cf154387a0063626037e5c8a811ba007be7f..84cf49829ae8e78ae72f33f928d4513be6bc07d2 100644 (file)
 
 
 #include "core/types.h"
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
+#include "gui/elems/midiIO/midiLearner.h"
 #include "gui/dialogs/window.h"
-#include "gui/elems/midiIO/midiLearnerBase.h"
 
 
 class geButton;
@@ -45,13 +47,22 @@ only with channels.
 Both MidiOutputMidiCh and MidiOutputSampleCh have the MIDI lighting widget set.
 In addition MidiOutputMidiCh has the MIDI message output box. */
 
-/* TODO - gdMidiOutput is almost the same thing of gdMidiInput. Create another
-parent class gdMidiIO to inherit from */
-
 namespace giada {
 namespace v 
 {
-class geMidiLearner;
+class geLightningLearnerPack : public geMidiLearnerPack
+{
+public:
+
+       geLightningLearnerPack(int x, int y, ID channelId);
+
+       void update(const c::io::Channel_OutputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
 class gdMidiOutputBase : public gdWindow
 {
 public:
@@ -59,8 +70,6 @@ public:
        gdMidiOutputBase(int w, int h, ID channelId);
        ~gdMidiOutputBase();
 
-       void refresh() override;
-
 protected:
 
        /* cb_close
@@ -75,14 +84,15 @@ protected:
        /* setTitle
         * set window title. */
 
-       void setTitle(int chanNum);
+       void setTitle(ID channelId);
 
        ID m_channelId;
-
-       std::vector<geMidiLearnerBase*> m_learners;
        
-       geButton* m_close;
-       geCheck*  m_enableLightning;
+       c::io::Channel_OutputData m_data;
+       
+       geLightningLearnerPack* m_learners;
+       geButton*               m_close;
+       geCheck*                m_enableLightning;
 };
 }} // giada::v::
 
index 0ccf7f1d6fdd392014b16d451cf1752ddb51e92c..f4c738548716f5b9db05397004a4fde7487af436 100644 (file)
  * -------------------------------------------------------------------------- */
 
 
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "utils/gui.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
+#include <FL/Fl_Pack.H>
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearner.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
 #include "gui/elems/basics/choice.h"
+#include "utils/gui.h"
 #include "midiOutputMidiCh.h"
 
 
@@ -41,26 +41,25 @@ namespace v
 gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
 : gdMidiOutputBase(300, 168, channelId)
 {
-       m::model::ChannelsLock l(m::model::channels);
-       m::MidiChannel& c = static_cast<m::MidiChannel&>(m::model::get(m::model::channels, m_channelId));
-       
+       end();
        setTitle(m_channelId + 1);
-       begin();
 
-       m_enableOut   = new geCheck(x()+8, y()+8, 150, 20, "Enable MIDI output");
-       m_chanListOut = new geChoice(w()-108, y()+8, 100, 20);
+       m_enableOut   = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 150, G_GUI_UNIT, "Enable MIDI output");
+       m_chanListOut = new geChoice(w()-108, G_GUI_OUTER_MARGIN, 100, G_GUI_UNIT);
 
-       m_enableLightning = new geCheck(x()+8, m_chanListOut->y()+m_chanListOut->h()+8, 120, 20, "Enable MIDI lightning output");
-       m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+8,  
-               w()-16, "playing", G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId));
-       m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+32
-               w()-16, "mute", G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId));
-       m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+56, 
-               w()-16, "solo", G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId));
+       m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, m_chanListOut->y() + m_chanListOut->h() + G_GUI_OUTER_MARGIN, 
+               120, G_GUI_UNIT, "Enable MIDI lightning output");
+       
+       m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN
+               m_enableLightning->y() + m_enableLightning->h() + G_GUI_OUTER_MARGIN, channelId);
+       
+       m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
 
-       m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close");
-
-       end();
+       add(m_enableOut);
+       add(m_chanListOut);
+       add(m_enableLightning);
+       add(m_learners);
+       add(m_close);
 
        m_chanListOut->add("Channel 1");
        m_chanListOut->add("Channel 2");
@@ -80,24 +79,15 @@ gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
        m_chanListOut->add("Channel 16");
        m_chanListOut->value(0);
                
-       if (c.midiOut)
-               m_enableOut->value(1);
-       else
-               m_chanListOut->deactivate();
-
-       m_enableLightning->value(c.midiOutL);
-       for (geMidiLearnerBase* l : m_learners)
-               c.midiOutL ? l->activate() : l->deactivate();
-
-       m_chanListOut->value(c.midiOutChan);
        m_chanListOut->callback(cb_setChannel, (void*)this);
-
        m_enableOut->callback(cb_enableOut, (void*)this);
        m_enableLightning->callback(cb_enableLightning, (void*)this);
        m_close->callback(cb_close, (void*)this);
 
-       set_modal();
        u::gui::setFavicon(this);
+
+       set_modal();
+       rebuild();
        show();
 }
 
@@ -105,6 +95,23 @@ gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
 /* -------------------------------------------------------------------------- */
 
 
+void gdMidiOutputMidiCh::rebuild()
+{
+       m_data = c::io::channel_getOutputData(m_channelId);
+
+       assert(m_data.output.has_value());
+       
+       m_learners->update(m_data);
+       m_chanListOut->value(m_data.output->filter);
+       m_enableOut->value(m_data.output->enabled);
+
+       m_data.output->enabled ? m_chanListOut->activate() : m_chanListOut->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
 void gdMidiOutputMidiCh::cb_enableOut (Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_enableOut(); }
 void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_setChannel(); }
 
@@ -114,13 +121,7 @@ void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget *w, void *p) { ((gdMidiOutputMi
 
 void gdMidiOutputMidiCh::cb_enableOut()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               static_cast<m::MidiChannel&>(c).midiOut     = m_enableOut->value();
-               static_cast<m::MidiChannel&>(c).midiOutChan = m_chanListOut->value();
-       });
-
-       m_enableOut->value() ? m_chanListOut->activate() : m_chanListOut->deactivate();
+       c::io::channel_enableMidiOutput(m_channelId, m_enableOut->value());
 }
 
 
@@ -129,9 +130,6 @@ void gdMidiOutputMidiCh::cb_enableOut()
 
 void gdMidiOutputMidiCh::cb_setChannel()
 {
-       m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               static_cast<m::MidiChannel&>(c).midiOutChan = m_chanListOut->value();
-       });
+       c::io::channel_setMidiOutputFilter(m_channelId, m_chanListOut->value());
 }
 }} // giada::v::
index 41799fdf6a4cfd38606db8ae7be390c69fcb1ea3..79b44d91b5aadd5391fefa120222e355865fc1f5 100644 (file)
@@ -43,6 +43,8 @@ public:
 
        gdMidiOutputMidiCh(ID channelId);
 
+       void rebuild() override;
+
 private:
 
        static void cb_enableOut (Fl_Widget* w, void* p);
index dee27dc32116636b6517a314e4d4d044b436a870..257d46ac35c5d6b8f1ec6da1e4ddea29a204d9d6 100644 (file)
  * -------------------------------------------------------------------------- */
 
 
+#include <FL/Fl_Pack.H>
 #include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
 #include "utils/gui.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
+#include "gui/elems/midiIO/midiLearner.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/check.h"
 #include "midiOutputSampleCh.h"
@@ -40,30 +40,39 @@ namespace v
 gdMidiOutputSampleCh::gdMidiOutputSampleCh(ID channelId)
 : gdMidiOutputBase(300, 140, channelId)
 {
-       m::model::ChannelsLock l(m::model::channels);
-       m::Channel& c = m::model::get(m::model::channels, m_channelId);
-       
-       setTitle(c.id);
+       end();
+       setTitle(m_channelId);
 
-       m_enableLightning = new geCheck(8, 8, 120, 20, "Enable MIDI lightning output");
-       m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+8, w()-16, "playing", 
-               G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId));
-       m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+32, w()-16, "mute",   
-               G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId));
-       m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+56, w()-16, "solo",   
-               G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId));
+       m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, 20, "Enable MIDI lightning output");
 
-       m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close");
-       m_close->callback(cb_close, (void*)this);
+       m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN, 
+               m_enableLightning->y() + m_enableLightning->h() + 8, channelId);
+
+       m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + 8, 80, 20, "Close");
+
+       add(m_enableLightning);
+       add(m_learners);
+       add(m_close);
 
-       m_enableLightning->value(c.midiOutL);
+       m_close->callback(cb_close, (void*)this);
        m_enableLightning->callback(cb_enableLightning, (void*)this);
        
-       for (geMidiLearnerBase* l : m_learners)
-               c.midiOutL ? l->activate() : l->deactivate();
+       u::gui::setFavicon(this);
 
        set_modal();
-       u::gui::setFavicon(this);
+       rebuild();
        show();
 }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiOutputSampleCh::rebuild()
+{
+       m_data = c::io::channel_getOutputData(m_channelId);
+
+       m_enableLightning->value(m_data.lightningEnabled);
+       m_learners->update(m_data);
+}
 }} // giada::v::
index 0dd41f599b3870f2ea78c307e945c5cbeda919d7..10f403310ba6406cc56c40c047505adbf81cca44 100644 (file)
@@ -40,6 +40,8 @@ class gdMidiOutputSampleCh : public gdMidiOutputBase
 public:
 
     gdMidiOutputSampleCh(ID channelId);
+
+       void rebuild() override;
 };
 }} // giada::v::
 
index 277604bab53e061ea276e01834a70ef2cd0b54e2..e15ed6c80f2a3e3bff302783664dc676579163f5 100644 (file)
@@ -30,7 +30,6 @@
 
 #include "glue/plugin.h"
 #include "utils/gui.h"
-#include "core/channels/channel.h"
 #include "core/conf.h"
 #include "core/pluginManager.h"
 #include "core/pluginHost.h"
@@ -44,9 +43,9 @@
 namespace giada {
 namespace v
 {
-gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID chanID)
-: gdWindow(X, Y, W, H, "Available plugins"),
-  m_chanID(chanID)
+gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
+: gdWindow   (X, Y, W, H, "Available plugins")
+, m_channelId(channelId)
 {
        /* top area */
        Fl_Group *group_top = new Fl_Group(8, 8, w()-16, 20);
@@ -132,7 +131,7 @@ void gdPluginChooser::cb_add()
        int pluginIndex = browser->value() - 3; // subtract header lines
        if (pluginIndex < 0)
                return;
-       c::plugin::addPlugin(pluginIndex, m_chanID);
+       c::plugin::addPlugin(pluginIndex, m_channelId);
        do_callback();
 }
 }} // giada::v::
index beee9676cdfd6b22b386ebb3290768a2a859db2c..cd5cb66d50074e5dfac18c7e65110ce3b739c460 100644 (file)
@@ -49,7 +49,7 @@ class gdPluginChooser : public gdWindow
 {
 public:
 
-       gdPluginChooser(int x, int y, int w, int h, ID chanID);
+       gdPluginChooser(int x, int y, int w, int h, ID channelId);
        ~gdPluginChooser();
 
 private:
@@ -66,7 +66,7 @@ private:
        geButton*        cancelBtn;
        gePluginBrowser* browser;
 
-       ID m_chanID;
+       ID m_channelId;
 };
 }} // giada::v::
 
index f062261d76cdec995af390a801963936af4860e5..cb025a730db3401b83bbd5a97e38c42be33b9a20 100644 (file)
 
 #include <cassert>
 #include <string>
-#include "core/model/model.h"
-#include "core/channels/channel.h"
 #include "core/conf.h"
 #include "core/const.h"
-#include "core/pluginHost.h"
 #include "utils/string.h"
 #include "utils/gui.h"
 #include "gui/elems/basics/liquidScroll.h"
@@ -55,9 +52,9 @@ extern giada::v::gdMainWindow* G_MainWin;
 namespace giada {
 namespace v
 {
-gdPluginList::gdPluginList(ID chanID)
-: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204), 
-  m_channelId(chanID)
+gdPluginList::gdPluginList(ID channelId)
+: gdWindow   (m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204)
+, m_channelId(channelId)
 {
        end();
 
@@ -66,20 +63,9 @@ gdPluginList::gdPluginList(ID chanID)
        list->end();
        add(list);
 
-       rebuild();
-
-       if (m_channelId == m::mixer::MASTER_OUT_CHANNEL_ID)
-               label("Master Out Plug-ins");
-       else
-       if (m_channelId == m::mixer::MASTER_IN_CHANNEL_ID)
-               label("Master In Plug-ins");
-       else {
-               std::string l = "Channel " + u::string::iToString(m_channelId + 1) + " Plug-ins";
-               copy_label(l.c_str());
-       }
-
        u::gui::setFavicon(this);
        set_non_modal();
+       rebuild();
        show();
 }
 
@@ -105,17 +91,25 @@ void gdPluginList::cb_addPlugin(Fl_Widget* v, void* p) { ((gdPluginList*)p)->cb_
 
 void gdPluginList::rebuild()
 {
+       m_plugins = c::plugin::getPlugins(m_channelId);
+
+       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)
+               label("Master In Plug-ins");
+       else {
+               std::string l = "Channel " + u::string::iToString(m_plugins.channelId) + " Plug-ins";
+               copy_label(l.c_str());
+       }
+
        /* Clear the previous list. */
 
        list->clear();
        list->scroll_to(0, 0);
 
-       m::model::ChannelsLock l(m::model::channels);
-
-       const m::Channel& ch = m::model::get(m::model::channels, m_channelId);
-
-       for (ID pluginId : ch.pluginIds)
-               list->addWidget(new gePluginElement(pluginId, m_channelId, 0, 0, 0));
+       for (ID pluginId : m_plugins.pluginIds)
+               list->addWidget(new gePluginElement(0, 0, 0, c::plugin::getPlugin(pluginId, m_plugins.channelId)));
        
        addPlugin = list->addWidget(new geButton(0, 0, 0, G_GUI_UNIT, "-- add new plugin --"));
        
@@ -133,7 +127,7 @@ void gdPluginList::cb_addPlugin()
        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_channelId), WID_FX_CHOOSER);
+               m_plugins.channelId), WID_FX_CHOOSER);
 }
 
 
index 8ac8f464834992a68fc0eb171a2ae4274d9e9da9..a56a261ae771064bb595f3fc6682a9c08197d8c9 100644 (file)
@@ -32,7 +32,7 @@
 #define GD_PLUGINLIST_H
 
 
-#include "core/pluginHost.h"
+#include "glue/plugin.h"
 #include "window.h"
 
 
@@ -48,7 +48,7 @@ class gdPluginList : public gdWindow
 {
 public:
 
-       gdPluginList(ID chanID);
+       gdPluginList(ID channelId);
        ~gdPluginList();
 
        void rebuild() override;
@@ -65,8 +65,8 @@ private:
        geLiquidScroll* list;   
 
        ID m_channelId;
+       c::plugin::Plugins m_plugins;
 };
-
 }} // giada::v::
 
 
index 9e038d6e8aeee68a1869f392c346648681d7fd69..f21017331d93d8d1068b9f7a9533d1f3e44f34c3 100644 (file)
@@ -29,9 +29,8 @@
 
 
 #include <FL/fl_draw.H>
+#include "glue/plugin.h"
 #include "utils/gui.h"
-#include "core/plugin.h"
-#include "core/model/model.h"
 #include "core/const.h"
 #include "gui/elems/basics/liquidScroll.h"
 #include "gui/elems/plugin/pluginParameter.h"
 namespace giada {
 namespace v
 {
-gdPluginWindow::gdPluginWindow(ID pluginId)
-: gdWindow  (450, 156), 
-  m_pluginId(pluginId)
+gdPluginWindow::gdPluginWindow(const c::plugin::Plugin& plugin)
+: gdWindow(450, 156)
+, m_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));
-
-       m::model::PluginsLock l(m::model::plugins);
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
        
        m_list->type(Fl_Scroll::VERTICAL_ALWAYS);
        m_list->begin();
-               int labelWidth = getLabelWidth();
-               int numParams = p.getNumParameters();
-               for (int i=0; i<numParams; i++) {
-                       int py = m_list->y() + (i * (G_GUI_UNIT + G_GUI_INNER_MARGIN));
+               int labelWidth = 100; // TODO
+               for (int index : m_plugin.paramIndexes) {
+                       int py = m_list->y() + (index * (G_GUI_UNIT + G_GUI_INNER_MARGIN));
                        int pw = m_list->w() - m_list->scrollbar_size() - (G_GUI_OUTER_MARGIN*3);
-                       new v::gePluginParameter(i, m_pluginId, m_list->x(), py, pw, labelWidth);
+                       new v::gePluginParameter(m_list->x(), py, pw, labelWidth, c::plugin::getParam(index, m_plugin.id));
                }
        m_list->end();
 
        end();
 
-       label(p.getName().c_str());
+       label(m_plugin.name.c_str());
 
        size_range(450, (G_GUI_UNIT + (G_GUI_OUTER_MARGIN*2)));
        resizable(m_list);
@@ -79,40 +74,10 @@ gdPluginWindow::gdPluginWindow(ID pluginId)
 /* -------------------------------------------------------------------------- */
 
 
-void gdPluginWindow::updateParameter(int index, bool changeSlider)
-{
-       static_cast<v::gePluginParameter*>(m_list->child(index))->update(changeSlider);
-}
-
-
 void gdPluginWindow::updateParameters(bool changeSlider)
 {
-       m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
-       {
-               for (int i=0; i<p.getNumParameters(); i++) {
-                       static_cast<v::gePluginParameter*>(m_list->child(i))->update(changeSlider);
-               }
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int gdPluginWindow::getLabelWidth() const
-{
-       m::model::PluginsLock l(m::model::plugins);
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
-       int width = 0;
-       int numParams = p.getNumParameters();
-       for (int i=0; i<numParams; i++) {
-               int wl = 0, hl = 0;   
-               fl_measure(p.getParameterName(i).c_str(), wl, hl);
-               if (wl > width)
-                       width = wl;
-       }
-       return width;
+       for (int index : m_plugin.paramIndexes)
+               static_cast<v::gePluginParameter*>(m_list->child(index))->update(c::plugin::getParam(index, m_plugin.id), changeSlider);
 }
 }} // giada::v::
 
index 3c37c954bf4f6e90ca385ba5e1f18b5880f79195..a0c106f071f03b03e5f1ed641f2428ec3cb1acf0 100644 (file)
@@ -41,6 +41,11 @@ class geLiquidScroll;
 
 
 namespace giada {
+namespace c {
+namespace plugin
+{
+class Plugin;
+}}
 namespace m
 {
 class Plugin;
@@ -51,16 +56,13 @@ class gdPluginWindow : public gdWindow
 {
 public:
 
-       gdPluginWindow(ID pluginId);
+       gdPluginWindow(const c::plugin::Plugin&);
 
-       void updateParameter(int index, bool changeSlider=false);
        void updateParameters(bool changeSlider=false);
 
 private:
-
-       int getLabelWidth() const;
        
-       ID m_pluginId;
+       const c::plugin::Plugin& m_plugin;
                
        geLiquidScroll* m_list;
 };
index aa7c581de831a4805c5f91039ad939b4e63a041e..792003b41f237ef698b43eb98fdd930768c326ec 100644 (file)
@@ -31,9 +31,7 @@
 #include <FL/x.H>
 #include "utils/log.h"
 #include "utils/gui.h"
-#include "core/pluginHost.h"
-#include "core/model/model.h"
-#include "core/plugin.h"
+#include "glue/plugin.h"
 #include "core/const.h"
 #include "pluginWindowGUI.h"
 #ifdef G_OS_MAC
 namespace giada {
 namespace v
 {
-gdPluginWindowGUI::gdPluginWindowGUI(ID pluginId)
+gdPluginWindowGUI::gdPluginWindowGUI(const c::plugin::Plugin& p)
 #ifdef G_OS_MAC
-: gdWindow     (Fl::w(), Fl::h()),
+: gdWindow(Fl::w(), Fl::h())
 #else
-: gdWindow     (320, 200),
+: gdWindow(320, 200)
 #endif
-  m_pluginId(pluginId),
-  m_ui         (nullptr)
+, m_plugin(p)
+, m_ui    (nullptr)
 {
        show();
 
@@ -88,15 +86,11 @@ gdPluginWindowGUI::gdPluginWindowGUI(ID pluginId)
 
        resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
 
-
 #endif
 
        Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this);
 
-       m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
-       {
-               copy_label(p.getName().c_str());
-       });
+       copy_label(m_plugin.name.c_str());
 }
 
 
@@ -142,10 +136,7 @@ void gdPluginWindowGUI::cb_refresh()
 
 void gdPluginWindowGUI::openEditor(void* parent)
 {
-       m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
-       {
-               m_ui = p.createEditor();
-       });
+       m_ui = m_plugin.createEditor();
        if (m_ui == nullptr) {
                u::log::print("[gdPluginWindowGUI::openEditor] unable to create editor!\n");
                return;
index 45e368207e5e9a38849f10c48adad7edeebbc5b5..ac39f849140c40857e220c16c0502536c6674559 100644 (file)
 
 
 namespace giada {
+namespace c {
+namespace plugin
+{
+struct Plugin;
+}}
 namespace v
 {
 class gdPluginWindowGUI : public gdWindow
 {
 public:
 
-       gdPluginWindowGUI(ID pluginId);
+       gdPluginWindowGUI(const c::plugin::Plugin&);
        ~gdPluginWindowGUI();
 
 private:
@@ -60,7 +65,7 @@ private:
        void openEditor(void* parent); 
        void closeEditor(); 
 
-       ID m_pluginId;
+       const c::plugin::Plugin& m_plugin;
 
        juce::AudioProcessorEditor* m_ui;
 };
index 93884ded0e8115bc56d501b8b4ea97e8fa7ad49b..57a83670c3fdcb1aefaf742fa1be4c2c828f9985 100644 (file)
@@ -31,8 +31,6 @@
 #include <FL/Fl_Group.H>
 #include "glue/channel.h"
 #include "glue/sampleEditor.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
 #include "core/waveFx.h"
 #include "core/conf.h"
 #include "core/const.h"
 namespace giada {
 namespace v 
 {
-gdSampleEditor::gdSampleEditor(ID channelId, ID waveId)
+gdSampleEditor::gdSampleEditor(ID channelId)
 : gdWindow   (m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY, 
-                 m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH),
-  m_channelId(channelId),
-  m_waveId   (waveId)
+              m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH)
+, m_channelId(channelId)
 {
        Fl_Group* upperBar = createUpperBar();
        
-       waveTools = new geWaveTools(channelId, waveId, G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN, 
+       waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN, 
                w()-16, h()-128);
        
        Fl_Group* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y()+waveTools->h()+G_GUI_OUTER_MARGIN, 
@@ -107,7 +104,8 @@ gdSampleEditor::~gdSampleEditor()
        m::conf::conf.sampleEditorGridVal = atoi(grid->text());
        m::conf::conf.sampleEditorGridOn  = snap->value();
        
-       c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE);
+       c::sampleEditor::stopPreview();
+       c::sampleEditor::cleanupPreview();
 }
 
 
@@ -116,24 +114,21 @@ gdSampleEditor::~gdSampleEditor()
 
 void gdSampleEditor::rebuild()
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               copy_label(c.name.c_str());
-       });
-       
-       volumeTool->rebuild();
-       waveTools->rebuild();
-       panTool->rebuild();
-       pitchTool->rebuild();
-       rangeTool->rebuild();
-       shiftTool->rebuild();
-
-       m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w)
-       {
-               updateInfo(w);
-               if (w.isLogical()) // Logical samples (aka takes) cannot be reloaded.
-                       reload->deactivate();
-       });
+       m_data = c::sampleEditor::getData(m_channelId);
+
+       copy_label(m_data.name.c_str());
+
+       waveTools->rebuild(m_data);
+       volumeTool->rebuild(m_data);
+       panTool->rebuild(m_data);
+       pitchTool->rebuild(m_data);
+       rangeTool->rebuild(m_data);
+       shiftTool->rebuild(m_data);
+
+       updateInfo();
+
+       if (m_data.isLogical) // Logical samples (aka takes) cannot be reloaded.
+               reload->deactivate();   
 }
 
 
@@ -143,11 +138,7 @@ void gdSampleEditor::rebuild()
 void gdSampleEditor::refresh()
 {
        waveTools->refresh();
-       
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               play->setStatus(c.previewMode == PreviewMode::LOOP || c.previewMode == PreviewMode::NORMAL ? 1 : 0);
-       });
+       play->setStatus(m_data.a_getPreviewStatus() == ChannelStatus::PLAY);
 }
 
 
@@ -192,22 +183,22 @@ Fl_Group* gdSampleEditor::createUpperBar()
        return g;
 }
 
-
+\
 /* -------------------------------------------------------------------------- */
-
+\
 
 Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h)
 {
        Fl_Group* g = new Fl_Group(x, y, 572, h);
        g->begin();
        g->resizable(0);
-               volumeTool = new geVolumeTool(m_channelId, g->x(), g->y());
-               panTool    = new gePanTool(m_channelId, volumeTool->x()+volumeTool->w()+4, g->y());
+               volumeTool = new geVolumeTool(m_data, g->x(), g->y());
+               panTool    = new gePanTool(m_data, volumeTool->x()+volumeTool->w()+4, g->y());
         
-               pitchTool = new gePitchTool(m_channelId, g->x(), panTool->y()+panTool->h()+8);
+               pitchTool = new gePitchTool(m_data, g->x(), panTool->y()+panTool->h()+8);
 
-               rangeTool = new geRangeTool(m_channelId, m_waveId, g->x(), pitchTool->y()+pitchTool->h()+8);
-               shiftTool = new geShiftTool(m_channelId, m_waveId, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8);
+               rangeTool = new geRangeTool(m_data, g->x(), pitchTool->y()+pitchTool->h()+8);
+               shiftTool = new geShiftTool(m_data, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8);
                reload    = new geButton(g->x()+g->w()-70, shiftTool->y(), 70, 20, "Reload");
        g->end();
 
@@ -304,16 +295,16 @@ void gdSampleEditor::cb_enableSnap()
 
 void gdSampleEditor::cb_togglePreview()
 {
-       if (play->getStatus())
-               c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE);
+       if (!play->getStatus())
+               c::sampleEditor::playPreview(loop->value());
        else
-               c::sampleEditor::setPreview(m_channelId, loop->value() ? PreviewMode::LOOP : PreviewMode::NORMAL);
+               c::sampleEditor::stopPreview();
 }
 
 
 void gdSampleEditor::cb_rewindPreview()
 {
-       c::sampleEditor::rewindPreview(m_channelId);
+       c::sampleEditor::setPreviewTracker(m_data.begin);
 }
 
 
@@ -322,7 +313,7 @@ void gdSampleEditor::cb_rewindPreview()
 
 void gdSampleEditor::cb_reload()
 {
-       c::sampleEditor::reload(m_channelId, m_waveId);
+       c::sampleEditor::reload(m_data.channelId, m_data.waveId);
        redraw();
 }
 
@@ -359,27 +350,16 @@ void gdSampleEditor::cb_changeGrid()
 /* -------------------------------------------------------------------------- */
 
 
-void gdSampleEditor::updateInfo(const m::Wave& w)
+void gdSampleEditor::updateInfo()
 {
-       std::string bitDepth = w.getBits() != 0 ? u::string::iToString(w.getBits()) : "(unknown)";
+       std::string bitDepth = m_data.waveBits != 0 ? u::string::iToString(m_data.waveBits) : "(unknown)";
        std::string infoText = 
-               "File: "      + w.getPath() + "\n"
-               "Size: "      + u::string::iToString(w.getSize()) + " frames\n"
-               "Duration: "  + u::string::iToString(w.getDuration()) + " seconds\n"
+               "File: "      + m_data.wavePath + "\n"
+               "Size: "      + u::string::iToString(m_data.waveSize) + " frames\n"
+               "Duration: "  + u::string::iToString(m_data.waveDuration) + " seconds\n"
                "Bit depth: " + bitDepth + "\n"
-               "Frequency: " + u::string::iToString(w.getRate()) + " Hz\n";
+               "Frequency: " + u::string::iToString(m_data.waveRate) + " Hz\n";
 
        info->copy_label(infoText.c_str());
 }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void gdSampleEditor::setWaveId(ID id)
-{
-       m_waveId = id;
-       waveTools->waveId = id;
-       waveTools->waveform->setWaveId(id);
-}
 }} // giada::v::
index f31c21d6868fc9863136f88f0c692ff0c2192c48..12bc59a9636addf12f98a5a515e9077cfb92ae68 100644 (file)
@@ -30,6 +30,7 @@
 
 
 #include "core/types.h"
+#include "glue/sampleEditor.h"
 #include "window.h"
 
 
@@ -44,7 +45,6 @@ class geStatusButton;
 namespace giada {
 namespace m
 {
-class SampleChannel;
 class Wave;
 }
 namespace v 
@@ -56,22 +56,18 @@ class gePanTool;
 class gePitchTool;
 class geRangeTool;
 class geShiftTool;
-
 class gdSampleEditor : public gdWindow
 {
 friend class geWaveform;
 
 public:
 
-       gdSampleEditor(ID channelId, ID waveId);
+       gdSampleEditor(ID channelId);
        ~gdSampleEditor();
 
        void rebuild() override;
        void refresh() override;
 
-       void updateInfo(const m::Wave& w);
-       void setWaveId(ID id);
-
        geChoice* grid;
        geCheck*  snap;
        geBox*    sep1;
@@ -118,9 +114,12 @@ private:
        void cb_enableSnap();
        void cb_togglePreview();
        void cb_rewindPreview();
-       
+
+       void updateInfo();
+
        ID m_channelId;
-       ID m_waveId;
+
+       c::sampleEditor::Data m_data;
 };
 }} // giada::v::
 
index 63f8aa40ac69f75af505294c71bf92090ba90579..2a5e994032c5c745a875c49e898705127686cb89 100644 (file)
 
 #include <cassert>
 #include <FL/Fl.H>
-#include "core/channels/channel.h"
 #include "core/init.h"
-#include "core/const.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/recManager.h"
-#include "core/conf.h"
-#include "glue/io.h"
-#include "glue/main.h"
+#include "glue/events.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/elems/mainWindow/keyboard/channel.h"
 #include "gui/elems/mainWindow/keyboard/keyboard.h"
@@ -65,13 +58,20 @@ std::function<void()> signalCb_ = nullptr;
 /* -------------------------------------------------------------------------- */
 
 
-void perform_(const geChannel* gch, int event)
+void perform_(ID channelId, int event)
 {
-       if (event == FL_KEYDOWN)
-               c::io::keyPress(gch->channelId, Fl::event_ctrl(), Fl::event_shift(), G_MAX_VELOCITY);
+       if (event == FL_KEYDOWN) {
+               if (Fl::event_ctrl())
+                       c::events::toggleMuteChannel(channelId, Thread::MAIN);
+               else
+               if (Fl::event_shift())
+                       c::events::killChannel(channelId, Thread::MAIN);
+               else
+                       c::events::pressChannel(channelId, G_MAX_VELOCITY, Thread::MAIN);
+       }
        else
        if (event == FL_KEYUP)  
-               c::io::keyRelease(gch->channelId, Fl::event_ctrl(), Fl::event_shift());
+               c::events::releaseChannel(channelId, Thread::MAIN);
 }
 
 
@@ -86,7 +86,7 @@ void dispatchChannels_(int event)
        G_MainWin->keyboard->forEachChannel([=](geChannel& c)
        {
                if (c.handleKey(event))
-                       perform_(&c, event);
+                       perform_(c.getData().id, event);
        });
 }
 
@@ -117,19 +117,19 @@ void dispatchKey(int event)
        if (event == FL_KEYDOWN) {
                if (Fl::event_key() == FL_BackSpace && !backspace_) {
                        backspace_ = true;
-                       m::mh::rewindSequencer();
+                       c::events::rewindSequencer(Thread::MAIN);
                }
                else if (Fl::event_key() == FL_End && !end_) {
                        end_ = true;
-                       c::main::toggleInputRec();
+                       c::events::toggleInputRecording();
                }
                else if (Fl::event_key() == FL_Enter && !enter_) {
                        enter_ = true;
-                       m::recManager::toggleActionRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode));
+                       c::events::toggleActionRecording();
                }
                else if (Fl::event_key() == ' ' && !space_) {
                        space_ = true;
-                       m::mh::toggleSequencer();
+                       c::events::toggleSequencer(Thread::MAIN);
                }
                else if (Fl::event_key() == FL_Escape && !esc_) {
                        esc_ = true;
@@ -163,10 +163,10 @@ void dispatchKey(int event)
 /* -------------------------------------------------------------------------- */
 
 
-void dispatchTouch(const geChannel* gch, bool status)
+void dispatchTouch(const geChannel& gch, bool status)
 {
        triggerSignalCb_();
-       perform_(gch, status ? FL_KEYDOWN : FL_KEYUP);
+       perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
 }
 
 
index e1670faa9bc743a1265e85d5f5b735cb0b77f668..bb10117e3acfa1f3d46d4dbe5a58543a5282f408 100644 (file)
@@ -47,7 +47,7 @@ void dispatchKey(int event);
 /* dispatchTouch
 Processes a mouse click/touch event. */
 
-void dispatchTouch(const geChannel* gch, bool status);
+void dispatchTouch(const geChannel& gch, bool status);
 
 void setSignalCallback(std::function<void()> f);
 }}} // giada::v::dispatcher
index 65f5007e95441b9b9cc658f88884b308246f17a5..ee5cf1b756e3f82e3247f1e4e1adffb467b1eac8 100644 (file)
 namespace giada {
 namespace v
 {
-geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h)
-:      Fl_Group(x, y, w, h),
-       m_base  (static_cast<gdBaseActionEditor*>(window())),
-       m_action(nullptr)
+geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h,
+       gdBaseActionEditor* base)
+: Fl_Group(x, y, w, h)
+, m_data  (nullptr)
+, m_base  (base)
+, m_action(nullptr)
 {
 }
 
@@ -51,7 +53,7 @@ geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h)
 
 geBaseAction* geBaseActionEditor::getActionAtCursor() const
 {
-       for (int i=0; i<children(); i++) {
+       for (int i = 0; i < children(); i++) {
                geBaseAction* a = static_cast<geBaseAction*>(child(i));
                if (a->hovered)
                        return a;
index e9163b0a7b1caf853f73ed7c84634285157ff244..74032d9734a3cd7dc0295dde9122c7532ae4440b 100644 (file)
@@ -43,12 +43,10 @@ class geBaseActionEditor : public Fl_Group
 {
 public:
 
-       geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h);
-
-  /* updateActions
-  Rebuild the actions widgets from scratch. */
+       /* updateActions
+       Rebuild the actions widgets from scratch. */
   
-       virtual void rebuild() = 0;
+       virtual void rebuild(c::actionEditor::Data& d) = 0;
 
        /* handle
        Override base FL_Group events. */
@@ -63,6 +61,13 @@ public:
 
 protected:
 
+       geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*);
+
+       c::actionEditor::Data* m_data;
+
+       /* m_base
+       Pointer to parent class. */
+
        gdBaseActionEditor* m_base;
 
        /* m_action
index aabfd2bd9f4675580b398d0df1fef17ae9c2a832..83fe8ec0f9795a1ee9548551d814d82ee837fd1b 100644 (file)
 #include <FL/fl_draw.H>
 #include "utils/log.h"
 #include "utils/math.h"
-#include "core/channels/sampleChannel.h"
 #include "core/const.h"
 #include "core/conf.h"
 #include "core/action.h"
 #include "core/recorder.h"
 #include "glue/actionEditor.h"
+#include "glue/channel.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
 #include "envelopePoint.h"
 #include "envelopeEditor.h"
@@ -44,8 +44,8 @@
 namespace giada {
 namespace v
 {
-geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l)
-:      geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH)
+geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b)
+: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b)
 {
        copy_label(l);
 }
@@ -108,10 +108,9 @@ void geEnvelopeEditor::draw()
 /* -------------------------------------------------------------------------- */
 
 
-void geEnvelopeEditor::rebuild()
+void geEnvelopeEditor::rebuild(c::actionEditor::Data& d)
 {
-       namespace mr = m::recorder;
-       namespace ca = c::actionEditor;
+       m_data = &d;
 
        /* Remove all existing actions and set a new width, according to the current
        zoom level. */
@@ -119,7 +118,7 @@ void geEnvelopeEditor::rebuild()
        clear();
        size(m_base->fullWidth, h());
 
-       for (const m::Action& a : m_base->getActions()) {
+       for (const m::Action& a : m_data->actions) {
                if (a.event.getStatus() != m::MidiEvent::ENVELOPE)
                        continue;
                add(new geEnvelopePoint(frameToX(a.frame), valueToY(a.event.getVelocity()), a));                
@@ -175,9 +174,9 @@ void geEnvelopeEditor::onAddAction()
        Frame f = m_base->pixelToFrame(Fl::event_x() - x());
        int   v = yToValue(Fl::event_y() - y());
        
-       c::actionEditor::recordEnvelopeAction(m_base->channelId, f, v);
+       c::actionEditor::recordEnvelopeAction(m_data->channelId, f, v);
        
-       m_base->rebuild();
+       m_base->rebuild(); // TODO - USELESS
 }
 
 
@@ -186,9 +185,9 @@ void geEnvelopeEditor::onAddAction()
 
 void geEnvelopeEditor::onDeleteAction()  
 {
-       c::actionEditor::deleteEnvelopeAction(m_base->channelId, m_action->a1);
+       c::actionEditor::deleteEnvelopeAction(m_data->channelId, m_action->a1);
                
-       m_base->rebuild();
+       m_base->rebuild(); // TODO - USELESS
 }
 
 
@@ -225,7 +224,7 @@ void geEnvelopeEditor::onRefreshAction()
 {
        Frame f = m_base->pixelToFrame((m_action->x() - x()) + geEnvelopePoint::SIDE / 2);
        float v = yToValue(m_action->y() - y(), geEnvelopePoint::SIDE);
-       c::actionEditor::updateEnvelopeAction(m_base->channelId, m_action->a1, f, v);
+       c::actionEditor::updateEnvelopeAction(m_data->channelId, m_action->a1, f, v);
 
        m_base->rebuild();
 }
index b1e559150343a8cce7779a0a12acd5e87a7cff7a..50def75e34f07dddd70f3d5f4a14f121d77bccba 100644 (file)
@@ -40,18 +40,16 @@ class SampleChannel;
 namespace v
 {
 class geEnvelopePoint;
-
-
 class geEnvelopeEditor : public geBaseActionEditor
 {
 public:
 
-       geEnvelopeEditor(Pixel x, Pixel y, const char* l);
+       geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*);
        ~geEnvelopeEditor();
 
        void draw() override;
 
-       void rebuild() override;
+       void rebuild(c::actionEditor::Data& d) override;
 
 private:
 
index 784833f634c6666850a3f29974ae51efaed18088..574280f4cf1f6e34c272df7884eea70dc0aefa34 100644 (file)
@@ -26,7 +26,6 @@
 
 
 #include <FL/Fl.H>
-#include "core/channels/midiChannel.h"
 #include "core/const.h"
 #include "core/conf.h"
 #include "gui/dialogs/actionEditor/midiActionEditor.h"
@@ -38,14 +37,16 @@ namespace giada {
 namespace v
 {
 geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base)
-: geScroll(x, y, 200, 422),
-  m_base  (base)
+: geScroll(x, y, 200, 422)
+, m_base  (base)
 {
-       pianoRoll = new gePianoRoll(x, y, m_base->fullWidth);
-       
-       size(m_base->fullWidth, m::conf::conf.pianoRollH);
+       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);
 }
 
 
@@ -80,9 +81,9 @@ void geNoteEditor::scroll()
 /* -------------------------------------------------------------------------- */
 
 
-void geNoteEditor::rebuild()
+void geNoteEditor::rebuild(c::actionEditor::Data& d)
 {
        size(m_base->fullWidth, h());
-       pianoRoll->rebuild();
+       pianoRoll->rebuild(d);
 }
 }} // giada::v::
\ No newline at end of file
index be900d399bc998a7c68492d1e6b8474cc764f8e0..143dd835efecafe045a4c527308973ffe23b89b4 100644 (file)
@@ -37,8 +37,6 @@ namespace v
 {
 class gdMidiActionEditor;
 class gePianoRoll;
-
-
 class geNoteEditor : public geScroll
 {
 public:
@@ -46,7 +44,7 @@ public:
        geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base);
        ~geNoteEditor();
 
-       void rebuild();
+       void rebuild(c::actionEditor::Data& d);
        void scroll();
 
        gePianoRoll* pianoRoll;
index 4ff9a552d68fec0e39794d958f700c5dcdd79942..08c4dbac29880f5bc62c5368eac7595f99b41f69 100644 (file)
@@ -27,7 +27,6 @@
 
 #include <cassert>
 #include <FL/Fl.H>
-#include "core/channels/midiChannel.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "core/clock.h"
@@ -36,6 +35,7 @@
 #include "utils/log.h"
 #include "utils/string.h"
 #include "utils/math.h"
+#include "glue/channel.h"
 #include "glue/actionEditor.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
 #include "pianoItem.h"
@@ -46,9 +46,9 @@
 namespace giada {
 namespace v
 {
-gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W)
-       : geBaseActionEditor(X, Y, W, 40),
-         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);
 }
@@ -72,7 +72,7 @@ void gePianoRoll::drawSurface1()
 
        int octave = MAX_OCTAVES;
 
-       for (int i=1; i<=MAX_KEYS+1; i++) {
+       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 */
 
@@ -179,11 +179,12 @@ void gePianoRoll::draw()
 {
        fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0);
 
-#if defined(__APPLE__) // TODO - is this still useful?
-       for (Pixel i=36; i<m_base->fullWidth; i+=36) /// TODO: i < ae->coverX is faster
+// 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);
 #else
-       for (Pixel i=CELL_W; i<m_base->loopWidth; i+=CELL_W)
+       for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W)
                fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0);
 #endif
 
@@ -216,7 +217,7 @@ void gePianoRoll::onAddAction()
 {
        Frame frame = m_base->pixelToFrame(Fl::event_x() - x());
        int   note  = yToNote(Fl::event_y() - y());
-       c::actionEditor::recordMidiAction(m_base->channelId, note, G_MAX_VELOCITY, 
+       c::actionEditor::recordMidiAction(m_data->channelId, note, G_MAX_VELOCITY, 
                frame);
 
        m_base->rebuild();  // Rebuild velocityEditor as well
@@ -228,7 +229,7 @@ void gePianoRoll::onAddAction()
 
 void gePianoRoll::onDeleteAction()
 {
-       c::actionEditor::deleteMidiAction(m_base->channelId, m_action->a1);     
+       c::actionEditor::deleteMidiAction(m_data->channelId, m_action->a1);     
        
        m_base->rebuild();  // Rebuild velocityEditor as well
 }
@@ -314,7 +315,7 @@ void gePianoRoll::onRefreshAction()
        int note     = yToNote(m_action->y() - y());
        int velocity = m_action->a1.event.getVelocity();
 
-       ca::updateMidiAction(m_base->channelId, m_action->a1, note, velocity, f1, f2);
+       ca::updateMidiAction(m_data->channelId, m_action->a1, note, velocity, f1, f2);
 
        m_base->rebuild();  // Rebuild velocityEditor as well
 }
@@ -355,9 +356,9 @@ Pixel gePianoRoll::getPianoItemW(Pixel px, const m::Action& a1, const m::Action&
 /* -------------------------------------------------------------------------- */
 
 
-void gePianoRoll::rebuild()
+void gePianoRoll::rebuild(c::actionEditor::Data& d)
 {
-       namespace ca = c::actionEditor;
+       m_data = &d;
 
        /* Remove all existing actions and set a new width, according to the current
        zoom level. */
@@ -365,7 +366,7 @@ void gePianoRoll::rebuild()
        clear();
        size(m_base->fullWidth, (MAX_KEYS + 1) * CELL_H);
 
-       for (const m::Action& a1 : m_base->getActions())
+       for (const m::Action& a1 : m_data->actions)
        {
                if (a1.event.getStatus() == m::MidiEvent::NOTE_OFF)
                        continue;
index 080ce937fd0dfb778e8ff5592e1ad33e59ace3f6..3f49c2c69f2274fb38b57f0928d6f771aa697ecb 100644 (file)
@@ -50,12 +50,12 @@ public:
        static const Pixel CELL_H    = 20;
        static const Pixel CELL_W    = 40;
 
-       gePianoRoll(Pixel x, Pixel y, Pixel w);
+       gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*);
 
        void draw() override;
        int  handle(int e) override;
 
-       void rebuild() override;
+       void rebuild(c::actionEditor::Data& d) override;
 
        Pixel pick;
 
index a45cb47f22ff8c4d94cf87285d15b9db59809a59..ba42a88fab77e8ed9f48acfeed1fdd0aef4a9735 100644 (file)
@@ -26,7 +26,6 @@
 
 
 #include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
 #include "core/const.h"
 #include "core/action.h"
 #include "sampleAction.h"
index c54fc00df04e00cbfe35ddac48d824701b03af43..24bc9dfacac8bacad1dcc6efe284036efacf91a4 100644 (file)
 #include <cassert>
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
 #include "core/recorder.h"
 #include "core/const.h"
 #include "core/conf.h"
 #include "core/action.h"
 #include "utils/log.h"
 #include "glue/actionEditor.h"
+#include "glue/channel.h"
 #include "gui/dialogs/actionEditor/baseActionEditor.h"
 #include "sampleAction.h"
 #include "sampleActionEditor.h"
@@ -44,8 +43,8 @@
 namespace giada {
 namespace v
 {
-geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y)
-: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH)
+geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
+: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b)
 {
 }
 
@@ -62,20 +61,12 @@ geSampleActionEditor::~geSampleActionEditor()
 /* -------------------------------------------------------------------------- */
 
 
-void geSampleActionEditor::rebuild()
+void geSampleActionEditor::rebuild(c::actionEditor::Data& d)
 {
-       namespace mr = m::recorder;
-       namespace ca = c::actionEditor;
+       m_data = &d;
 
-       bool isSinglePressMode;
-       bool isAnyLoopMode;
-       
-       m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c)
-       {
-               const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               isSinglePressMode = sc.mode == ChannelMode::SINGLE_PRESS;
-               isAnyLoopMode     = sc.isAnyLoopMode();
-       });
+       bool isSinglePressMode = m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS;
+       bool isAnyLoopMode     = m_data->sample->isLoopMode;
 
        /* Remove all existing actions and set a new width, according to the current
        zoom level. */
@@ -83,12 +74,12 @@ void geSampleActionEditor::rebuild()
        clear();
        size(m_base->fullWidth, h());
        
-       for (const m::Action& a1 : m_base->getActions()) {
+       for (const m::Action& a1 : m_data->actions) {
 
                if (a1.event.getStatus() == m::MidiEvent::ENVELOPE || isNoteOffSinglePress(a1))
                        continue;
 
-               m::Action a2 = a1.next != nullptr ? *a1.next : m::Action{};
+        const m::Action& a2 = a1.next != nullptr ? *a1.next : m::Action{};
 
                Pixel px = x() + m_base->frameToPixel(a1.frame);
                Pixel py = y() + 4;
@@ -140,9 +131,7 @@ void geSampleActionEditor::draw()
 void geSampleActionEditor::onAddAction()     
 {
        Frame f = m_base->pixelToFrame(Fl::event_x() - x());
-       c::actionEditor::recordSampleAction(m_base->channelId, m_base->getActionType(), f);
-       
-       m_base->rebuild();
+       c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f);
 }
 
 
@@ -151,9 +140,7 @@ void geSampleActionEditor::onAddAction()
 
 void geSampleActionEditor::onDeleteAction()  
 {
-       c::actionEditor::deleteSampleAction(m_base->channelId, m_action->a1);
-       
-       m_base->rebuild();
+       c::actionEditor::deleteSampleAction(m_data->channelId, m_action->a1);
 }
 
 
@@ -218,7 +205,7 @@ void geSampleActionEditor::onRefreshAction()
                f2 = m_base->pixelToFrame(p2);
        }
 
-       ca::updateSampleAction(m_base->channelId, m_action->a1, type, f1, f2);
+       ca::updateSampleAction(m_data->channelId, m_action->a1, type, f1, f2);
                        
        m_base->rebuild();
 }
@@ -229,12 +216,8 @@ void geSampleActionEditor::onRefreshAction()
 
 bool geSampleActionEditor::isNoteOffSinglePress(const m::Action& a)
 {
-       bool res;
-       m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c)
-       {
-               const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               res = sc.mode == ChannelMode::SINGLE_PRESS && a.event.getStatus() == m::MidiEvent::NOTE_OFF;
-       });
-       return res;
+       return m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS && 
+              a.event.getStatus() == m::MidiEvent::NOTE_OFF;
+
 }
 }} // giada::v::
index 05bf0b80270be0554198539b959dd74582eac240..1d23d3d1edfbaeea677fc1c9b014c38d1023b2bb 100644 (file)
@@ -46,12 +46,12 @@ class geSampleActionEditor : public geBaseActionEditor
 {
 public:
 
-       geSampleActionEditor(Pixel x, Pixel y);
+       geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*);
        ~geSampleActionEditor();
 
        void draw() override;
 
-       void rebuild() override;
+       void rebuild(c::actionEditor::Data& d) override;
 
 private:
 
index c3d5195f0b16e9e12e5d2b61e0d9906265afa770..57918422778f7b0e5d4602f9067155c6404aa358 100644 (file)
@@ -30,7 +30,6 @@
 #include <FL/fl_draw.H>
 #include "utils/log.h"
 #include "utils/math.h"
-#include "core/channels/midiChannel.h"
 #include "core/const.h"
 #include "core/conf.h"
 #include "core/action.h"
@@ -44,8 +43,8 @@
 namespace giada {
 namespace v
 {
-geVelocityEditor::geVelocityEditor(Pixel x, Pixel y)
-:      geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH)
+geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
+:      geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b)
 {
 }
 
@@ -110,9 +109,9 @@ int geVelocityEditor::yToValue(Pixel px) const
 /* -------------------------------------------------------------------------- */
 
 
-void geVelocityEditor::rebuild()
+void geVelocityEditor::rebuild(c::actionEditor::Data& d)
 {
-       namespace ca = c::actionEditor;
+       m_data = &d;
 
        /* Remove all existing actions and set a new width, according to the current
        zoom level. */
@@ -120,8 +119,8 @@ void geVelocityEditor::rebuild()
        clear();
        size(m_base->fullWidth, h());
 
-       for (const m::Action& action : m_base->getActions())
-       {
+       for (const m::Action& action : m_data->actions) {
+               
                if (action.event.getStatus() == m::MidiEvent::NOTE_OFF)
                        continue;
 
index 3dc141ad510aa7324c7bd0bbba1a701ba31e4016..be19b8ad924994afa2a448e26a6a41c0565cdeb6 100644 (file)
@@ -46,20 +46,20 @@ class geVelocityEditor : public geBaseActionEditor
 {
 public:
 
-       geVelocityEditor(Pixel x, Pixel y);
+       geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*);
        ~geVelocityEditor();
 
        void draw() override;
 
-       void rebuild() override;
+       void rebuild(c::actionEditor::Data& d) override;
 
 private:
 
        void onMoveAction()    override;
        void onRefreshAction() override;
-       void onAddAction()     override{};
-       void onDeleteAction()  override{};
-       void onResizeAction()  override{};
+       void onAddAction()     override {};
+       void onDeleteAction()  override {};
+       void onResizeAction()  override {};
 
        Pixel valueToY(int v)   const;
        int   yToValue(Pixel y) const;
index eb82d800be7e76eda482424bbec6f1125a80b8fa..3eb53ba67333a9c4fdcab8015e85fb8431c53468 100644 (file)
@@ -2,9 +2,6 @@
  *
  * Giada - Your Hardcore Loopmachine
  *
- * geBaseButton
- * Base class for every button widget.
- *
  * -----------------------------------------------------------------------------
  *
  * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
index 6c60eb11ae78825a0a581bb68724f7b35a606823..1a23cee578ca80b0795672892bb927e6857eee64 100644 (file)
@@ -2,9 +2,6 @@
  *
  * Giada - Your Hardcore Loopmachine
  *
- * geBaseButton
- * Base class for every button widget.
- *
  * -----------------------------------------------------------------------------
  *
  * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
@@ -35,6 +32,8 @@
 #include <string>
 #include <FL/Fl_Button.H>
 
+/* geBaseButton
+Base class for every button widget. */
 
 class geBaseButton : public Fl_Button
 {
diff --git a/src/gui/elems/basics/group.cpp b/src/gui/elems/basics/group.cpp
new file mode 100644 (file)
index 0000000..d888c1f
--- /dev/null
@@ -0,0 +1,94 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <algorithm>
+#include "group.h"
+
+
+namespace giada {
+namespace v 
+{
+geGroup::geGroup(int x, int y) : Fl_Group(x, y, 0, 0)
+{
+    end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::size_t geGroup::countChildren() const
+{
+    return m_widgets.size();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geGroup::add(Fl_Widget* widget)
+{
+    widget->position(widget->x() + x(), widget->y() + y());
+    
+    Fl_Group::add(widget);
+    m_widgets.push_back(widget);
+
+    int newW = 0;
+    int newH = 0;
+
+    for (const Fl_Widget* wg : m_widgets) {
+        newW = std::max(newW, (wg->x() + wg->w()) - x());
+        newH = std::max(newH, (wg->y() + wg->h()) - y());
+    }
+
+    /* Don't call size(newW, newH) as it changes widgets position. Adjust width
+    and height manually instead. */
+
+    w(newW); h(newH);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geGroup::getChild(std::size_t i)
+{
+    return m_widgets.at(i); // Throws std::out_of_range in case
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geGroup::getLastChild()
+{
+    return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
+}
+}}
\ No newline at end of file
diff --git a/src/gui/elems/basics/group.h b/src/gui/elems/basics/group.h
new file mode 100644 (file)
index 0000000..3bc0ae6
--- /dev/null
@@ -0,0 +1,76 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * 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 GE_GROUP_H
+#define GE_GROUP_H
+
+
+#include <vector>
+#include <FL/Fl_Group.H>
+
+
+namespace giada {
+namespace v 
+{
+/* geGroup
+A group that resizes itself accoring to the content. */
+
+class geGroup : public Fl_Group
+{
+public:
+
+       geGroup(int x, int y);
+
+    /* countChildren
+    Returns the number of widgets contained in this group. */
+    
+    std::size_t countChildren() const;
+
+    /* add
+    Adds a Fl_Widget 'w' to this group. Coordinates are relative to the group,
+    so origin starts at (0, 0). */
+
+    void add(Fl_Widget* w);
+
+    Fl_Widget* getChild(std::size_t i);
+    Fl_Widget* getLastChild();
+
+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. */
+
+    std::vector<Fl_Widget*> m_widgets;    
+};
+}}
+
+
+#endif
diff --git a/src/gui/elems/basics/pack.cpp b/src/gui/elems/basics/pack.cpp
new file mode 100644 (file)
index 0000000..288df56
--- /dev/null
@@ -0,0 +1,62 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/const.h"
+#include "pack.h"
+
+
+namespace giada {
+namespace v 
+{
+gePack::gePack(int x, int y, Direction d, int gutter)
+: geGroup    (x, y)
+, m_direction(d)
+, m_gutter   (gutter)
+{
+    end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePack::add(Fl_Widget* widget)
+{
+    if (countChildren() == 0)
+        widget->position(0, 0);
+    else
+    if (m_direction == Direction::HORIZONTAL)
+        widget->position((getLastChild()->x() + getLastChild()->w() + m_gutter) - x(), 0);
+    else
+        widget->position(0, (getLastChild()->y() + getLastChild()->h() + m_gutter) - y());
+
+    geGroup::add(widget);
+}
+}}
\ No newline at end of file
diff --git a/src/gui/elems/basics/pack.h b/src/gui/elems/basics/pack.h
new file mode 100644 (file)
index 0000000..dda1233
--- /dev/null
@@ -0,0 +1,67 @@
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * 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 GE_PACK_H
+#define GE_PACK_H
+
+
+#include "core/const.h"
+#include "gui/elems/basics/group.h"
+
+
+namespace giada {
+namespace v 
+{
+enum class Direction { HORIZONTAL, VERTICAL };
+
+/* gePack
+A stack of widgets that resize itself according to its content. */
+
+class gePack : public geGroup
+{
+public:
+
+       gePack(int x, int y, Direction d, int gutter=G_GUI_INNER_MARGIN);
+
+    /* add
+    Adds a Fl_Widget 'w' to this pack. Coordinates are relative to the group,
+    so origin starts at (0, 0). */
+
+    void add(Fl_Widget* w);
+
+private:
+
+    Direction m_direction;
+    int       m_gutter;
+};
+}}
+
+
+#endif
index fb405113351bed7ec812284366108b39bd830bb9..eb1fce098c61a82e0203063c0ea96189e2f88882 100644 (file)
@@ -35,8 +35,9 @@
 
 
 geScroll::geScroll(int x, int y, int w, int h, int t)
-       : Fl_Scroll(x, y, w, h)
+: Fl_Scroll(x, y, w, h)
 {
+       end();
        type(t);
 
        scrollbar.color(G_COLOR_GREY_2);
diff --git a/src/gui/elems/basics/scrollPack.cpp b/src/gui/elems/basics/scrollPack.cpp
new file mode 100644 (file)
index 0000000..364cbff
--- /dev/null
@@ -0,0 +1,89 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/const.h"
+#include "boxtypes.h"
+#include "scrollPack.h"
+
+
+namespace giada {
+namespace v 
+{
+geScrollPack::geScrollPack(int x, int y, int w, int h, int type, Direction dir,
+    int gutter)
+: geScroll   (x, y, w, h, type)
+, m_direction(dir)
+, m_gutter   (gutter)
+{
+    end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::size_t geScrollPack::countChildren() const
+{
+    return m_widgets.size();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geScrollPack::add(Fl_Widget* w)
+{
+    if (countChildren() == 0)
+        w->position(x(), y());
+    else
+    if (m_direction == Direction::HORIZONTAL)
+        w->position((getLastChild()->x() + getLastChild()->w() + m_gutter), y());
+    else
+        w->position(x(), (getLastChild()->y() + getLastChild()->h() + m_gutter));
+
+    geScroll::add(w);
+    m_widgets.push_back(w);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geScrollPack::getChild(std::size_t i)
+{
+    return m_widgets.at(i); // Throws std::out_of_range in case
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geScrollPack::getLastChild()
+{
+    return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
+}
+}}
\ No newline at end of file
diff --git a/src/gui/elems/basics/scrollPack.h b/src/gui/elems/basics/scrollPack.h
new file mode 100644 (file)
index 0000000..64de3b6
--- /dev/null
@@ -0,0 +1,73 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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 GE_SCROLL_PACK_H
+#define GE_SCROLL_PACK_H
+
+
+#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/pack.h"
+
+
+namespace giada {
+namespace v 
+{
+/* geScrollPack
+A scrollable viewport that contains packed widgets. */
+
+class geScrollPack : public geScroll
+{
+public:
+
+       geScrollPack(int x, int y, int w, int h, int type=Fl_Scroll::BOTH, 
+        Direction d=Direction::HORIZONTAL, int gutter=G_GUI_INNER_MARGIN);
+
+    /* countChildren
+    Returns the number of widgets contained in this group. */
+    
+    std::size_t countChildren() const;
+    
+    void add(Fl_Widget* w);
+
+    Fl_Widget* getChild(std::size_t i);
+    Fl_Widget* getLastChild();
+
+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. */
+
+    std::vector<Fl_Widget*> m_widgets;  
+
+    Direction m_direction;
+    int       m_gutter;
+};
+}}
+
+
+#endif
index 931e76148f654a41f03791255aa02dcc141789d0..904c78d0acc26d0f1987f9e3713820ad298b795e 100644 (file)
@@ -42,16 +42,10 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H)
 {
        begin();
 
-       Fl_Group* radioGrp_1 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex
-               new geBox(x(), y()+10, 70, 25, "When a channel with recorded actions is halted:", FL_ALIGN_LEFT);
-               recsStopOnChanHalt_1 = new geRadio(x()+25, y()+35, 280, 20, "stop it immediately");
-               recsStopOnChanHalt_0 = new geRadio(x()+25, y()+60, 280, 20, "play it until finished");
-       radioGrp_1->end();
-
-       Fl_Group* radioGrp_2 = new Fl_Group(x(), radioGrp_1->y()+radioGrp_1->h(), w(), 70); // radio group for the mutex
-               new geBox(x(), y()+80, 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT);
-               chansStopOnSeqHalt_1 = new geRadio(x()+25, y()+105, 280, 20, "stop immediately all dynamic channels");
-               chansStopOnSeqHalt_0 = new geRadio(x()+25, y()+130, 280, 20, "play all dynamic channels until finished");
+       Fl_Group* radioGrp_2 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex
+               new geBox(x(), radioGrp_2->y(), 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT);
+               chansStopOnSeqHalt_1 = new geRadio(x()+25, radioGrp_2->y() + 25, 280, 20, "stop immediately all dynamic channels");
+               chansStopOnSeqHalt_0 = new geRadio(x()+25, radioGrp_2->y() + 50, 280, 20, "play all dynamic channels until finished");
        radioGrp_2->end();
 
        treatRecsAsLoops      = new geCheck(x(), radioGrp_2->y()+radioGrp_2->h() + 15, 280, 20, "Treat one shot channels with actions as loops");
@@ -62,13 +56,10 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H)
        labelsize(G_GUI_FONT_SIZE_BASE);
        selection_color(G_COLOR_GREY_4);
 
-       m::conf::conf.recsStopOnChanHalt == 1 ? recsStopOnChanHalt_1->value(1) : recsStopOnChanHalt_0->value(1);
        m::conf::conf.chansStopOnSeqHalt == 1 ? chansStopOnSeqHalt_1->value(1) : chansStopOnSeqHalt_0->value(1);
        treatRecsAsLoops->value(m::conf::conf.treatRecsAsLoops);
        inputMonitorDefaultOn->value(m::conf::conf.inputMonitorDefaultOn);
 
-       recsStopOnChanHalt_1->callback(cb_radio_mutex, (void*)this);
-       recsStopOnChanHalt_0->callback(cb_radio_mutex, (void*)this);
        chansStopOnSeqHalt_1->callback(cb_radio_mutex, (void*)this);
        chansStopOnSeqHalt_0->callback(cb_radio_mutex, (void*)this);
 }
@@ -97,7 +88,6 @@ void geTabBehaviors::cb_radio_mutex(Fl_Widget* w)
 
 void geTabBehaviors::save()
 {
-       m::conf::conf.recsStopOnChanHalt = recsStopOnChanHalt_1->value() == 1 ? 1 : 0;
        m::conf::conf.chansStopOnSeqHalt = chansStopOnSeqHalt_1->value() == 1 ? 1 : 0;
        m::conf::conf.treatRecsAsLoops = treatRecsAsLoops->value() == 1 ? 1 : 0;
        m::conf::conf.inputMonitorDefaultOn = inputMonitorDefaultOn->value() == 1 ? 1 : 0;
index f5a9f7c187414a4e108664bf89f498ba000db147..59fb16d8f3db8e30885139d3791435df9f1d0355 100644 (file)
@@ -47,8 +47,6 @@ public:
 
        void save();
 
-       geRadio *recsStopOnChanHalt_1;
-       geRadio *recsStopOnChanHalt_0;
        geRadio *chansStopOnSeqHalt_1;
        geRadio *chansStopOnSeqHalt_0;
        geCheck *treatRecsAsLoops;
index 1c9441f42d0f9d69eb80309a4e26488e60a48b9e..20623a423b46973dd53a6c04da014db48be5fd6d 100644 (file)
 
 #include <FL/Fl.H>
 #include <FL/fl_draw.H>
-#include "core/channels/channel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/graphics.h"
 #include "core/pluginHost.h"
 #include "utils/gui.h"
 #include "glue/channel.h"
+#include "glue/events.h"
 #include "gui/dialogs/mainWindow.h"
 #include "gui/dialogs/pluginList.h"
 #include "gui/elems/basics/button.h"
@@ -51,9 +51,9 @@ extern giada::v::gdMainWindow* G_MainWin;
 namespace giada {
 namespace v
 {
-geChannel::geChannel(int X, int Y, int W, int H, ID channelId)
-: Fl_Group (X, Y, W, H),
-  channelId(channelId)
+geChannel::geChannel(int X, int Y, int W, int H, c::channel::Data d)
+: Fl_Group (X, Y, W, H)
+, m_channel(d)
 {
 }
 
@@ -97,16 +97,18 @@ void geChannel::cb_openFxWindow(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_ope
 
 void geChannel::refresh()
 {
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               if (mainButton->visible())
-                       mainButton->refresh();
-               if (c.recStatus == ChannelStatus::WAIT || c.playStatus == ChannelStatus::WAIT)
-                       blink();
-               playButton->setStatus(c.isPlaying());
-               mute->setStatus(c.mute);
-               solo->setStatus(c.solo);
-       });
+       ChannelStatus playStatus = m_channel.a_getPlayStatus(); 
+       ChannelStatus recStatus  = m_channel.a_getRecStatus(); 
+
+       if (mainButton->visible())
+               mainButton->refresh();
+
+       if (recStatus == ChannelStatus::WAIT || playStatus == ChannelStatus::WAIT)
+               blink();
+
+       playButton->setStatus(playStatus == ChannelStatus::PLAY || playStatus == ChannelStatus::ENDING);
+       mute->setStatus(m_channel.a_getMute());
+       solo->setStatus(m_channel.a_getSolo());
 }
 
 
@@ -115,7 +117,7 @@ void geChannel::refresh()
 
 void geChannel::cb_arm()
 {
-       c::channel::setArm(channelId, arm->value());
+       c::events::toggleArmChannel(m_channel.id, Thread::MAIN);
 }
 
 
@@ -124,7 +126,7 @@ void geChannel::cb_arm()
 
 void geChannel::cb_mute()
 {
-       c::channel::toggleMute(channelId);
+       c::events::toggleMuteChannel(m_channel.id, Thread::MAIN);
 }
 
 
@@ -133,7 +135,7 @@ void geChannel::cb_mute()
 
 void geChannel::cb_solo()
 {
-       c::channel::toggleSolo(channelId);
+       c::events::toggleSoloChannel(m_channel.id, Thread::MAIN);
 }
 
 
@@ -142,7 +144,7 @@ void geChannel::cb_solo()
 
 void geChannel::cb_changeVol()
 {
-       c::channel::setVolume(channelId, vol->value());
+       c::events::setChannelVolume(m_channel.id, vol->value(), Thread::MAIN);
 }
 
 
@@ -152,7 +154,7 @@ void geChannel::cb_changeVol()
 #ifdef WITH_VST
 void geChannel::cb_openFxWindow()
 {
-       u::gui::openSubWindow(G_MainWin, new v::gdPluginList(channelId), WID_FX_LIST);
+       u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST);
 }
 #endif
 
@@ -218,10 +220,7 @@ void geChannel::packWidgets()
 
 bool geChannel::handleKey(int e)
 {
-       m::model::ChannelsLock l(m::model::channels);
-       const m::Channel& ch = m::model::get(m::model::channels, channelId);
-       
-       if (Fl::event_key() != ch.key) 
+       if (Fl::event_key() != m_channel.key) 
                return false;
 
        if (e == FL_KEYDOWN && !playButton->value()) {  // Key not already pressed
@@ -234,7 +233,16 @@ bool geChannel::handleKey(int e)
                playButton->value(0);
                return true;
        }
-       
+
        return false;
 }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+const c::channel::Data& geChannel::getData() const
+{
+       return m_channel;
+}
 }} // giada::v::
index 10180d8f8b9ce73dca078f2d06b9c448e38c7279..d3fac32d00dcfbb67016d16e4096b5ce94d24250 100644 (file)
@@ -30,6 +30,7 @@
 
 
 #include <FL/Fl_Group.H>
+#include "glue/channel.h"
 #include "core/types.h"
 
 
@@ -40,19 +41,14 @@ class geStatusButton;
 
 
 namespace giada {
-namespace m
-{
-class Channel;
-}
 namespace v
 {
 class geChannelButton;
-
 class geChannel : public Fl_Group
 {
 public:
 
-       geChannel(int x, int y, int w, int h, ID channelId);
+       geChannel(int x, int y, int w, int h, c::channel::Data d);
 
        void draw() override;
 
@@ -72,7 +68,10 @@ public:
 
        bool handleKey(int e);
 
-       ID channelId;
+       /* getData
+       Returns a reference to the internal data. Read-only. */
+
+       const c::channel::Data& getData() const;
  
        geStatusButton*  playButton;
        geButton*        arm;
@@ -115,7 +114,7 @@ protected:
        void cb_solo();
        void cb_changeVol();
 #ifdef WITH_VST
-               void cb_openFxWindow();
+       void cb_openFxWindow();
 #endif
 
        /* blink
@@ -127,6 +126,11 @@ protected:
        Spread widgets across available space. */
 
        void packWidgets();
+
+       /* m_channel
+       Channel's data. */
+
+       c::channel::Data m_channel;
 };
 }} // giada::v::
 
index eaa2057939097e5b56db80480e08b7c58fb2f8f8..c8e7f255ffa70f2db76baa20e8bc2094659bbda9 100644 (file)
@@ -30,6 +30,7 @@
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/recorder.h"
+#include "glue/channel.h"
 #include "utils/string.h"
 #include "channelButton.h"
 
 namespace giada {
 namespace v
 {
-geChannelButton::geChannelButton(int x, int y, int w, int h, ID channelId)
-: geButton   (x, y, w, h), 
-  m_channelId(channelId),
-  m_key      ("")
+geChannelButton::geChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geButton (x, y, w, h)
+, m_channel(d)
 {
 }
 
@@ -50,35 +50,21 @@ geChannelButton::geChannelButton(int x, int y, int w, int h, ID channelId)
 
 void geChannelButton::refresh()
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               switch (c.playStatus) {
-                       case ChannelStatus::OFF:
-                       case ChannelStatus::EMPTY:
-                               setDefaultMode(); break;
-                       case ChannelStatus::PLAY:
-                               setPlayMode(); break;
-                       case ChannelStatus::ENDING:
-                               setEndingMode(); break;
-                       default: break;
-               }
-
-               switch (c.recStatus) {
-                       case ChannelStatus::ENDING:
-                               setEndingMode(); break;
-                       default: break;
-               }
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geChannelButton::setKey(int k)
-{
-       m_key = k == 0 ? "" : std::string(1, k);
-       redraw();
+       switch (m_channel.a_getPlayStatus()) {
+               case ChannelStatus::OFF:
+               case ChannelStatus::EMPTY:
+                       setDefaultMode(); break;
+               case ChannelStatus::PLAY:
+                       setPlayMode(); break;
+               case ChannelStatus::ENDING:
+                       setEndingMode(); break;
+               default: break;
+       }       
+       switch (m_channel.a_getRecStatus()) {
+               case ChannelStatus::ENDING:
+                       setEndingMode(); break;
+               default: break;
+       }
 }
 
 
@@ -89,7 +75,7 @@ void geChannelButton::draw()
 {
        geButton::draw();
 
-       if (m_key == "")
+       if (m_channel.key == 0)
                return;
 
        /* draw background */
@@ -100,7 +86,7 @@ void geChannelButton::draw()
 
        fl_color(G_COLOR_LIGHT_2);
        fl_font(FL_HELVETICA, 11);
-       fl_draw(m_key.c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER);
+       fl_draw(std::string(1, static_cast<wchar_t>(m_channel.key)).c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER);
 }
 
 
@@ -154,5 +140,4 @@ void geChannelButton::setEndingMode()
 {
        bgColor0 = G_COLOR_GREY_4;
 }
-
 }} // giada::v::
index 633addb137bb441c572a2d48d1106d320aca66b9..a648d4ed8449735caa736601c4ec7090426d5440 100644 (file)
 
 
 namespace giada {
-namespace m 
-{ 
-class Channel; 
-}
+namespace c {
+namespace channel
+{
+struct Data;
+}}
 namespace v
 {
 class geChannelButton : public geButton
 {
 public:
 
-       geChannelButton(int x, int y, int w, int h, ID channelId);
+       geChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
 
        virtual void refresh();
 
        void draw() override;
   
-       void setKey(int k);
        void setPlayMode();
        void setEndingMode();
        void setDefaultMode(const char* l=0);
@@ -58,8 +58,7 @@ public:
 
 protected:
 
-       ID          m_channelId;
-       std::string m_key;
+    const c::channel::Data& m_channel;
 };
 }} // giada::v::
 
index b4dbb0b8d58b176b6ed6ecbf85bfb247c42e9654..758580b2f77172836028d53ce735c82240cfcdc2 100644 (file)
@@ -30,7 +30,8 @@
 #include <cassert>
 #include <FL/fl_draw.H>
 #include "utils/gui.h"
-#include "core/channels/sampleChannel.h"
+#include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
 #include "core/model/model.h"
 #include "core/graphics.h"
 #include "core/const.h"
 namespace giada {
 namespace v
 {
-geChannelMode::geChannelMode(int x, int y, int w, int h, ID channelId)
-: Fl_Menu_Button(x, y, w, h), 
-  m_channelId   (channelId)
+geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d)
+: Fl_Menu_Button(x, y, w, h) 
+, m_channel     (d)
 {
        box(G_CUSTOM_BORDER_BOX);
        textsize(G_GUI_FONT_SIZE_BASE);
        textcolor(G_COLOR_LIGHT_2);
        color(G_COLOR_GREY_2);
 
-       add("Loop . basic",      0, cb_changeMode, (void*) ChannelMode::LOOP_BASIC);
-       add("Loop . once",       0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE);
-       add("Loop . once . bar", 0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE_BAR);
-       add("Loop . repeat",     0, cb_changeMode, (void*) ChannelMode::LOOP_REPEAT);
-       add("Oneshot . basic",   0, cb_changeMode, (void*) ChannelMode::SINGLE_BASIC);
-       add("Oneshot . press",   0, cb_changeMode, (void*) ChannelMode::SINGLE_PRESS);
-       add("Oneshot . retrig",  0, cb_changeMode, (void*) ChannelMode::SINGLE_RETRIG);
-       add("Oneshot . endless", 0, cb_changeMode, (void*) ChannelMode::SINGLE_ENDLESS);
+       add("Loop . basic",      0, cb_changeMode, (void*) SamplePlayerMode::LOOP_BASIC);
+       add("Loop . once",       0, cb_changeMode, (void*) SamplePlayerMode::LOOP_ONCE);
+       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 . 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);
+
+       value(static_cast<int>(m_channel.sample->mode));
 }
 
 
@@ -69,32 +72,29 @@ void geChannelMode::draw()
 {
        fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);    // border
 
-       m::model::ChannelsLock l(m::model::channels);
-       const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, m_channelId));
-       
-       switch (ch.mode) {
-               case ChannelMode::LOOP_BASIC:
+       switch (m_channel.sample->mode) {
+               case SamplePlayerMode::LOOP_BASIC:
                        fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::LOOP_ONCE:
+               case SamplePlayerMode::LOOP_ONCE:
                        fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::LOOP_ONCE_BAR:
+               case SamplePlayerMode::LOOP_ONCE_BAR:
                        fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::LOOP_REPEAT:
+               case SamplePlayerMode::LOOP_REPEAT:
                        fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::SINGLE_BASIC:
+               case SamplePlayerMode::SINGLE_BASIC:
                        fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::SINGLE_PRESS:
+               case SamplePlayerMode::SINGLE_PRESS:
                        fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::SINGLE_RETRIG:
+               case SamplePlayerMode::SINGLE_RETRIG:
                        fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1);
                        break;
-               case ChannelMode::SINGLE_ENDLESS:
+               case SamplePlayerMode::SINGLE_ENDLESS:
                        fl_draw_pixmap(oneshotEndless_xpm, x()+1, y()+1);
                        break;
        }
@@ -112,6 +112,6 @@ void geChannelMode::cb_changeMode(Fl_Widget* v, void* p) { ((geChannelMode*)v)->
 
 void geChannelMode::cb_changeMode(int mode)
 {
-       c::channel::setSampleMode(m_channelId, static_cast<ChannelMode>(mode));
+       c::channel::setSamplePlayerMode(m_channel.id, static_cast<SamplePlayerMode>(mode));
 }
 }} // giada::v::
index b6072b86d4b98fe8dbd0bbfffd2b0bdc942642f5..7333f9b8e6fbe378e7aba12eb459adf0ddb386c6 100644 (file)
 
 
 namespace giada {
-namespace m 
-{ 
-class SampleChannel; 
-}
 namespace v
 {
 class geChannelMode : public Fl_Menu_Button
 {
 public:
 
-  geChannelMode(int x, int y, int w, int h, ID channelId);
+       geChannelMode(int x, int y, int w, int h, c::channel::Data& d);
 
        void draw() override;
 
@@ -54,7 +50,7 @@ private:
     static void cb_changeMode(Fl_Widget* v, void* p);
     void cb_changeMode(int mode);
 
-    ID m_channelId;
+    c::channel::Data& m_channel;
 };
 }} // giada::v::
 
index 95d32fbeee075dcd6f99249c64de284a19bee5a4..616708a4a3063f071925c158cfb51a027e336b05 100644 (file)
 
 
 #include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
-#include "core/mixer.h"
-#include "core/clock.h"
-#include "core/recorder.h"
-#include "core/recManager.h"
 #include "core/const.h"
+#include "glue/channel.h"
 #include "channelStatus.h"
 
 
 namespace giada {
 namespace v
 {
-geChannelStatus::geChannelStatus(int x, int y, int w, int h, ID channelId)
-: Fl_Box(x, y, w, h), channelId(channelId)
+geChannelStatus::geChannelStatus(int x, int y, int w, int h, c::channel::Data& d)
+: Fl_Box   (x, y, w, h)
+, m_channel(d)
 {
 }
 
@@ -53,38 +49,32 @@ 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
 
-       m::model::ChannelsLock l(m::model::channels);
-       const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, channelId));
-       
-       if (ch.playStatus == ChannelStatus::WAIT    || 
-           ch.playStatus == ChannelStatus::ENDING  ||
-           ch.recStatus == ChannelStatus::WAIT || 
-           ch.recStatus == ChannelStatus::ENDING)
+       ChannelStatus playStatus = m_channel.a_getPlayStatus();
+       ChannelStatus recStatus  = m_channel.a_getRecStatus();
+       Pixel         pos        = 0;
+
+       if (playStatus == ChannelStatus::WAIT    || 
+           playStatus == ChannelStatus::ENDING  ||
+           recStatus  == ChannelStatus::WAIT    || 
+           recStatus  == ChannelStatus::ENDING)
        {
                fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
        }
        else
-       if (ch.playStatus == ChannelStatus::PLAY)
+       if (playStatus == ChannelStatus::PLAY) {
+               /* Equation for the progress bar: 
+               ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
+               Frame tracker = m_channel.sample->a_getTracker();
+               Frame begin   = m_channel.sample->a_getBegin();
+               Frame end     = m_channel.sample->a_getEnd();
+               pos = ((tracker - begin) * (w() - 1)) / ((end - begin));
                fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
+       }
        else
                fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2);  // status empty
 
-
-       if (m::recManager::isRecordingInput() && ch.armed)
-               fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_RED);     // take in progress
-       else
-       if (m::recManager::isRecordingAction())
-               fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_BLUE);    // action recording
-
-       /* Equation for the progress bar: 
-       ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
-
-       int pos = ch.getPosition();
-       if (pos == -1)
-               pos = 0;
-       else
-               pos = (pos * (w()-1)) / ((ch.getEnd() - ch.getBegin()));
-       fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1);
+       if (pos != 0)
+               fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1);
 }
 
 }} // giada::v::
index 3afbe8e655e1308bcff3d4b7a4cf83a3bd750062..90e0dbb13843abe3dcf4df9b60741e9817f50558 100644 (file)
 
 
 namespace giada {
-namespace m 
-{ 
-class SampleChannel; 
-}
+namespace c {
+namespace channel
+{
+struct Data;
+}}
 namespace v
 {
 class geChannelStatus : public Fl_Box
 {
 public:
 
-       geChannelStatus(int x, int y, int w, int h, ID channelId);
+       geChannelStatus(int x, int y, int w, int h, c::channel::Data& d);
 
        void draw() override;
 
-       ID channelId;
+private:
+
+       c::channel::Data& m_channel;
 };
 }} // giada::v::
 
index 1a2959fe671902569a79abd2c38107b249350067..dc0650205edeaaf1a18d265e5da36f01f27bcb23 100644 (file)
@@ -28,8 +28,7 @@
 #include <cassert>
 #include <FL/fl_draw.H>
 #include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
+#include "core/channels/state.h"
 #include "core/model/model.h"
 #include "glue/channel.h"
 #include "utils/log.h"
@@ -76,32 +75,34 @@ void geColumn::cb_addChannel(Fl_Widget* v, void* p) { ((geColumn*)p)->cb_addChan
 /* -------------------------------------------------------------------------- */
 
 
-geChannel* geColumn::addChannel(ID channelId, ChannelType t, int height)
+geChannel* geColumn::addChannel(c::channel::Data d)
 {
        geChannel* gch  = nullptr;
        Fl_Widget* last = m_channels.size() == 0 ? static_cast<Fl_Widget*>(m_addChannelBtn) : m_channels.back();
 
-       if (t == ChannelType::SAMPLE)
-               gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId);
+       if (d.type == ChannelType::SAMPLE)
+               gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d);
        else
-               gch = new geMidiChannel  (x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId);
+               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);
 
        /* Update the column height while dragging the resizer bar. */
 
-       bar->onDrag = [=](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 = [=](const Fl_Widget* w)
+       bar->onRelease = [channelId = d.id, this](const Fl_Widget* w)
        {
-               storeChannelHeight(w, channelId);
+               resizable(this);
+               c::channel::setHeight(channelId, w->h());
        };
 
        m_channels.push_back(gch);
@@ -164,7 +165,7 @@ void geColumn::cb_addChannel()
 geChannel* geColumn::getChannel(ID channelId) const
 {
        for (geChannel* c : m_channels)
-               if (c->channelId == channelId)
+               if (c->getData().id == channelId)
                        return c;
        return nullptr;
 }
@@ -214,17 +215,4 @@ int geColumn::computeHeight() const
                out += c->h() + G_GUI_INNER_MARGIN;
        return out + m_addChannelBtn->h() + G_GUI_INNER_MARGIN;
 }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geColumn::storeChannelHeight(const Fl_Widget* w, ID channelId) const
-{      
-       m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
-       {       
-               c.height = w->h();
-       });
-}
-
 }} // giada::v::
index 745f3efca4d53b7c086fa8d8d406b69b565b4b9d..fbcd6bfa642bb0c241d6746fc338a8d0cd0d2971 100644 (file)
@@ -32,6 +32,7 @@
 #include <functional>
 #include <vector>
 #include <FL/Fl_Group.H>
+#include "glue/channel.h"
 #include "core/types.h"
 
 
@@ -55,7 +56,7 @@ public:
        /* addChannel
        Adds a new channel in this column. */
 
-       geChannel* addChannel(ID channelId, ChannelType t, int size);
+       geChannel* addChannel(c::channel::Data d);
 
        /* refreshChannels
        Updates channels' graphical statues. Called on each GUI cycle. */
@@ -77,7 +78,6 @@ private:
 
        int countChannels() const;
        int computeHeight() const;
-       void storeChannelHeight(const Fl_Widget* c, ID channelId) const;
 
        std::vector<geChannel*> m_channels;
 
index 01fc0e451e2b22c93063cd0a6fdb9821f2238004..46e64fab77c9c6908b5285d9352bb315a9adefa8 100644 (file)
@@ -27,8 +27,6 @@
 
 #include <cassert>
 #include <FL/fl_draw.H>
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
 #include "glue/io.h"
 #include "glue/channel.h"
 #include "utils/fs.h"
@@ -49,22 +47,10 @@ namespace giada {
 namespace v
 {
 geKeyboard::geKeyboard(int X, int Y, int W, int H)
-: Fl_Scroll     (X, Y, W, H),
-  m_addColumnBtn(nullptr)
+: geScroll      (X, Y, W, H, Fl_Scroll::BOTH_ALWAYS)
+, m_addColumnBtn(nullptr)
 {
        end();
-
-       color(G_COLOR_GREY_1);
-       type(Fl_Scroll::BOTH_ALWAYS);
-       scrollbar.color(G_COLOR_GREY_2);
-       scrollbar.selection_color(G_COLOR_GREY_4);
-       scrollbar.labelcolor(G_COLOR_LIGHT_1);
-       scrollbar.slider(G_CUSTOM_BORDER_BOX);
-       hscrollbar.color(G_COLOR_GREY_2);
-       hscrollbar.selection_color(G_COLOR_GREY_4);
-       hscrollbar.labelcolor(G_COLOR_LIGHT_1);
-       hscrollbar.slider(G_CUSTOM_BORDER_BOX);
-
        init();
 }
 
@@ -102,13 +88,8 @@ void geKeyboard::rebuild()
        for (ColumnLayout c : layout)
                addColumn(c.width, c.id);
 
-       /* Parse the model and assign each channel to its column. */
-
-       m::model::ChannelsLock lock(m::model::channels);
-
-       for (const m::Channel* ch : m::model::channels)
-               if (!ch->isInternal())
-                       getColumn(ch->columnId)->addChannel(ch->id, ch->type, ch->height);
+       for (const c::channel::Data& ch : c::channel::getChannels())
+               getColumn(ch.columnId)->addChannel(ch);
        
        redraw();
 }
index aa4feadd2feb8050742427f26fc61bdb421da88f..29728d914b62141e0e60671e34c18b3c0d5afa9b 100644 (file)
@@ -30,8 +30,8 @@
 
 
 #include <vector>
-#include <FL/Fl_Scroll.H>
-#include "core/channels/channel.h"
+#include <functional>
+#include "gui/elems/basics/scroll.h"
 #include "core/idManager.h"
 #include "core/const.h"
 
@@ -45,9 +45,7 @@ namespace v
 {
 class geColumn;
 class geChannel;
-class geSampleChannel;
-
-class geKeyboard : public Fl_Scroll
+class geKeyboard : public geScroll
 {
 public:
 
index c70390718b3bcc09b58de9d37e0e2d50a8835ce0..34377e77fa3d0532a2b0bdfff97fbc56d52993e3 100644 (file)
@@ -29,7 +29,6 @@
 #include <FL/Fl_Menu_Button.H>
 #include "core/const.h"
 #include "core/graphics.h"
-#include "core/channels/midiChannel.h"
 #include "core/model/model.h"
 #include "core/recorder.h"
 #include "utils/gui.h"
@@ -83,38 +82,37 @@ enum class Menu
 
 void menuCallback(Fl_Widget* w, void* v)
 {
-       geMidiChannel* gch = static_cast<geMidiChannel*>(w);
+       const geMidiChannel*    gch  = static_cast<geMidiChannel*>(w);
+       const c::channel::Data& data = gch->getData();
 
-       Menu selectedItem = (Menu) (intptr_t) v;
-
-       switch (selectedItem)
+       switch ((Menu) (intptr_t) v)
        {
                case Menu::CLEAR_ACTIONS:
                case Menu::__END_CLEAR_ACTION_SUBMENU__:
                        break;
                case Menu::EDIT_ACTIONS:
-                       u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(gch->channelId), WID_ACTION_EDITOR);
+                       u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR);
                        break;
                case Menu::CLEAR_ACTIONS_ALL:
-                       c::recorder::clearAllActions(gch->channelId);
+                       c::recorder::clearAllActions(data.id);
                        break;
                case Menu::SETUP_KEYBOARD_INPUT:
-                       u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId), WID_KEY_GRABBER);
+                       u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER);
                        break;
                case Menu::SETUP_MIDI_INPUT:
-                       u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId), WID_MIDI_INPUT);
+                       u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT);
                        break;
                case Menu::SETUP_MIDI_OUTPUT:
-                       u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(gch->channelId), WID_MIDI_OUTPUT);
+                       u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT);
                        break;
                case Menu::CLONE_CHANNEL:
-                       c::channel::cloneChannel(gch->channelId);
+                       c::channel::cloneChannel(data.id);
                        break;          
                case Menu::RENAME_CHANNEL:
-                       u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId), WID_SAMPLE_NAME);
+                       u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME);
                        break;
                case Menu::DELETE_CHANNEL:
-                       c::channel::deleteChannel(gch->channelId);
+                       c::channel::deleteChannel(data.id);
                        break;
        }
 }
@@ -124,8 +122,9 @@ void menuCallback(Fl_Widget* w, void* v)
 /* -------------------------------------------------------------------------- */
 
 
-geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId)
-: geChannel(X, Y, W, H, channelId)
+geMidiChannel::geMidiChannel(int X, int Y, int W, int H, c::channel::Data d)
+: geChannel(X, Y, W, H, d)
+, m_data   (d)
 {
 #if defined(WITH_VST)
        constexpr int delta = 6 * (G_GUI_UNIT + G_GUI_INNER_MARGIN);
@@ -135,7 +134,7 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId)
 
        playButton = new geStatusButton     (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm);
        arm        = new geButton           (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm);
-       mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId);
+       mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel);
        mute       = new geStatusButton     (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm);
        solo       = new geStatusButton     (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm);
 #if defined(WITH_VST)
@@ -149,18 +148,15 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId)
 
        resizable(mainButton);
 
-       m::model::ChannelsLock l(m::model::channels);
-       const m::Channel& ch = m::model::get(m::model::channels, channelId);
-
 #ifdef WITH_VST
-       fx->setStatus(ch.pluginIds.size() > 0);
+       fx->setStatus(m_channel.pluginIds.size() > 0);
 #endif
 
        playButton->callback(cb_playButton, (void*)this);
        playButton->when(FL_WHEN_CHANGED);   // On keypress && on keyrelease
 
        arm->type(FL_TOGGLE_BUTTON);
-       arm->value(ch.armed);
+       arm->value(m_channel.a_isArmed());
        arm->callback(cb_arm, (void*)this);
 
 #ifdef WITH_VST
@@ -173,10 +169,9 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId)
        solo->type(FL_TOGGLE_BUTTON);
        solo->callback(cb_solo, (void*)this);
 
-       mainButton->setKey(ch.key);
        mainButton->callback(cb_openMenu, (void*)this);
 
-       vol->value(ch.volume);
+       vol->value(m_channel.volume);
        vol->callback(cb_changeVol, (void*)this);
 
        size(w(), h()); // Force responsiveness
@@ -195,7 +190,7 @@ void geMidiChannel::cb_openMenu(Fl_Widget* v, void* p) { ((geMidiChannel*)p)->cb
 
 void geMidiChannel::cb_playButton()
 {
-       v::dispatcher::dispatchTouch(this, playButton->value());
+       v::dispatcher::dispatchTouch(*this, playButton->value());
 }
 
 
@@ -207,7 +202,7 @@ 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},
+                       {"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},
@@ -220,11 +215,8 @@ void geMidiChannel::cb_openMenu()
 
        /* No 'clear actions' if there are no actions. */
 
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               if (!c.hasActions)
-                       rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate();
-       });
+       if (!m_data.hasActions)
+               rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate();
 
        Fl_Menu_Button b(0, 0, 100, 50);
        b.box(G_CUSTOM_BORDER_BOX);
index 0dd051d4b0ac4aa78ba0a7600ec180792e579352..c0cfed09eb44ec3e36a6047216d68bf63062350c 100644 (file)
 
 
 namespace giada {
-namespace m
-{
-class MidiChannel;
-}
 namespace v
 {
 class geMidiChannel : public geChannel
 {
 public:
 
-    geMidiChannel(int x, int y, int w, int h, ID channelId);
+    geMidiChannel(int x, int y, int w, int h, c::channel::Data d);
 
     void resize(int x, int y, int w, int h) override;
 
@@ -54,6 +50,8 @@ private:
        static void cb_openMenu(Fl_Widget* v, void* p);
        void cb_playButton();
        void cb_openMenu();
+
+       c::channel::Data m_data;
 };
 }} // giada::v::
 
index b7c4dc1ecce4aec658471fb8c5c4c1db56f09826..9c5deb5be87f48ef2238b4b7d22732afdd45ecfd 100644 (file)
 
 
 #include "utils/string.h"
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
+#include "glue/channel.h"
 #include "midiChannelButton.h"
 
 
 namespace giada {
 namespace v
 {
-geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, ID channelId)
-: geChannelButton(x, y, w, h, channelId)
+geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geChannelButton(x, y, w, h, d)
 {
-    std::string l; 
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               const m::MidiChannel& mc = static_cast<m::MidiChannel&>(c);
-               if (mc.name.empty())
-                       l = "-- MIDI --";
-               else
-                       l = mc.name.c_str();
-
-               if (mc.midiOut) 
-                       l += " (ch " + u::string::iToString(mc.midiOutChan + 1) + " out)";
-       });
-
-    label(l.c_str());
 }
 
 
@@ -62,12 +46,25 @@ void geMidiChannelButton::refresh()
 {
        geChannelButton::refresh();
 
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               if (m::recManager::isRecordingAction() && c.armed)
-                       setActionRecordMode();
-       });
+       refreshLabel();
+
+       if (m_channel.a_isRecordingAction() && m_channel.a_isArmed())
+               setActionRecordMode();
        
        redraw();
 }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiChannelButton::refreshLabel()
+{
+    std::string l = m_channel.name.empty() ? "-- MIDI --" : m_channel.name; 
+
+       if (m_channel.midi->a_isOutputEnabled())
+               l += " (ch " + std::to_string(m_channel.midi->a_getFilter() + 1) + " out)";
+
+       label(l.c_str());
+}
 }} // giada::v::
index 712bfc7993ea633feab451a9198533f88fd23af5..45c702afbce56cd35cdd9843070ac42e2e7845e4 100644 (file)
 
 
 namespace giada {
-namespace m 
-{ 
-class MidiChannel; 
-}
 namespace v
 {
 class geMidiChannelButton : public geChannelButton
 {
 public:
 
-       geMidiChannelButton(int x, int y, int w, int h, ID channelId);
+       geMidiChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
        
        void refresh() override;
+
+private:
+
+       void refreshLabel();
 };
 }} // giada::v::
 
index 2bfe9163bad2f35c2adf3640142730f0368f4950..1ed3cd6d2e69e811cd394e067a01e52c0559f728 100644 (file)
@@ -26,7 +26,8 @@
 
 
 #include <cassert>
-#include "core/channels/sampleChannel.h"
+#include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
 #include "core/model/model.h"
 #include "core/mixer.h"
 #include "core/conf.h"
@@ -37,6 +38,7 @@
 #include "core/recManager.h"
 #include "glue/io.h"
 #include "glue/channel.h"
+#include "glue/events.h"
 #include "glue/recorder.h"
 #include "glue/storage.h"
 #include "utils/gui.h"
@@ -98,57 +100,48 @@ enum class Menu
 
 void menuCallback(Fl_Widget* w, void* v)
 {
-       geSampleChannel* gch = static_cast<geSampleChannel*>(w);
+       const geSampleChannel*  gch  = static_cast<geSampleChannel*>(w);
+       const c::channel::Data& data = gch->getData();
 
-       ID    waveId;
-       bool inputMonitor;
-       m::model::onGet(m::model::channels, gch->channelId, [&](m::Channel& c)
-       {
-               waveId       = static_cast<m::SampleChannel&>(c).waveId;
-               inputMonitor = static_cast<m::SampleChannel&>(c).inputMonitor;
-       });
-
-       Menu selectedItem = (Menu) (intptr_t) v;
-
-       switch (selectedItem) {
+       switch ((Menu) (intptr_t) v) {
                case Menu::INPUT_MONITOR: {
-                       c::channel::setInputMonitor(gch->channelId, !inputMonitor);
+                       c::channel::setInputMonitor(data.id, !data.sample->a_getInputMonitor());
                        break;
                }
                case Menu::LOAD_SAMPLE: {
                        gdWindow* w = new gdBrowserLoad("Browse sample", 
-                               m::conf::conf.samplePath.c_str(), c::storage::loadSample, gch->channelId);
+                               m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id);
                        u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
                        break;
                }
                case Menu::EXPORT_SAMPLE: {
                        gdWindow* w = new gdBrowserSave("Save sample", 
-                               m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, gch->channelId);
+                               m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id);
                        u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
                        break;
                }
                case Menu::SETUP_KEYBOARD_INPUT: {
-                       u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId), 
+                       u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), 
                                WID_KEY_GRABBER);
                        break;
                }
                case Menu::SETUP_MIDI_INPUT: {
-                       u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId), 
+                       u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), 
                                WID_MIDI_INPUT);
                        break;
                }
                case Menu::SETUP_MIDI_OUTPUT: {
-                       u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(gch->channelId), 
+                       u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id), 
                                WID_MIDI_OUTPUT);
                        break;
                }
                case Menu::EDIT_SAMPLE: {
-                       u::gui::openSubWindow(G_MainWin, new gdSampleEditor(gch->channelId, waveId), 
+                       u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id),
                                WID_SAMPLE_EDITOR);
                        break;
                }
                case Menu::EDIT_ACTIONS: {
-                       u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(gch->channelId), 
+                       u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id), 
                                WID_ACTION_EDITOR);
                        break;
                }
@@ -156,32 +149,32 @@ void menuCallback(Fl_Widget* w, void* v)
                case Menu::__END_CLEAR_ACTIONS_SUBMENU__:
                        break;
                case Menu::CLEAR_ACTIONS_ALL: {
-                       c::recorder::clearAllActions(gch->channelId);
+                       c::recorder::clearAllActions(data.id);
                        break;
                }
                case Menu::CLEAR_ACTIONS_VOLUME: {
-                       c::recorder::clearVolumeActions(gch->channelId);
+                       c::recorder::clearVolumeActions(data.id);
                        break;
                }
                case Menu::CLEAR_ACTIONS_START_STOP: {
-                       c::recorder::clearStartStopActions(gch->channelId);
+                       c::recorder::clearStartStopActions(data.id);
                        break;
                }
                case Menu::CLONE_CHANNEL: {
-                       c::channel::cloneChannel(gch->channelId);
+                       c::channel::cloneChannel(data.id);
                        break;
                }
                case Menu::RENAME_CHANNEL: {
-                       u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId), 
+                       u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), 
                                WID_SAMPLE_NAME);
                        break;
                }
                case Menu::FREE_CHANNEL: {
-                       c::channel::freeChannel(gch->channelId);
+                       c::channel::freeChannel(data.id);
                        break;
                }
                case Menu::DELETE_CHANNEL: {
-                       c::channel::deleteChannel(gch->channelId);
+                       c::channel::deleteChannel(data.id);
                        break;
                }
        }
@@ -194,8 +187,8 @@ void menuCallback(Fl_Widget* w, void* v)
 /* -------------------------------------------------------------------------- */
 
 
-geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId)
-: geChannel(X, Y, W, H, channelId)
+geSampleChannel::geSampleChannel(int X, int Y, int W, int H, c::channel::Data d)
+: geChannel(X, Y, W, H, d)
 {
 #if defined(WITH_VST)
        constexpr int delta = 9 * (G_GUI_UNIT + G_GUI_INNER_MARGIN);
@@ -205,10 +198,10 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId)
 
        playButton  = new geStatusButton       (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm);
        arm         = new geButton             (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm);
-       status      = new geChannelStatus      (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, channelId);
-       mainButton  = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId);
+       status      = new geChannelStatus      (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, m_channel);
+       mainButton  = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel);
        readActions = new geStatusButton       (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, readActionOff_xpm, readActionOn_xpm, readActionDisabled_xpm);
-       modeBox     = new geChannelMode        (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, channelId);
+       modeBox     = new geChannelMode        (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, m_channel);
        mute        = new geStatusButton       (modeBox->x() + modeBox->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm);
        solo        = new geStatusButton       (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm);
 #if defined(WITH_VST)
@@ -222,21 +215,15 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId)
 
        resizable(mainButton);
 
-       m::model::ChannelsLock l(m::model::channels);
-       const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, channelId));
-
-       modeBox->value(static_cast<int>(ch.mode));
-       modeBox->redraw();
-
 #ifdef WITH_VST
-       fx->setStatus(ch.pluginIds.size() > 0);
+       fx->setStatus(m_channel.pluginIds.size() > 0);
 #endif
 
        playButton->callback(cb_playButton, (void*)this);
        playButton->when(FL_WHEN_CHANGED);   // On keypress && on keyrelease
 
        arm->type(FL_TOGGLE_BUTTON);
-       arm->value(ch.armed);
+       arm->value(m_channel.a_isArmed());
        arm->callback(cb_arm, (void*)this);
 
 #ifdef WITH_VST
@@ -249,13 +236,11 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId)
        solo->type(FL_TOGGLE_BUTTON);
        solo->callback(cb_solo, (void*)this);
 
-       mainButton->setKey(ch.key);
        mainButton->callback(cb_openMenu, (void*)this);
 
-       readActions->setStatus(ch.readActions);
        readActions->callback(cb_readActions, (void*)this);
 
-       vol->value(ch.volume);
+       vol->value(m_channel.volume);
        vol->callback(cb_changeVol, (void*)this);
 
        size(w(), h()); // Force responsiveness
@@ -275,7 +260,7 @@ void geSampleChannel::cb_readActions(Fl_Widget* v, void* p) { ((geSampleChannel*
 
 void geSampleChannel::cb_playButton()
 {
-       v::dispatcher::dispatchTouch(this, playButton->value());
+       v::dispatcher::dispatchTouch(*this, playButton->value());
 }
 
 
@@ -284,19 +269,6 @@ void geSampleChannel::cb_playButton()
 
 void geSampleChannel::cb_openMenu()
 {
-       bool inputMonitor;
-       bool isEmptyOrMissing;
-       bool hasActions;
-       bool isAnyLoopMode;
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               inputMonitor     = sc.inputMonitor;
-               isEmptyOrMissing = sc.playStatus == ChannelStatus::EMPTY || sc.playStatus == ChannelStatus::MISSING;
-               hasActions       = sc.hasActions;
-               isAnyLoopMode    = sc.isAnyLoopMode();
-       });
-
        /* If you're recording (input or actions) no menu is allowed; you can't do
        anything, especially deallocate the channel. */
 
@@ -305,7 +277,7 @@ void geSampleChannel::cb_openMenu()
 
        Fl_Menu_Item rclick_menu[] = {
                {"Input monitor",            0, menuCallback, (void*) Menu::INPUT_MONITOR,
-                       FL_MENU_TOGGLE | FL_MENU_DIVIDER | (inputMonitor ? FL_MENU_VALUE : 0)},
+                       FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->a_getInputMonitor() ? 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},
@@ -325,21 +297,20 @@ void geSampleChannel::cb_openMenu()
                {0}
        };
 
-       if (isEmptyOrMissing) {
+       if (m_channel.sample->waveId == 0) {
                rclick_menu[(int) Menu::EXPORT_SAMPLE].deactivate();
                rclick_menu[(int) Menu::EDIT_SAMPLE].deactivate();
                rclick_menu[(int) Menu::FREE_CHANNEL].deactivate();
                rclick_menu[(int) Menu::RENAME_CHANNEL].deactivate();
        }
 
-       if (!hasActions)
+       if (!m_channel.hasActions)
                rclick_menu[(int) Menu::CLEAR_ACTIONS].deactivate();
 
-
        /* No 'clear start/stop actions' for those channels in loop mode: they cannot
        have start/stop actions. */
 
-       if (isAnyLoopMode)
+       if (m_channel.sample->isLoop)
                rclick_menu[(int) Menu::CLEAR_ACTIONS_START_STOP].deactivate();
 
        Fl_Menu_Button b(0, 0, 100, 50);
@@ -360,7 +331,7 @@ void geSampleChannel::cb_openMenu()
 
 void geSampleChannel::cb_readActions()
 {
-       c::channel::toggleReadingActions(channelId);
+       c::events::toggleReadActionsChannel(m_channel.id, Thread::MAIN);
 }
 
 
@@ -371,17 +342,14 @@ void geSampleChannel::refresh()
 {
        geChannel::refresh();
 
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               if (c.hasData()) 
-                       status->redraw();
-               if (c.hasActions) {
-                       readActions->activate();
-                       readActions->setStatus(c.readActions);
-               }
-               else
-                       readActions->deactivate();
-       });
+       if (m_channel.sample->waveId != 0)
+               status->redraw();
+       if (m_channel.hasActions) {
+               readActions->activate();
+               readActions->setStatus(m_channel.a_getReadActions());           
+       }
+       else
+               readActions->deactivate();
 }
 
 
index 8e42f809e07850da2fa3324e7d601197ba9b8ff1..4a2ac7105aa76da6eb6b161f0c2e47c85c1e2560 100644 (file)
@@ -29,6 +29,7 @@
 #define GE_SAMPLE_CHANNEL_H
 
 
+#include "glue/channel.h"
 #include "channel.h"
 
 
@@ -36,23 +37,19 @@ class geStatusButton;
 
 
 namespace giada {
-namespace m 
-{ 
-class SampleChannel; 
-}
 namespace v
 {
 class geChannelMode;
-
 class geSampleChannel : public geChannel
 {
 public:
 
-       geSampleChannel(int x, int y, int w, int h, ID channelId);
+       geSampleChannel(int x, int y, int w, int h, c::channel::Data d);
 
        void resize(int x, int y, int w, int h) override;
        void draw() override;
 
+
        void refresh() override;
 
        geChannelMode*  modeBox;
index 74aa1e5255c3e9a237b5447172f7d07783d7ee9f..d7d08cc3c6c683e8f5bcff74c7a9b901a5ef4708 100644 (file)
 
 
 #include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
-#include "core/const.h"
-#include "core/model/model.h"
-#include "core/wave.h"
-#include "core/mixer.h"
-#include "core/recorder.h"
-#include "core/recManager.h"
 #include "utils/string.h"
 #include "utils/fs.h"
 #include "glue/channel.h"
@@ -48,33 +41,18 @@ extern giada::v::gdMainWindow* G_MainWin;
 namespace giada {
 namespace v
 {
-geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, ID channelId)
-: geChannelButton(x, y, w, h, channelId)
+geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geChannelButton(x, y, w, h, d)
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-               
-               switch (sc.playStatus) {
-                       case ChannelStatus::EMPTY:
-                               label("-- no sample --");
-                               break;
-                       case ChannelStatus::MISSING:
-                       case ChannelStatus::WRONG:
-                               label("* file not found! *");
-                               break;
-                       default:
-                               if (sc.name.empty()) {
-                                       m::model::onGet(m::model::waves, sc.waveId, [&](m::Wave& w)
-                                       {
-                                               label(w.getBasename(false).c_str());
-                                       });
-                               }
-                               else
-                                       label(sc.name.c_str());
-                               break;
-               }
-       });
+       switch (m_channel.a_getPlayStatus()) {
+               case ChannelStatus::MISSING:
+               case ChannelStatus::WRONG:
+                       label("* file not found! *");
+                       break;
+               default:
+                       label(m_channel.sample->waveId == 0 ? "-- no sample --" : m_channel.name.c_str());
+                       break;
+       }
 }
 
 
@@ -84,15 +62,12 @@ geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, ID chan
 void geSampleChannelButton::refresh()
 {
        geChannelButton::refresh();
-       
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               if (m::recManager::isRecordingInput() && c.armed)
-                       setInputRecordMode();
-               else
-               if (m::recManager::isRecordingAction() && c.hasData())
-                       setActionRecordMode();
-       });
+
+       if (m_channel.a_isRecordingInput() && m_channel.a_isArmed())
+               setInputRecordMode();
+       else
+       if (m_channel.a_isRecordingAction() && m_channel.sample->waveId != 0 && !m_channel.sample->isLoop)
+               setActionRecordMode();
 
        redraw();
 }
@@ -112,7 +87,7 @@ int geSampleChannelButton::handle(int e)
                        break;
                }
                case FL_PASTE: {
-                       c::channel::loadChannel(m_channelId, u::string::trim(u::fs::stripFileUrl(Fl::event_text())));
+                       c::channel::loadChannel(m_channel.id, u::string::trim(u::fs::stripFileUrl(Fl::event_text())));
                        ret = 1;
                        break;
                }
index e59cc08e7918fc51e6a983b2e917d8df90a2d9bd..01b36e723f4796decbcd01f5db354a7a37982738 100644 (file)
 
 
 namespace giada {
-namespace m 
-{ 
-class SampleChannel; 
-}
 namespace v
 {
 class geSampleChannelButton : public geChannelButton
 {
 public:
 
-       geSampleChannelButton(int x, int y, int w, int h, ID channelId);
+       geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
        
     int handle(int e) override;
 
index d779a4d3ded9567380fdf6ad57dbdf8a53004af9..fcd4a543538085780a8fa11fe96a8acf9c508e6e 100644 (file)
 
 
 #include "core/const.h"
-#include "core/model/model.h"
 #include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/pluginHost.h"
+#include "glue/events.h"
 #include "glue/main.h"
+#include "glue/channel.h"
 #include "utils/gui.h"
 #include "gui/elems/soundMeter.h"
 #include "gui/elems/basics/statusButton.h"
@@ -48,13 +46,8 @@ namespace giada {
 namespace v
 {
 geMainIO::geMainIO(int x, int y)
-: Fl_Pack(x, y, 396, G_GUI_UNIT)
+: gePack(x, y, Direction::HORIZONTAL)
 {
-       type(Fl_Pack::HORIZONTAL);
-       spacing(G_GUI_INNER_MARGIN);
-
-       begin();
-
 #if defined(WITH_VST)
 
        masterFxIn  = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm);
@@ -64,26 +57,32 @@ geMainIO::geMainIO(int x, int y)
        outMeter    = new geSoundMeter  (0, 0, 140, G_GUI_UNIT);
        outVol      = new geDial        (0, 0, G_GUI_UNIT, G_GUI_UNIT);
        masterFxOut = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm);
+       add(masterFxIn);
+       add(inVol);
+       add(inMeter);
+       add(inToOut);
+       add(outMeter);
+       add(outVol);
+       add(masterFxOut);
 
 #else
 
-       inVol       = new geDial      (0, 0, G_GUI_UNIT, G_GUI_UNIT);
-       inMeter     = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
-       outMeter    = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
-       outVol      = new geDial      (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+       inVol    = new geDial      (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+       inMeter  = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
+       outMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
+       outVol   = new geDial      (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+       add(inVol);    
+       add(inMeter);  
+       add(outMeter); 
+       add(outVol);   
 
 #endif
 
-       end();
-
        resizable(nullptr);   // don't resize any widget
 
        outVol->callback(cb_outVol, (void*)this);
        inVol->callback(cb_inVol, (void*)this);
 
-       outVol->value(m::mh::getOutVol());
-       inVol->value(m::mh::getInVol());
-
 #ifdef WITH_VST
 
        masterFxOut->callback(cb_masterFxOut, (void*)this);
@@ -112,7 +111,7 @@ void geMainIO::cb_inToOut    (Fl_Widget* v, void* p) { ((geMainIO*)p)->cb_inToOu
 
 void geMainIO::cb_outVol()
 {
-       c::main::setOutVol(outVol->value());
+       c::events::setMasterOutVolume(outVol->value(), Thread::MAIN);
 }
 
 
@@ -121,7 +120,7 @@ void geMainIO::cb_outVol()
 
 void geMainIO::cb_inVol()
 {
-       c::main::setInVol(inVol->value());
+       c::events::setMasterInVolume(inVol->value(), Thread::MAIN);
 }
 
 
@@ -144,7 +143,7 @@ void geMainIO::cb_masterFxIn()
 
 void geMainIO::cb_inToOut()
 {
-       m::mh::setInToOut(inToOut->value());
+       c::main::setInToOut(inToOut->value());
 }
 
 #endif
@@ -155,13 +154,13 @@ void geMainIO::cb_inToOut()
 
 void geMainIO::setOutVol(float v)
 {
-  outVol->value(v);
+       outVol->value(v);
 }
 
 
 void geMainIO::setInVol(float v)
 {
-  inVol->value(v);
+       inVol->value(v);
 }
 
 
@@ -172,13 +171,13 @@ void geMainIO::setInVol(float v)
 
 void geMainIO::setMasterFxOutFull(bool v)
 {
-  masterFxOut->setStatus(v);
+       masterFxOut->setStatus(v);
 }
 
 
 void geMainIO::setMasterFxInFull(bool v)
 {
-  masterFxIn->setStatus(v);
+       masterFxIn->setStatus(v);
 }
 
 #endif
@@ -189,8 +188,8 @@ void geMainIO::setMasterFxInFull(bool v)
 
 void geMainIO::refresh()
 {
-       outMeter->mixerPeak = m::mixer::peakOut.load();
-       inMeter->mixerPeak  = m::mixer::peakIn.load();
+       outMeter->mixerPeak = m_io.a_getMasterOutPeak(); 
+       inMeter->mixerPeak  = m_io.a_getMasterInPeak();
        outMeter->redraw();
        inMeter->redraw();
 }
@@ -201,20 +200,14 @@ void geMainIO::refresh()
 
 void geMainIO::rebuild()
 {
-       m::model::onGet(m::model::channels, m::mixer::MASTER_OUT_CHANNEL_ID, [&](m::Channel& c)
-       {
-               outVol->value(c.volume);
-#ifdef WITH_VST
-               masterFxOut->setStatus(c.pluginIds.size() > 0);
-#endif
-       });
+       m_io = c::main::getIO();
 
-       m::model::onGet(m::model::channels, m::mixer::MASTER_IN_CHANNEL_ID, [&](m::Channel& c)
-       {
-               inVol->value(c.volume);
+       outVol->value(m_io.masterOutVol);
+       inVol->value(m_io.masterInVol);
 #ifdef WITH_VST
-               masterFxIn->setStatus(c.pluginIds.size() > 0);
+       masterFxOut->setStatus(m_io.masterOutHasPlugins);
+       masterFxIn->setStatus(m_io.masterInHasPlugins);
+       inToOut->value(m_io.inToOut);
 #endif
-       });
 }
 }} // giada::v::
index 479c86a231e96ae3e883c8b60dae1581f7f94bcc..1f9a850ff1271218629f21a1e8500aa2bd1d205c 100644 (file)
@@ -29,7 +29,8 @@
 #define GE_MAIN_IO_H
 
 
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
+#include "glue/main.h"
 
 
 class geSoundMeter;
@@ -43,7 +44,7 @@ class geButton;
 namespace giada {
 namespace v
 {
-class geMainIO : public Fl_Pack
+class geMainIO : public gePack
 {
 public:
 
@@ -76,6 +77,8 @@ private:
        void cb_inToOut();
 #endif
 
+       c::main::IO m_io;
+
        geSoundMeter* outMeter;
        geSoundMeter* inMeter;
        geDial*       outVol;
index 0f84ba0baee8c051012787bf6eb757f8cc87cc5b..99d74be24e7225c1c01103dd8d3fffc2860ffe50 100644 (file)
@@ -27,8 +27,6 @@
 
 #include <cassert>
 #include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
-#include "core/channels/channel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/mixer.h"
@@ -58,19 +56,16 @@ namespace giada {
 namespace v
 {
 geMainMenu::geMainMenu(int x, int y)
-: Fl_Pack(x, y, 300, 20)
+: gePack(x, y, Direction::HORIZONTAL)
 {
-       type(Fl_Pack::HORIZONTAL);
-       spacing(G_GUI_INNER_MARGIN);
-
-       begin();
-
        file   = new geButton(0, 0, 70, 21, "file");
        edit   = new geButton(0, 0, 70, 21, "edit");
        config = new geButton(0, 0, 70, 21, "config");
        about  = new geButton(0, 0, 70, 21, "about");
-
-       end();
+       add(file);
+       add(edit);
+       add(config);
+       add(about);
 
        resizable(nullptr);   // don't resize any widget
 
@@ -135,7 +130,7 @@ void geMainMenu::cb_file()
        }
        else
        if (strcmp(m->label(), "Close project") == 0) {
-               c::main::resetToInitState(/*createColumns=*/true);
+               c::main::closeProject(/*createColumns=*/true);
        }
 #ifndef NDEBUG
        else
@@ -156,7 +151,7 @@ void geMainMenu::cb_file()
 void geMainMenu::cb_edit()
 {
        Fl_Menu_Item menu[] = {
-               {"Clear all samples"},
+               {"Free all Sample channels"},
                {"Clear all actions"},
                {"Setup global MIDI input..."},
                {0}
@@ -177,7 +172,7 @@ void geMainMenu::cb_edit()
        const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
        if (!m) return;
 
-       if (strcmp(m->label(), "Clear all samples") == 0) 
+       if (strcmp(m->label(), "Free all Sample channels") == 0) 
                c::main::clearAllSamples();
        else
        if (strcmp(m->label(), "Clear all actions") == 0) 
index 39e436b5e9ed4924d15f717d676a6f88175c5c38..9d114e2597958893170e452622ab773825d93e7a 100644 (file)
@@ -29,7 +29,7 @@
 #define GE_MAIN_MENU_H
 
 
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
 
 
 class geButton;
@@ -38,7 +38,7 @@ class geButton;
 namespace giada {
 namespace v
 {
-class geMainMenu : public Fl_Pack
+class geMainMenu : public gePack
 {
 public:
 
index 0479c940daa8308d3b96b277598ec3bb0f1b3b01..eb705edbaa438717cc77e1ee458ab54c534ba525 100644 (file)
 
 
 #include "core/const.h"
-#include "core/model/model.h"
-#include "core/kernelAudio.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
 #include "core/graphics.h"
 #include "core/clock.h"
 #include "glue/main.h"
+#include "glue/events.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include "gui/elems/basics/button.h"
@@ -50,27 +47,24 @@ namespace giada {
 namespace v
 {
 geMainTimer::geMainTimer(int x, int y)
-       : Fl_Group(x, y, 210, 20)
+: geGroup(x, y)
 {
-       begin();
-
-       quantizer  = new geChoice(x, y, 50, 20, "", false);
-       bpm        = new geButton(quantizer->x()+quantizer->w()+4,  y, 50, 20);
-       meter      = new geButton(bpm->x()+bpm->w()+8,  y, 50, 20);
-       multiplier = new geButton(meter->x()+meter->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm);
-       divider    = new geButton(multiplier->x()+multiplier->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_xpm);
-
-       end();
+       quantizer  = new geChoice(0, 0, 50, 20, "", false);
+       bpm        = new geButton(quantizer->x() + quantizer->w() + G_GUI_INNER_MARGIN, 0, 50, G_GUI_UNIT);
+       meter      = new geButton(bpm->x() + bpm->w() + G_GUI_OUTER_MARGIN, 0, 50, G_GUI_UNIT);
+       multiplier = new geButton(meter->x() + meter->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", multiplyOff_xpm, multiplyOn_xpm);
+       divider    = new geButton(multiplier->x() + multiplier->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", divideOff_xpm, divideOn_xpm);
+       add(quantizer);
+       add(bpm);
+       add(meter);
+       add(multiplier);
+       add(divider);
 
        resizable(nullptr);   // don't resize any widget
 
-       bpm->copy_label(u::string::fToString(m::clock::getBpm(), 1).c_str());
        bpm->callback(cb_bpm, (void*)this);
-
        meter->callback(cb_meter, (void*)this);
-       
-       multiplier->callback(cb_multiplier, (void*)this);
-       
+       multiplier->callback(cb_multiplier, (void*)this);       
        divider->callback(cb_divider, (void*)this);
 
        quantizer->add("off", 0, cb_quantizer, (void*)this);
@@ -81,15 +75,6 @@ geMainTimer::geMainTimer(int x, int y)
        quantizer->add("1\\/6", 0, cb_quantizer, (void*)this);
        quantizer->add("1\\/8", 0, cb_quantizer, (void*)this);
        quantizer->value(0); //  "off" by default
-
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-       
-       /* Can't change bpm from within Giada when using JACK. */
-
-       if (m::kernelAudio::getAPI() == G_SYS_API_JACK)
-               bpm->deactivate();
-
-#endif
 }
 
 
@@ -135,7 +120,7 @@ void geMainTimer::cb_quantizer()
 
 void geMainTimer::cb_multiplier()
 {
-       c::main::beatsMultiply();
+       c::events::multiplyBeats();
 }
 
 
@@ -144,7 +129,7 @@ void geMainTimer::cb_multiplier()
 
 void geMainTimer::cb_divider()
 {
-       c::main::beatsDivide();
+       c::events::divideBeats();
 }
 
 
@@ -153,7 +138,9 @@ void geMainTimer::cb_divider()
 
 void geMainTimer::refresh()
 {
-       if (m::recManager::isRecordingInput()) {
+       m_timer = c::main::getTimer();
+
+       if (m_timer.isRecordingInput) {
                bpm->deactivate();
                meter->deactivate();
                multiplier->deactivate();
@@ -163,8 +150,8 @@ void geMainTimer::refresh()
                /* Don't reactivate bpm when using JACK. It must stay disabled. */
 
 #if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-               if (m::kernelAudio::getAPI() != G_SYS_API_JACK)
-                       bpm->activate();
+               if (m_timer.isUsingJack)
+                       bpm->deactivate();
 #else
                bpm->activate();
 #endif
@@ -180,12 +167,20 @@ void geMainTimer::refresh()
 
 void geMainTimer::rebuild()
 {
-       m::model::onGet(m::model::clock, [&](m::model::Clock& c)
-       {
-               setBpm(c.bpm);
-               setMeter(c.beats, c.bars);
-               setQuantizer(c.quantize);
-       });
+       m_timer = c::main::getTimer();
+
+       setBpm(m_timer.bpm);
+       setMeter(m_timer.beats, m_timer.bars);
+       setQuantizer(m_timer.quantize);
+
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+       
+       /* Can't change bpm from within Giada when using JACK. */
+
+       if (m_timer.isUsingJack)
+               bpm->deactivate();
+
+#endif
 }
 
 
index cd4b25861aee35d8b58276c15510c5a73c187524..676b1fcd7d1ace104ec1fd2b77842d0b1da54d8b 100644 (file)
@@ -29,7 +29,8 @@
 #define GE_MAIN_TIMER_H
 
 
-#include <FL/Fl_Group.H>
+#include "glue/main.h"
+#include "gui/elems/basics/group.h"
 
 
 class geButton;
@@ -39,7 +40,7 @@ class geChoice;
 namespace giada {
 namespace v
 {
-class geMainTimer : public Fl_Group
+class geMainTimer : public geGroup
 {
 public:
 
@@ -54,7 +55,7 @@ public:
        void setQuantizer(int q);
 
        /* setLock
-       Locks bpm, beter and multipliers. Used during audio recordings. */
+       Locks bpm, meter and multipliers. Used during audio recordings. */
 
        void setLock(bool v);
 
@@ -71,6 +72,8 @@ private:
        void cb_multiplier();
        void cb_divider();
 
+       c::main::Timer m_timer;
+
        geButton* bpm;
        geButton* meter;
        geChoice* quantizer;
index 6e91586b7b49c34fd687c8615fcb51431e864242..cf7c6803264dffb1927a31549dd5f8068ac6a766 100644 (file)
 #include "core/graphics.h"
 #include "core/conf.h"
 #include "core/clock.h"
+#include "core/sequencer.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/recManager.h"
 #include "core/conf.h"
 #include "core/const.h"
 #include "glue/main.h"
+#include "glue/events.h"
 #include "gui/elems/basics/button.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/statusButton.h"
@@ -44,34 +46,39 @@ namespace giada {
 namespace v
 {
 geMainTransport::geMainTransport(int x, int y)
-: Fl_Pack(x, y, 200, 25)
+: gePack(x, y, Direction::HORIZONTAL)
 {
-       type(Fl_Pack::HORIZONTAL);
-       spacing(G_GUI_INNER_MARGIN);
-
        rewind         = new geButton      (0, 0, 25, 25, "", rewindOff_xpm, rewindOn_xpm);
        play           = new geStatusButton(0, 0, 25, 25, play_xpm, pause_xpm);
-                        new geBox         (0, 0, 10, 25);
+       spacer1        = new geBox         (0, 0, 10, 25);
        recTriggerMode = new geButton      (0, 0, 15, 25, "", recTriggerModeOff_xpm, recTriggerModeOn_xpm);
        recAction      = new geStatusButton(0, 0, 25, 25, recOff_xpm, recOn_xpm);
        recInput       = new geStatusButton(0, 0, 25, 25, inputRecOff_xpm, inputRecOn_xpm);
-                        new geBox         (0, 0, 10, 25);
+       spacer2        = new geBox         (0, 0, 10, 25);
        metronome      = new geStatusButton(0, 0, 15, 25, metronomeOff_xpm, metronomeOn_xpm);
-
+       add(rewind);
+       add(play);
+       add(spacer1);
+       add(recTriggerMode);
+       add(recAction);
+       add(recInput);
+       add(spacer2);
+       add(metronome);
+       
        rewind->callback([](Fl_Widget* w, void* v) { 
-               m::mh::rewindSequencer();
+               c::events::rewindSequencer(Thread::MAIN);
        });
 
        play->callback([](Fl_Widget* w, void* v) { 
-               m::mh::toggleSequencer();
+               c::events::toggleSequencer(Thread::MAIN);
        });
 
        recAction->callback([](Fl_Widget* w, void* v) { 
-               c::main::toggleActionRec();
+               c::events::toggleActionRecording();
        });
 
        recInput->callback([](Fl_Widget* w, void* v) { 
-               c::main::toggleInputRec();
+               c::events::toggleInputRecording();
        });
 
        recTriggerMode->value(static_cast<int>(m::conf::conf.recTriggerMode));
@@ -82,7 +89,7 @@ geMainTransport::geMainTransport(int x, int y)
 
        metronome->type(FL_TOGGLE_BUTTON);
        metronome->callback([](Fl_Widget* w, void* v) {
-               m::mixer::toggleMetronome();
+               c::events::toggleMetronome();
        });
 }
 
@@ -95,7 +102,6 @@ void geMainTransport::refresh()
        play->setStatus(m::clock::isRunning());
        recAction->setStatus(m::recManager::isRecordingAction());
        recInput->setStatus(m::recManager::isRecordingInput());
-       metronome->setStatus(m::mixer::isMetronomeOn());
+       metronome->setStatus(m::sequencer::isMetronomeOn());
 }
-
 }} // giada::v::
index dcf7a74e42fba378acfdf08c087d21f6842dff6b..1338e3923fd113c017850668b59f1c7f63628bc4 100644 (file)
 #define GE_MAIN_TRANSPORT_H
 
 
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
 
 
 class geButton;
+class geBox;
 class geStatusButton;
 
 
 namespace giada {
 namespace v
 {
-class geMainTransport : public Fl_Pack
+class geMainTransport : public gePack
 {
 public:
 
@@ -51,11 +52,11 @@ private:
 
        geButton* rewind;
        geStatusButton* play;
-       
+       geBox* spacer1; 
        geButton* recTriggerMode;
        geStatusButton* recAction;
        geStatusButton* recInput;
-
+       geBox* spacer2;
        geStatusButton* metronome;
 };
 }} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearner.cpp b/src/gui/elems/midiIO/midiLearner.cpp
new file mode 100644 (file)
index 0000000..a969606
--- /dev/null
@@ -0,0 +1,133 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "utils/string.h"
+#include "gui/elems/basics/boxtypes.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/button.h"
+#include "midiLearner.h"
+
+
+namespace giada {
+namespace v 
+{
+geMidiLearner::geMidiLearner(int X, int Y, int W, std::string l, int param)
+: Fl_Group    (X, Y, W, 20)
+, onStartLearn(nullptr)
+, onStopLearn (nullptr)
+, onClearLearn(nullptr)
+, m_param     (param)
+{
+       begin();
+       m_text     = new geBox(x(), y(), 156, 20, l.c_str());
+       m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20);
+       m_button   = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn");
+       end();
+
+       m_text->box(G_CUSTOM_BORDER_BOX);
+       m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
+
+       m_valueBtn->box(G_CUSTOM_BORDER_BOX);
+       m_valueBtn->callback(cb_value, (void*)this);
+       m_valueBtn->when(FL_WHEN_RELEASE);
+
+       m_button->type(FL_TOGGLE_BUTTON);
+       m_button->callback(cb_button, (void*)this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::cb_button(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->onLearn(); }
+void geMidiLearner::cb_value(Fl_Widget* v, void* p)  { ((geMidiLearner*)p)->onReset(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::update(uint32_t value)
+{
+       std::string tmp = "(not set)";
+       
+       if (value != 0x0) {
+               tmp = "0x" + u::string::iToString(value, /*hex=*/true);
+               tmp.pop_back();  // Remove last two digits, useless in MIDI messages
+               tmp.pop_back();  // Remove last two digits, useless in MIDI messages
+       }
+
+       m_valueBtn->copy_label(tmp.c_str());
+       m_button->value(0);     
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::activate()
+{
+       Fl_Group::activate();
+       m_valueBtn->activate();
+       m_button->activate();
+}
+
+
+void geMidiLearner::deactivate()
+{
+       Fl_Group::deactivate();
+       m_valueBtn->deactivate();
+       m_button->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::onLearn() const
+{
+       assert(onStartLearn != nullptr);
+       assert(onStopLearn != nullptr);
+
+       if (m_button->value() == 1)
+               onStartLearn(m_param);
+       else
+               onStopLearn();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::onReset() const
+{
+       assert(onClearLearn != nullptr);
+
+       if (Fl::event_button() == FL_RIGHT_MOUSE)
+               onClearLearn(m_param);  
+}
+}} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearner.h b/src/gui/elems/midiIO/midiLearner.h
new file mode 100644 (file)
index 0000000..e03ceb2
--- /dev/null
@@ -0,0 +1,85 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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 GE_MIDI_LEARNER_H
+#define GE_MIDI_LEARNER_H
+
+
+#include <functional>
+#include <string>
+#include <FL/Fl_Group.H>
+
+
+class geBox;
+class geButton;
+
+
+namespace giada {
+namespace v 
+{
+class geMidiLearner : public Fl_Group
+{
+public:
+
+       geMidiLearner(int x, int y, int w, std::string l, int param);
+
+       /* update
+       Updates and repaints the label widget with value 'value'. */
+
+       void update(uint32_t value);
+
+       void activate();
+       void deactivate();
+
+       std::function<void(uint32_t)> onStartLearn;
+       std::function<void()>         onStopLearn;
+       std::function<void(uint32_t)> onClearLearn;
+
+protected:
+
+       /* m_param
+       Parameter index to be learnt. */
+
+       int m_param;
+
+       geBox*    m_text;
+       geButton* m_valueBtn;
+       geButton* m_button;
+
+private:
+
+       static void cb_button(Fl_Widget* v, void* p);
+       static void cb_value (Fl_Widget* v, void* p);
+
+       void onLearn() const;
+       void onReset() const;
+
+};
+}} // giada::v::
+
+
+#endif
diff --git a/src/gui/elems/midiIO/midiLearnerBase.cpp b/src/gui/elems/midiIO/midiLearnerBase.cpp
deleted file mode 100644 (file)
index 1da5e87..0000000
+++ /dev/null
@@ -1,105 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <string>
-#include "utils/string.h"
-#include "gui/elems/basics/boxtypes.h"
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v 
-{
-geMidiLearnerBase::geMidiLearnerBase(int X, int Y, int W, std::string l, int param, uint32_t value)
-: Fl_Group   (X, Y, W, 20),
-  m_param    (param)
-{
-       begin();
-       m_text     = new geBox(x(), y(), 156, 20, l.c_str());
-       m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20);
-       m_button   = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn");
-       end();
-
-       m_text->box(G_CUSTOM_BORDER_BOX);
-       m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
-
-       m_valueBtn->box(G_CUSTOM_BORDER_BOX);
-       m_valueBtn->callback(cb_value, (void*)this);
-       m_valueBtn->when(FL_WHEN_RELEASE);
-
-       m_button->type(FL_TOGGLE_BUTTON);
-       m_button->callback(cb_button, (void*)this);
-       
-       update(value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::cb_button(Fl_Widget* v, void* p) { ((geMidiLearnerBase*)p)->onLearn(); }
-void geMidiLearnerBase::cb_value(Fl_Widget* v, void* p)  { ((geMidiLearnerBase*)p)->onReset(); }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::update(uint32_t value)
-{
-       std::string tmp = "(not set)";
-       
-       if (value != 0x0) {
-               tmp = "0x" + u::string::iToString(value, /*hex=*/true);
-               tmp.pop_back();  // Remove last two digits, useless in MIDI messages
-               tmp.pop_back();  // Remove last two digits, useless in MIDI messages
-       }
-
-       m_valueBtn->copy_label(tmp.c_str());
-       m_button->value(0);     
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::activate()
-{
-       Fl_Group::activate();
-       m_valueBtn->activate();
-       m_button->activate();
-}
-
-
-void geMidiLearnerBase::deactivate()
-{
-       Fl_Group::deactivate();
-       m_valueBtn->deactivate();
-       m_button->deactivate();
-}
-}} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearnerBase.h b/src/gui/elems/midiIO/midiLearnerBase.h
deleted file mode 100644 (file)
index e0f3e73..0000000
+++ /dev/null
@@ -1,82 +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 GE_MIDI_LEARNER_BASE_H
-#define GE_MIDI_LEARNER_BASE_H
-
-
-#include <string>
-#include <FL/Fl_Group.H>
-
-
-class geBox;
-class geButton;
-
-
-namespace giada {
-namespace v 
-{
-class geMidiLearnerBase : public Fl_Group
-{
-public:
-
-       virtual ~geMidiLearnerBase() = default;
-
-       virtual void refresh() = 0;
-       virtual void onLearn() = 0;
-       virtual void onReset() = 0;
-
-       void activate();
-       void deactivate();
-
-protected:
-
-       geMidiLearnerBase(int x, int y, int w, std::string l, int param, uint32_t value);
-
-       /* update
-       Updates and repaints the label widget with value 'value'. */
-
-       void update(uint32_t value);
-
-       /* m_param
-       Parameter index to be learnt. */
-
-       int m_param;
-
-       geBox*    m_text;
-       geButton* m_valueBtn;
-       geButton* m_button;
-
-private:
-
-       static void cb_button(Fl_Widget* v, void* p);
-       static void cb_value (Fl_Widget* v, void* p);
-};
-}} // giada::v::
-
-
-#endif
diff --git a/src/gui/elems/midiIO/midiLearnerChannel.cpp b/src/gui/elems/midiIO/midiLearnerChannel.cpp
deleted file mode 100644 (file)
index a7dd485..0000000
+++ /dev/null
@@ -1,88 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerChannel.h"
-
-
-namespace giada {
-namespace v 
-{
-geMidiLearnerChannel::geMidiLearnerChannel(int x, int y, int w, std::string l, int param, uint32_t value, ID channelId)
-: geMidiLearnerBase(x, y, w, l, param, value),
-  m_channelId      (channelId)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerChannel::refresh()
-{
-       m::model::onGet(m::model::channels, m_channelId, [&](const m::Channel& c)
-       {
-               switch (m_param) {
-                       case G_MIDI_IN_KEYPRESS     : update(c.midiInKeyPress); break;
-                       case G_MIDI_IN_KEYREL       : update(c.midiInKeyRel); break;
-                       case G_MIDI_IN_KILL         : update(c.midiInKill); break;
-                       case G_MIDI_IN_ARM          : update(c.midiInArm); break;
-                       case G_MIDI_IN_MUTE         : update(c.midiInVolume); break;
-                       case G_MIDI_IN_SOLO         : update(c.midiInMute); break;
-                       case G_MIDI_IN_VOLUME       : update(c.midiInSolo); break;
-                       case G_MIDI_IN_PITCH        : update(static_cast<const m::SampleChannel&>(c).midiInPitch); break;
-                       case G_MIDI_IN_READ_ACTIONS : update(static_cast<const m::SampleChannel&>(c).midiInReadActions); break;
-                       case G_MIDI_OUT_L_PLAYING   : update(static_cast<const m::SampleChannel&>(c).midiOutLplaying); break;
-                       case G_MIDI_OUT_L_MUTE      : update(static_cast<const m::SampleChannel&>(c).midiOutLmute); break;
-                       case G_MIDI_OUT_L_SOLO      : update(static_cast<const m::SampleChannel&>(c).midiOutLsolo); break;
-               }
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerChannel::onLearn()
-{
-       if (m_button->value() == 1)
-               c::io::startChannelMidiLearn(m_param, m_channelId);
-       else
-               c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerChannel::onReset()
-{
-       if (Fl::event_button() == FL_RIGHT_MOUSE)
-               c::io::clearChannelMidiLearn(m_param, m_channelId);     
-}
-}} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearnerChannel.h b/src/gui/elems/midiIO/midiLearnerChannel.h
deleted file mode 100644 (file)
index d22177a..0000000
+++ /dev/null
@@ -1,55 +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 GE_MIDI_LEARNER_CHANNEL_H
-#define GE_MIDI_LEARNER_CHANNEL_H
-
-
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v 
-{
-class geMidiLearnerChannel : public geMidiLearnerBase
-{
-public:
-
-       geMidiLearnerChannel(int x, int y, int w, std::string l, int param, uint32_t value, ID channelId);
-       
-       void refresh() override;
-       void onLearn() override;
-       void onReset() override;
-
-private:
-
-       ID m_channelId;
-};
-}} // giada::v::
-
-
-#endif
diff --git a/src/gui/elems/midiIO/midiLearnerMaster.cpp b/src/gui/elems/midiIO/midiLearnerMaster.cpp
deleted file mode 100644 (file)
index cb4a86f..0000000
+++ /dev/null
@@ -1,83 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerMaster.h"
-
-
-namespace giada {
-namespace v 
-{
-geMidiLearnerMaster::geMidiLearnerMaster(int X, int Y, int W, std::string l, int param, uint32_t value)
-: geMidiLearnerBase(X, Y, W, l, param, value)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerMaster::refresh()
-{
-       m::model::onGet(m::model::midiIn, [&](const m::model::MidiIn& m)
-       {
-               switch (m_param) {
-                       case G_MIDI_IN_REWIND      : update(m.rewind); break;
-                       case G_MIDI_IN_START_STOP  : update(m.startStop); break;
-                       case G_MIDI_IN_ACTION_REC  : update(m.actionRec); break;
-                       case G_MIDI_IN_INPUT_REC   : update(m.inputRec); break;
-                       case G_MIDI_IN_METRONOME   : update(m.volumeIn); break;
-                       case G_MIDI_IN_VOLUME_IN   : update(m.volumeOut); break;
-                       case G_MIDI_IN_VOLUME_OUT  : update(m.beatDouble); break;
-                       case G_MIDI_IN_BEAT_DOUBLE : update(m.beatHalf); break;
-                       case G_MIDI_IN_BEAT_HALF   : update(m.metronome); break;
-               }
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerMaster::onLearn()
-{
-       if (m_button->value() == 1)
-               c::io::startMasterMidiLearn(m_param);
-       else
-               c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerMaster::onReset()
-{
-       if (Fl::event_button() == FL_RIGHT_MOUSE)
-               c::io::clearMasterMidiLearn(m_param);   
-}
-}} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearnerMaster.h b/src/gui/elems/midiIO/midiLearnerMaster.h
deleted file mode 100644 (file)
index 8bb2829..0000000
+++ /dev/null
@@ -1,51 +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 GE_MIDI_LEARNER_MASTER_H
-#define GE_MIDI_LEARNER_MASTER_H
-
-
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v 
-{
-class geMidiLearnerMaster : public geMidiLearnerBase
-{
-public:
-
-       geMidiLearnerMaster(int x, int y, int w, std::string l, int param, uint32_t value);
-       
-       void refresh() override;
-       void onLearn() override;
-       void onReset() override;
-};
-}} // giada::v::
-
-
-#endif
diff --git a/src/gui/elems/midiIO/midiLearnerPack.cpp b/src/gui/elems/midiIO/midiLearnerPack.cpp
new file mode 100644 (file)
index 0000000..05ebc80
--- /dev/null
@@ -0,0 +1,92 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/const.h"
+#include "glue/io.h"
+#include "gui/elems/basics/box.h"
+#include "midiLearnerPack.h"
+
+
+namespace giada {
+namespace v 
+{
+constexpr int LEARNER_WIDTH = 284;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geMidiLearnerPack::geMidiLearnerPack(int X, int Y, std::string title)
+: gePack(X, Y, Direction::VERTICAL)
+{
+       end();
+
+       if (title != "") {
+               geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, title.c_str());
+               header->box(FL_BORDER_BOX);
+               add(header);
+       }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::setCallbacks(std::function<void(uint32_t)> s, std::function<void(uint32_t)> c)
+{
+    m_onStartLearn = s;
+    m_onClearLearn = c;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::addMidiLearner(std::string label, int param, bool visible)
+{
+       geMidiLearner* l = new geMidiLearner(0, 0, LEARNER_WIDTH, label, param);
+       
+       l->onStartLearn = m_onStartLearn;
+       l->onClearLearn = m_onClearLearn;
+       l->onStopLearn = [] () { c::io::stopMidiLearn(); };
+
+       add(l);
+       if (!visible) l->hide();
+       learners.push_back(l);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::setEnabled(bool v)
+{
+       if (v) for (auto* l : learners) l->activate();
+       else   for (auto* l : learners) l->deactivate();
+}
+}} // giada::v::
diff --git a/src/gui/elems/midiIO/midiLearnerPack.h b/src/gui/elems/midiIO/midiLearnerPack.h
new file mode 100644 (file)
index 0000000..228a06c
--- /dev/null
@@ -0,0 +1,61 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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 GE_LEARNER_PACK_H
+#define GE_LEARNER_PACK_H
+
+
+#include <string>
+#include <vector>
+#include "gui/elems/basics/pack.h"
+#include "gui/elems/midiIO/midiLearner.h"
+
+
+namespace giada {
+namespace v
+{
+class geMidiLearnerPack : public gePack
+{
+public:
+
+       geMidiLearnerPack(int x, int y, std::string title="");
+
+    void setCallbacks(std::function<void(uint32_t)>, std::function<void(uint32_t)>);
+    void addMidiLearner(std::string label, int param, bool visible=true); 
+    void setEnabled(bool v);
+
+    std::vector<geMidiLearner*> learners;
+
+private:
+
+    std::function<void(uint32_t)> m_onStartLearn;
+    std::function<void(uint32_t)> m_onClearLearn;
+};
+}} // giada::v::
+
+
+#endif
diff --git a/src/gui/elems/midiIO/midiLearnerPlugin.cpp b/src/gui/elems/midiIO/midiLearnerPlugin.cpp
deleted file mode 100644 (file)
index a44b153..0000000
+++ /dev/null
@@ -1,81 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifdef WITH_VST
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerPlugin.h"
-
-
-namespace giada {
-namespace v 
-{
-geMidiLearnerPlugin::geMidiLearnerPlugin(int x, int y, int w, std::string l, int param, uint32_t value, ID pluginId)
-: geMidiLearnerBase(x, y, w, l, param, value),
-  m_pluginId       (pluginId)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerPlugin::refresh()
-{
-       m::model::onGet(m::model::plugins, m_pluginId, [&](const m::Plugin& p)
-       {
-               assert(static_cast<size_t>(m_param) < p.midiInParams.size());
-               update(p.midiInParams[m_param]);
-       });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerPlugin::onLearn()
-{
-       if (m_button->value() == 1)
-               c::io::startPluginMidiLearn(m_param, m_pluginId);
-       else
-               c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerPlugin::onReset()
-{
-       if (Fl::event_button() == FL_RIGHT_MOUSE)
-               c::io::clearPluginMidiLearn(m_param, m_pluginId);       
-}
-}} // giada::v::
-
-
-#endif
\ No newline at end of file
diff --git a/src/gui/elems/midiIO/midiLearnerPlugin.h b/src/gui/elems/midiIO/midiLearnerPlugin.h
deleted file mode 100644 (file)
index 482cbaf..0000000
+++ /dev/null
@@ -1,60 +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/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifdef WITH_VST
-
-
-#ifndef GE_MIDI_LEARNER_PLUGIN_H
-#define GE_MIDI_LEARNER_PLUGIN_H
-
-
-#include "core/types.h"
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v 
-{
-class geMidiLearnerPlugin : public geMidiLearnerBase
-{
-public:
-
-       geMidiLearnerPlugin(int x, int y, int w, std::string l, int param, uint32_t value, ID pluginId);
-       
-       void refresh() override;
-       void onLearn() override;
-       void onReset() override;
-
-private:
-
-       ID m_pluginId;
-};
-}} // giada::v::
-
-
-#endif
-#endif
index 79489e33b870cad92b0c7ffc9889f2e739d4a0ae..0d861a8adc3c5a969eb3968a5ebecb13faf32f8c 100644 (file)
@@ -30,8 +30,6 @@
 
 #include <cassert>
 #include <string>
-#include "core/channels/channel.h"
-#include "core/model/model.h"
 #include "core/graphics.h"
 #include "core/pluginHost.h"
 #include "core/plugin.h"
 namespace giada {
 namespace v
 {
-gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W)
-: Fl_Pack    (X, Y, W, G_GUI_UNIT), 
-  m_channelId(channelId),
-  m_pluginId (pluginId)
+gePluginElement::gePluginElement(int X, int Y, int W, c::plugin::Plugin data)
+: Fl_Pack(X, Y, W, G_GUI_UNIT) 
+, m_plugin (data)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -66,38 +63,34 @@ gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W)
                remove    = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", fxRemoveOff_xpm, fxRemoveOn_xpm);
        end();
 
-       m::model::PluginsLock l(m::model::plugins);
-
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
        remove->callback(cb_removePlugin, (void*)this);
 
-       if (!p.valid) {
-               button->copy_label(p.getUniqueId().c_str());
+       if (!m_plugin.valid) {
+               button->copy_label(m_plugin.uniqueId.c_str());
                button->deactivate();
                bypass->deactivate();
                shiftUp->deactivate();
                shiftDown->deactivate();
                return;
        }
-       button->copy_label(p.getName().c_str());
+       button->copy_label(m_plugin.name.c_str());
        button->callback(cb_openPluginWindow, (void*)this);
 
        program->callback(cb_setProgram, (void*)this);
 
-       for (int i=0; i<p.getNumPrograms(); i++)
-               program->add(u::gui::removeFltkChars(p.getProgramName(i)).c_str());
+       for (const auto& p : m_plugin.programs)
+               program->add(u::gui::removeFltkChars(p.name).c_str());
 
        if (program->size() == 0) {
                program->add("-- no programs --\0");
                program->deactivate();
        }
        else
-               program->value(p.getCurrentProgram());
+               program->value(m_plugin.currentProgram);
 
        bypass->callback(cb_setBypass, (void*)this);
        bypass->type(FL_TOGGLE_BUTTON);
-       bypass->value(p.isBypassed() ? 0 : 1);
+       bypass->value(m_plugin.isBypassed ? 0 : 1);
 
        shiftUp->callback(cb_shiftUp, (void*)this);
        shiftDown->callback(cb_shiftDown, (void*)this);
@@ -109,7 +102,7 @@ gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W)
 
 ID gePluginElement::getPluginId() const
 {
-       return m_pluginId;
+       return m_plugin.id;
 }
 
 
@@ -131,7 +124,7 @@ void gePluginElement::cb_shiftUp()
 {
        const gdPluginList* parent = static_cast<const gdPluginList*>(window());
 
-       c::plugin::swapPlugins(m_pluginId, parent->getPrevElement(*this).getPluginId(), m_channelId);
+       c::plugin::swapPlugins(m_plugin.id, parent->getPrevElement(*this).getPluginId(), m_plugin.channelId);
 }
 
 
@@ -142,7 +135,7 @@ void gePluginElement::cb_shiftDown()
 {
        const gdPluginList* parent = static_cast<const gdPluginList*>(window());
 
-       c::plugin::swapPlugins(m_pluginId, parent->getNextElement(*this).getPluginId(), m_channelId);
+       c::plugin::swapPlugins(m_plugin.id, parent->getNextElement(*this).getPluginId(), m_plugin.channelId);
 }
 
 
@@ -155,8 +148,8 @@ void gePluginElement::cb_removePlugin()
        pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent 
        window 'add plugin'.*/
        
-       static_cast<gdWindow*>(window())->delSubWindow(m_pluginId + 1);
-       c::plugin::freePlugin(m_pluginId, m_channelId);
+       static_cast<gdWindow*>(window())->delSubWindow(m_plugin.id + 1);
+       c::plugin::freePlugin(m_plugin.id, m_plugin.channelId);
 }
 
 
@@ -165,14 +158,10 @@ void gePluginElement::cb_removePlugin()
 
 void gePluginElement::cb_openPluginWindow()
 {
-       m::model::PluginsLock l(m::model::plugins);
-
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
        /* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for 
        the parent window 'add plugin'. */
 
-       int pwid = m_pluginId + 1;
+       int pwid = m_plugin.id + 1;
 
        gdWindow* parent = static_cast<gdWindow*>(window());
        gdWindow* child  = parent->getChild(pwid);
@@ -181,10 +170,10 @@ void gePluginElement::cb_openPluginWindow()
                child->show();  // Raise it to top
        }
        else {
-               if (p.hasEditor())
-                       child = new gdPluginWindowGUI(m_pluginId);
+               if (m_plugin.hasEditor)
+                       child = new gdPluginWindowGUI(m_plugin);
                else 
-                       child = new gdPluginWindow(m_pluginId);
+                       child = new gdPluginWindow(m_plugin);
                child->setId(pwid);
                parent->addSubWindow(child);
        }
@@ -196,7 +185,7 @@ void gePluginElement::cb_openPluginWindow()
 
 void gePluginElement::cb_setBypass()
 {
-       c::plugin::toggleBypass(m_pluginId);
+       c::plugin::toggleBypass(m_plugin.id);
 }
 
 
@@ -205,7 +194,7 @@ void gePluginElement::cb_setBypass()
 
 void gePluginElement::cb_setProgram()
 {
-       c::plugin::setProgram(m_pluginId, program->value());
+       c::plugin::setProgram(m_plugin.id, program->value());
 }
 }} // giada::v::
 
index b6ca89a467756c7c8a46343876db8a7bc69cc682..c9acb07d11df9490da5fb3124b4f8fda48b263a2 100644 (file)
@@ -33,6 +33,7 @@
 
 
 #include <FL/Fl_Pack.H>
+#include "glue/plugin.h"
 
 
 class geChoice;
@@ -47,7 +48,7 @@ class gePluginElement : public Fl_Pack
 {
 public:
 
-       gePluginElement(ID pluginId, ID channelId, int x, int y, int w);
+       gePluginElement(int x, int y, int w, c::plugin::Plugin);
 
        ID getPluginId() const;
 
@@ -73,8 +74,7 @@ private:
        void cb_shiftDown();
        void cb_setProgram();
 
-       ID m_channelId;
-       ID m_pluginId;
+       c::plugin::Plugin m_plugin;
 };
 }} // giada::v::
 
index c401a5966f3f8b4c7495291674bcf637517829b8..2eec8d8e7ae2d5fb4c91be259743654820aa3257 100644 (file)
 #ifdef WITH_VST
 
 
-#include "core/model/model.h"
-#include "core/plugin.h"
 #include "core/const.h"
 #include "glue/plugin.h"
+#include "glue/events.h"
 #include "gui/elems/basics/boxtypes.h"
 #include "gui/elems/basics/box.h"
 #include "gui/elems/basics/slider.h"
 namespace giada {
 namespace v
 {
-gePluginParameter::gePluginParameter(int paramIndex, ID pluginId, 
-       int X, int Y, int W, int labelWidth)
-: Fl_Group    (X, Y, W, G_GUI_UNIT), 
-  m_pluginId  (pluginId),
-  m_paramIndex(paramIndex)
+gePluginParameter::gePluginParameter(int X, int Y, int W, int labelWidth, const c::plugin::Param p)
+: Fl_Group  (X, Y, W, G_GUI_UNIT)
+, m_param   (p)
 {
-       m::model::PluginsLock l(m::model::plugins);
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
        begin();
 
                const int VALUE_WIDTH = 100;
 
                m_label = new geBox(x(), y(), labelWidth, G_GUI_UNIT);
-               m_label->copy_label(p.getParameterName(m_paramIndex).c_str());
+               m_label->copy_label(m_param.name.c_str());
                m_label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
 
                m_slider = new geSlider(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN, y(), 
                        w()-(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN)-VALUE_WIDTH, G_GUI_UNIT);
-               m_slider->value(p.getParameter(m_paramIndex));
+               m_slider->value(m_param.value);
                m_slider->callback(cb_setValue, (void*)this);
 
                m_value = new geBox(m_slider->x()+m_slider->w()+G_GUI_OUTER_MARGIN, y(), VALUE_WIDTH, G_GUI_UNIT);
                m_value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
                m_value->box(G_CUSTOM_BORDER_BOX);
-
+               
        end();
+
        resizable(m_slider);
-       update(false);
+       update(m_param, false);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void gePluginParameter::cb_setValue(Fl_Widget* v, void* p)  { ((gePluginParameter*)p)->cb_setValue(); }
+void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); }
 
 
 /* -------------------------------------------------------------------------- */
@@ -84,7 +79,7 @@ void gePluginParameter::cb_setValue(Fl_Widget* v, void* p)  { ((gePluginParamete
 
 void gePluginParameter::cb_setValue()
 {
-       c::plugin::setParameter(m_pluginId, m_paramIndex, m_slider->value(), 
+       c::events::setPluginParameter(m_param.pluginId, m_param.index, m_slider->value(), 
                /*gui=*/true);
 }
 
@@ -92,18 +87,11 @@ void gePluginParameter::cb_setValue()
 /* -------------------------------------------------------------------------- */
 
 
-void gePluginParameter::update(bool changeSlider)
+void gePluginParameter::update(const c::plugin::Param& p, bool changeSlider)
 {
-       m::model::PluginsLock l(m::model::plugins);
-       const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
-       std::string v = p.getParameterText(m_paramIndex) + " " +
-                       p.getParameterLabel(m_paramIndex);
-
-       m_value->copy_label(v.c_str());
-
+       m_value->copy_label(std::string(p.text + " " + p.label).c_str());
        if (changeSlider)
-               m_slider->value(p.getParameter(m_paramIndex));
+               m_slider->value(p.value);
 }
 }} // giada::v::
 
index e3b2aff7f389330601f81e6eb1bfbdadb6e24281..6c4d9d2e82c732a83562dc1adebc6d0c711b3fd3 100644 (file)
@@ -41,24 +41,27 @@ class geSlider;
 
 
 namespace giada {
+namespace c {
+namespace plugin
+{
+struct Param;
+}}
 namespace v
 {
 class gePluginParameter : public Fl_Group
 {
 public:
 
-       gePluginParameter(int paramIndex, ID pluginId, int x, int y, int w,
-               int labelWidth);
+       gePluginParameter(int x, int y, int w, int labelWidth, const c::plugin::Param);
 
-       void update(bool changeSlider);
+       void update(const c::plugin::Param& p, bool changeSlider);
 
 private:
 
        static void cb_setValue(Fl_Widget* v, void* p);
        void cb_setValue();
 
-       ID  m_pluginId;
-       int m_paramIndex;
+       const c::plugin::Param m_param; 
 
        geBox*    m_label;
        geSlider* m_slider;
index 9cedcb51d7e4f6317a9799ef10d45580eb6aaffe..bb2fb2b343082738f28a67a7bbba0237440382eb 100644 (file)
@@ -26,7 +26,6 @@
 
 
 #include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
 #include "core/const.h"
 #include "core/waveFx.h"  
 #include "glue/channel.h"
index 6fb693473c1e4499b050bc2131165578e5a29bcb..ca646d676652845a0fc10d026bab8c4cb068b8d1 100644 (file)
 
 
 #include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/waveFx.h"  
-#include "glue/channel.h"
+#include "glue/events.h"
 #include "utils/gui.h"
 #include "utils/math.h"
 #include "utils/string.h"
@@ -46,9 +45,9 @@
 namespace giada {
 namespace v 
 {
-gePanTool::gePanTool(ID channelId, int x, int y)
-: Fl_Pack    (x, y, 200, G_GUI_UNIT),
-  m_channelId(channelId)
+gePanTool::gePanTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 200, G_GUI_UNIT)
+, m_data (nullptr)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -60,7 +59,7 @@ gePanTool::gePanTool(ID channelId, int x, int y)
                reset = new geButton(0, 0, 70, G_GUI_UNIT, "Reset");
        end();
 
-       dial->range(0.0f, 1.0f);
+       dial->range(0.0f, G_MAX_PAN);
        dial->callback(cb_panning, (void*)this);
 
        input->align(FL_ALIGN_RIGHT);
@@ -68,31 +67,38 @@ gePanTool::gePanTool(ID channelId, int x, int y)
        input->cursor_color(FL_WHITE);
 
        reset->callback(cb_panReset, (void*)this);
+
+       rebuild(d);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void gePanTool::rebuild()
+void gePanTool::rebuild(const c::sampleEditor::Data& d)
 {
-       float p;
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               p = static_cast<m::SampleChannel&>(c).getPan();
-       });
+       m_data = &d;
+       update(m_data->pan);
+
+}
 
-       dial->value(p);
 
-       if (p < 0.5f) {
-               std::string tmp = u::string::iToString((int) ((-p * 200.0f) + 100.0f)) + " L";
+/* -------------------------------------------------------------------------- */
+
+
+void gePanTool::update(float v)
+{
+       dial->value(v);
+
+       if (v < 0.5f) {
+               std::string tmp = u::string::iToString((int) ((-v * 200.0f) + 100.0f)) + " L";
                input->value(tmp.c_str());
        }
        else 
-       if (p == 0.5)
+       if (v == 0.5)
                input->value("C");
        else {
-               std::string tmp = u::string::iToString((int) ((p * 200.0f) - 100.0f)) + " R";
+               std::string tmp = u::string::iToString((int) ((v * 200.0f) - 100.0f)) + " R";
                input->value(tmp.c_str());
        }
 }
@@ -111,7 +117,7 @@ void gePanTool::cb_panReset(Fl_Widget* w, void* p) { ((gePanTool*)p)->cb_panRese
 
 void gePanTool::cb_panning()
 {
-       c::channel::setPan(m_channelId, dial->value());
+       c::events::sendChannelPan(m_data->channelId, dial->value());
 }
 
 
@@ -120,7 +126,6 @@ void gePanTool::cb_panning()
 
 void gePanTool::cb_panReset()
 {
-       c::channel::setPan(m_channelId, 0.5f);
+       c::events::sendChannelPan(m_data->channelId, 0.5f);
 }
-
 }} // giada::v::
index 1adeca12e4e6428cabbacdc35f0c4507509ce605..a8a39f50db33028b4a6c72efea2656cb8e8cdccd 100644 (file)
@@ -45,9 +45,10 @@ class gePanTool : public Fl_Pack
 {
 public:
 
-  gePanTool(ID channelId, int x, int y);
+  gePanTool(const c::sampleEditor::Data& d, int x, int y);
 
-  void rebuild();
+  void rebuild(const c::sampleEditor::Data& d);
+  void update(float v);
 
 private:
 
@@ -56,7 +57,7 @@ private:
   void cb_panning();
   void cb_panReset();
 
-  ID m_channelId;
+       const c::sampleEditor::Data* m_data;
 
   geBox*    label;
   geDial*   dial;
index fb7e5cf1f42803fc68b9ffdb09d468f57af13f88..30052ebb96432844a17b2fa1c71fd5248cd7acf9 100644 (file)
 
 
 #include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "core/graphics.h"  
 #include "core/clock.h"
-#include "glue/channel.h"
+#include "glue/events.h"
 #include "utils/gui.h"
 #include "utils/string.h"
 #include "gui/dialogs/sampleEditor.h"
@@ -46,9 +45,9 @@
 namespace giada {
 namespace v 
 {
-gePitchTool::gePitchTool(ID channelId, int x, int y)
-: Fl_Pack    (x, y, 600, G_GUI_UNIT),
-  m_channelId(channelId)
+gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 600, G_GUI_UNIT)
+, m_data (nullptr)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -77,21 +76,29 @@ gePitchTool::gePitchTool(ID channelId, int x, int y)
        pitchHalf->callback(cb_setPitchHalf, (void*)this);
        pitchDouble->callback(cb_setPitchDouble, (void*)this);
        pitchReset->callback(cb_resetPitch, (void*)this);
+
+       rebuild(d);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void gePitchTool::rebuild()
+void gePitchTool::rebuild(const c::sampleEditor::Data& d)
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               float p = static_cast<m::SampleChannel&>(c).getPitch();
-               
-               dial->value(p);
-               input->value(u::string::fToString(p, 4).c_str()); // 4 digits
-       });
+       m_data = &d;
+       update(m_data->pitch, /*isDial=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::update(float v, bool isDial)
+{
+       input->value(u::string::fToString(v, 4).c_str()); // 4 digits
+       if (!isDial)
+               dial->value(v);
 }
 
 
@@ -112,7 +119,7 @@ void gePitchTool::cb_setPitchNum   (Fl_Widget* w, void* p) { ((gePitchTool*)p)->
 
 void gePitchTool::cb_setPitch()
 {
-       c::channel::setPitch(m_channelId, dial->value());
+       c::events::setChannelPitch(m_data->channelId, dial->value(), Thread::MAIN);
 }
 
 
@@ -121,7 +128,7 @@ void gePitchTool::cb_setPitch()
 
 void gePitchTool::cb_setPitchNum()
 {
-       c::channel::setPitch(m_channelId, atof(input->value()));
+       c::events::setChannelPitch(m_data->channelId, atof(input->value()), Thread::MAIN);      
 }
 
 
@@ -130,7 +137,7 @@ void gePitchTool::cb_setPitchNum()
 
 void gePitchTool::cb_setPitchHalf()
 {
-       c::channel::setPitch(m_channelId, dial->value()/2);
+       c::events::setChannelPitch(m_data->channelId, dial->value() / 2, Thread::MAIN); 
 }
 
 
@@ -139,7 +146,7 @@ void gePitchTool::cb_setPitchHalf()
 
 void gePitchTool::cb_setPitchDouble()
 {
-       c::channel::setPitch(m_channelId, dial->value()*2);
+       c::events::setChannelPitch(m_data->channelId, dial->value() * 2, Thread::MAIN); 
 }
 
 
@@ -148,13 +155,8 @@ void gePitchTool::cb_setPitchDouble()
 
 void gePitchTool::cb_setPitchToBar()
 {
-       Frame end;
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               end = static_cast<m::SampleChannel&>(c).getEnd();
-       });
-
-       c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInBar());
+       c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInBar(), 
+               Thread::MAIN);  
 }
 
 
@@ -163,13 +165,8 @@ void gePitchTool::cb_setPitchToBar()
 
 void gePitchTool::cb_setPitchToSong()
 {
-       Frame end;
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               end = static_cast<m::SampleChannel&>(c).getEnd();
-       });
-
-       c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInLoop());
+       c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInLoop(), 
+               Thread::MAIN);  
 }
 
 
@@ -178,7 +175,6 @@ void gePitchTool::cb_setPitchToSong()
 
 void gePitchTool::cb_resetPitch()
 {
-       c::channel::setPitch(m_channelId, G_DEFAULT_PITCH);
+       c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN);   
 }
-
 }} // giada::v::
index 10019c366c67199f25ec9fb33ebf2bb7e0b9ea8b..f520d2f401c0914507dd381f5ed6b976dc0eec22 100644 (file)
@@ -45,9 +45,10 @@ class gePitchTool : public Fl_Pack
 {
 public:
 
-       gePitchTool(ID channelId, int x, int y);
+       gePitchTool(const c::sampleEditor::Data& d, int x, int y);
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
+       void update(float v, bool isDial=false);
 
 private:
 
@@ -66,7 +67,7 @@ private:
        void cb_resetPitch();
        void cb_setPitchNum();
 
-       ID m_channelId;
+       const c::sampleEditor::Data* m_data;
 
        geBox*    label;
        geDial*   dial;
index 6dbd341db02714187256739291a3a625c0ed7921..9c800c440972b5a893c7fb64b50442fdf22899bf 100644 (file)
@@ -27,7 +27,6 @@
 
 #include <cassert>
 #include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/wave.h"
 #include "glue/channel.h"
 namespace giada {
 namespace v 
 {
-geRangeTool::geRangeTool(ID channelId, ID waveId, int x, int y)
-: Fl_Pack    (x, y, 280, G_GUI_UNIT),
-  m_channelId(channelId),
-  m_waveId   (waveId)
+geRangeTool::geRangeTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 280, G_GUI_UNIT)
+, m_data (nullptr)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -69,25 +67,33 @@ geRangeTool::geRangeTool(ID channelId, ID waveId, int x, int y)
        m_end->callback(cb_setChanPos, this);
 
        m_reset->callback(cb_resetStartEnd, this);
+
+       rebuild(d);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void geRangeTool::rebuild()
+void geRangeTool::rebuild(const c::sampleEditor::Data& d)
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               m_begin->value(std::to_string(static_cast<m::SampleChannel&>(c).getBegin()).c_str());
-               m_end->value(std::to_string(static_cast<m::SampleChannel&>(c).getEnd()).c_str());
-       });
+       m_data = &d;
+       update(m_data->begin, m_data->end);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
+void geRangeTool::update(Frame begin, Frame end)
+{
+       m_begin->value(std::to_string(begin).c_str());
+       m_end->value(std::to_string(end).c_str());
+}
+
+/* -------------------------------------------------------------------------- */
+
+
 void geRangeTool::cb_setChanPos   (Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_setChanPos(); }
 void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_resetStartEnd(); }
 
@@ -97,7 +103,7 @@ void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->c
 
 void geRangeTool::cb_setChanPos()
 {
-       c::sampleEditor::setBeginEnd(m_channelId, atoi(m_begin->value()), atoi(m_end->value()));
+       c::sampleEditor::setBeginEnd(m_data->channelId, atoi(m_begin->value()), atoi(m_end->value()));
 }
 
 
@@ -106,13 +112,7 @@ void geRangeTool::cb_setChanPos()
 
 void geRangeTool::cb_resetStartEnd()
 {
-       Frame waveSize;
-       m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w)
-       { 
-               waveSize = w.getSize();
-       });
-       
-       c::sampleEditor::setBeginEnd(m_channelId, 0, waveSize - 1);
+       c::sampleEditor::setBeginEnd(m_data->channelId, 0, m_data->waveSize - 1);
 }
 
 }} // giada::v::
index 02c2b2fb27784fcdef84f983f7ee41275e4b8967..6ec75fdb88b7dfe71c0119e896bbe6dcb86a7fc9 100644 (file)
@@ -44,9 +44,10 @@ class geRangeTool : public Fl_Pack
 {
 public:
 
-       geRangeTool(ID channelId, ID waveId, int x, int y);
+       geRangeTool(const c::sampleEditor::Data& d, int x, int y);
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
+       void update(Frame begin, Frame end);
 
 private:
 
@@ -55,8 +56,7 @@ private:
        void cb_setChanPos();
        void cb_resetStartEnd();
 
-       ID m_channelId;
-       ID m_waveId;
+       const c::sampleEditor::Data* m_data;
 
        geBox*    m_label;
        geInput*  m_begin;
index 6b7cffb5f7436424bc3b26ff45da9f5c308de70a..26343952c4f622b97a2531f6af114652138ffa7e 100644 (file)
@@ -27,7 +27,6 @@
 
 #include <cassert>
 #include <cstdlib>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/const.h"
 #include "utils/gui.h"
 namespace giada {
 namespace v 
 {
-geShiftTool::geShiftTool(ID channelId, ID waveId, int x, int y)
-: Fl_Pack    (x, y, 300, G_GUI_UNIT),
-  m_channelId(channelId),
-  m_waveId   (waveId)
+geShiftTool::geShiftTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 300, G_GUI_UNIT)
+, m_data (nullptr)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -63,6 +61,8 @@ geShiftTool::geShiftTool(ID channelId, ID waveId, int x, int y)
        m_shift->callback(cb_setShift, (void*)this);
 
        m_reset->callback(cb_reset, (void*)this);
+
+       rebuild(d);
 }
 
 
@@ -94,12 +94,19 @@ void geShiftTool::cb_reset()
 /* -------------------------------------------------------------------------- */
 
 
-void geShiftTool::rebuild()
+void geShiftTool::rebuild(const c::sampleEditor::Data& d)
+{
+       m_data = &d;
+       update(m_data->shift);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geShiftTool::update(Frame shift)
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               m_shift->value(std::to_string(static_cast<m::SampleChannel&>(c).shift).c_str());
-       });
+       m_shift->value(std::to_string(shift).c_str());
 }
 
 
@@ -108,6 +115,6 @@ void geShiftTool::rebuild()
 
 void geShiftTool::shift(int f)
 {
-       c::sampleEditor::shift(m_channelId, m_waveId, f);
+       c::sampleEditor::shift(m_data->channelId, m_data->waveId, f);
 }
 }} // giada::v::
index aa26d065786034234c8c76dcf101be7f0a4944a2..2113517881d198f28a12e23e187a62b5f4aa9c3d 100644 (file)
@@ -44,9 +44,10 @@ class geShiftTool : public Fl_Pack
 {
 public:
 
-       geShiftTool(ID channelId, ID waveId, int x, int y);
+       geShiftTool(const c::sampleEditor::Data& d, int x, int y);
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
+       void update(Frame shift);
 
 private:
 
@@ -57,8 +58,7 @@ private:
 
        void shift(int f);
 
-       ID m_channelId;
-       ID m_waveId;
+       const c::sampleEditor::Data* m_data;
        
        geBox*    m_label;
        geInput*  m_shift;
index 81ac99eeb710104487e515715113391757ed1b22..4819815e6c08289269a03ecf80b190a8a3b66f5c 100644 (file)
 #include <cmath>
 #include <cstdlib>
 #include <FL/Fl_Pack.H>
-#include "core/channels/sampleChannel.h"
 #include "core/const.h"
-#include "core/model/model.h"
-#include "glue/channel.h"
+#include "glue/events.h"
 #include "utils/gui.h"
 #include "utils/math.h"
 #include "utils/string.h"
@@ -46,9 +44,9 @@
 namespace giada {
 namespace v 
 {
-geVolumeTool::geVolumeTool(ID channelId, int X, int Y)
-: Fl_Pack    (X, Y, 150, G_GUI_UNIT),
-  m_channelId(channelId)
+geVolumeTool::geVolumeTool(const c::sampleEditor::Data& d, int X, int Y)
+: Fl_Pack(X, Y, 150, G_GUI_UNIT)
+, m_data (nullptr)
 {
        type(Fl_Pack::HORIZONTAL);
        spacing(G_GUI_INNER_MARGIN);
@@ -63,23 +61,33 @@ geVolumeTool::geVolumeTool(ID channelId, int X, int Y)
        dial->callback(cb_setVolume, (void*)this);
 
        input->callback(cb_setVolumeNum, (void*)this);
+
+       rebuild(d);
 }
 
 
 /* -------------------------------------------------------------------------- */
 
 
-void geVolumeTool::rebuild()
+void geVolumeTool::rebuild(const c::sampleEditor::Data& d)
 {
-       float volume;
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) { volume = c.volume; });
+       m_data = &d;
+       update(m_data->volume, /*isDial=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
 
+void geVolumeTool::update(float v, bool isDial)
+{
        std::string tmp = "-inf";
-       float dB = u::math::linearToDB(volume);
+       float dB = u::math::linearToDB(v);
        if (dB > -INFINITY) 
                tmp = u::string::fToString(dB, 2);  // 2 digits
        input->value(tmp.c_str());
-       dial->value(volume);
+       if (!isDial)
+               dial->value(v);
 }
 
 
@@ -95,7 +103,7 @@ void geVolumeTool::cb_setVolumeNum(Fl_Widget* w, void* p) { ((geVolumeTool*)p)->
 
 void geVolumeTool::cb_setVolume()
 {
-       c::channel::setVolume(m_channelId, dial->value(), false, true);
+       c::events::setChannelVolume(m_data->channelId, dial->value(), Thread::MAIN);
 }
 
 
@@ -104,8 +112,7 @@ void geVolumeTool::cb_setVolume()
 
 void geVolumeTool::cb_setVolumeNum()
 {
-       float value = pow(10, (atof(input->value()) / 20)); // linear = 10^(dB/20)
-       c::channel::setVolume(m_channelId, value, false, true);
+       c::events::setChannelVolume(m_data->channelId, u::math::dBtoLinear(atof(input->value())), 
+               Thread::MAIN);
 }
-
 }} // giada::v::
index b719607819f470d89eb6a1eca9bfb9120a18b893..15276a2af17abf3145eb6915ccd6983aa7178db9 100644 (file)
@@ -44,22 +44,24 @@ class geVolumeTool : public Fl_Pack
 {
 public:
 
-       geVolumeTool(ID channelId, int x, int y);
+       geVolumeTool(const c::sampleEditor::Data& d, int x, int y);
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
+       void update(float v, bool isDial=false);
        
 private:
 
-       ID m_channelId;
+       static void cb_setVolume(Fl_Widget* w, void* p);
+       static void cb_setVolumeNum(Fl_Widget* w, void* p);
+       void cb_setVolume();
+       void cb_setVolumeNum();
+
+       const c::sampleEditor::Data* m_data;
 
        geBox*   label;
        geDial*  dial;
        geInput* input;
 
-       static void cb_setVolume   (Fl_Widget* w, void* p);
-       static void cb_setVolumeNum(Fl_Widget* w, void* p);
-       void cb_setVolume   ();
-       void cb_setVolumeNum();
 };
 }} // giada::v::
 
index ceaf1e666117d6ed1797a4fe8d0399d807c63d60..4e28e0ffe96634e199bc5c390f29b5dfaeade041 100644 (file)
@@ -28,7 +28,6 @@
 #include <cstdint>
 #include <FL/Fl_Menu_Item.H>
 #include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/waveFx.h"
 #include "core/const.h"
@@ -68,9 +67,9 @@ void menuCallback_(Fl_Widget* w, void* v)
 {
        const geWaveTools* wt = static_cast<geWaveTools*>(w);
        
-       ID     channelId    = wt->channelId;
-       size_t waveId    = wt->waveId;
-       Menu   selectedItem = (Menu) (intptr_t) v;
+       ID   channelId = wt->getChannelData().channelId;
+       ID   waveId    = wt->getChannelData().waveId;
+       Menu selectedItem = (Menu) (intptr_t) v;
 
        int a = wt->waveform->getSelectionA();
        int b = wt->waveform->getSelectionB();
@@ -89,28 +88,28 @@ void menuCallback_(Fl_Widget* w, void* v)
                        c::sampleEditor::trim(channelId, waveId, a, b);
                        break;
                case Menu::SILENCE:
-                       c::sampleEditor::silence(waveId, a, b);
+                       c::sampleEditor::silence(channelId, waveId, a, b);
                        break;    
                case Menu::REVERSE:
-                       c::sampleEditor::reverse(waveId, a, b);
+                       c::sampleEditor::reverse(channelId, waveId, a, b);
                        break;                  
                case Menu::NORMALIZE:
-                       c::sampleEditor::normalizeHard(waveId, a, b);
+                       c::sampleEditor::normalize(channelId, waveId, a, b);
                        break;  
                case Menu::FADE_IN:
-                       c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_IN);
+                       c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::IN);
                        break;
                case Menu::FADE_OUT:
-                       c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_OUT);
+                       c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::OUT);
                        break;
                case Menu::SMOOTH_EDGES:
-                       c::sampleEditor::smoothEdges(waveId, a, b);
+                       c::sampleEditor::smoothEdges(channelId, waveId, a, b);
                        break;
                case Menu::SET_BEGIN_END:
-                       c::sampleEditor::setBeginEnd(waveId, a, b);
+                       c::sampleEditor::setBeginEnd(channelId, a, b);
                        break;          
                case Menu::TO_NEW_CHANNEL:
-                       c::sampleEditor::toNewChannel(waveId, a, b);
+                       c::sampleEditor::toNewChannel(channelId, waveId, a, b);
                        break;
        }
 }
@@ -122,10 +121,9 @@ void menuCallback_(Fl_Widget* w, void* v)
 /* -------------------------------------------------------------------------- */
 
 
-geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h)
-: Fl_Scroll(x, y, w, h, nullptr),
-  channelId(channelId),
-  waveId(waveId)
+geWaveTools::geWaveTools(int x, int y, int w, int h)
+: Fl_Scroll(x, y, w, h, nullptr)
+, m_data   (nullptr)
 {
        type(Fl_Scroll::HORIZONTAL_ALWAYS);
        hscrollbar.color(G_COLOR_GREY_2);
@@ -133,7 +131,7 @@ geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h)
        hscrollbar.labelcolor(G_COLOR_LIGHT_1);
        hscrollbar.slider(G_CUSTOM_BORDER_BOX);
 
-       waveform = new v::geWaveform(channelId, waveId, x, y, w, h-24);
+       waveform = new v::geWaveform(x, y, w, h-24);
 }
 
 
@@ -141,9 +139,10 @@ geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h)
 /* -------------------------------------------------------------------------- */
 
 
-void geWaveTools::rebuild()
+void geWaveTools::rebuild(const c::sampleEditor::Data& d)
 {
-       waveform->rebuild();
+       m_data = &d;
+       waveform->rebuild(d);
 }
 
 
@@ -152,11 +151,8 @@ void geWaveTools::rebuild()
 
 void geWaveTools::refresh()
 {
-       m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
-       {
-               if (c.isPreview())
-                       waveform->redraw();
-       });
+       if (m_data->a_getPreviewStatus() == ChannelStatus::PLAY)
+               waveform->redraw();
 }
 
 
@@ -169,7 +165,7 @@ void geWaveTools::resize(int x, int y, int w, int h)
 
        if (this->w() == w || (this->w() != w && this->h() != h)) {   // vertical or both resize
                waveform->resize(x, y, waveform->w(), h-24);
-               waveform->rebuild();
+               waveform->rebuild(*m_data);
        }
 
        if (this->w() > waveform->w())
index ba8eb0ad402da6ac4fd0d4cf4f28aa774a6f9d6c..d920137bb65820fc2e40ed18a3779a373b915474 100644 (file)
@@ -40,7 +40,7 @@ class geWaveTools : public Fl_Scroll
 {
 public:
 
-       geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h);
+       geWaveTools(int x, int y, int w, int h);
 
        void resize(int x, int y, int w, int h) override;
        int  handle(int e) override;
@@ -49,7 +49,7 @@ public:
        Updates the waveform by realloc-ing new data (i.e. when the waveform has
        changed). */
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
 
        /* refresh
        Redraws the waveform, called by the video thread. This is meant to be called
@@ -57,15 +57,16 @@ public:
        method is smart enough to skip painting if the channel is stopped. */
 
        void refresh();
+
+       const c::sampleEditor::Data& getChannelData() const { return *m_data; }
        
        v::geWaveform* waveform;
 
-       ID channelId;
-       ID waveId;
-
 private:
 
        void openMenu();
+
+       const c::sampleEditor::Data* m_data;
 };
 }} // giada::v::
 
index 1ad02a8580dafa0ad32e31396220afd53860120f..aec4f9ae167e9d190a009a4e627567d225efee43 100644 (file)
@@ -29,7 +29,6 @@
 #include <cmath>
 #include <FL/fl_draw.H>
 #include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
 #include "core/model/model.h"
 #include "core/wave.h"
 #include "core/conf.h"
 namespace giada {
 namespace v 
 {
-geWaveform::geWaveform(ID channelId, ID waveId, int x, int y, int w, int h)
-: Fl_Widget     (x, y, w, h, nullptr),
-  m_selection   {},
-  m_channelId   (channelId),
-  m_waveId      (waveId),
-  m_chanStart   (0),
-  m_chanStartLit(false),
-  m_chanEnd     (0),
-  m_chanEndLit  (false),
-  m_pushed      (false),
-  m_dragged     (false),
-  m_resizedA    (false),
-  m_resizedB    (false),
-  m_ratio       (0.0f)
+geWaveform::geWaveform(int x, int y, int w, int h)
+: Fl_Widget     (x, y, w, h, nullptr)
+, m_selection   {}
+, m_data        (nullptr)
+, m_chanStart   (0)
+, m_chanStartLit(false)
+, m_chanEnd     (0)
+, m_chanEndLit  (false)
+, m_pushed      (false)
+, m_dragged     (false)
+, m_resizedA    (false)
+, m_resizedB    (false)
+, m_ratio       (0.0f)
 {
-       m_data.size = w;
+       m_waveform.size = w;
 
        m_grid.snap  = m::conf::conf.sampleEditorGridOn;
        m_grid.level = m::conf::conf.sampleEditorGridVal;
@@ -75,9 +73,9 @@ geWaveform::geWaveform(ID channelId, ID waveId, int x, int y, int w, int h)
 
 void geWaveform::clearData()
 {
-       m_data.sup.clear();
-       m_data.inf.clear();
-       m_data.size = 0;
+       m_waveform.sup.clear();
+       m_waveform.inf.clear();
+       m_waveform.size = 0;
        m_grid.points.clear();
 }
 
@@ -87,9 +85,10 @@ void geWaveform::clearData()
 
 int geWaveform::alloc(int datasize, bool force)
 {
-       m::model::WavesLock lock(m::model::waves);
+       /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */
        
-       const m::Wave& wave = m::model::get(m::model::waves, m_waveId);
+       m::model::WavesLock l(m::model::waves);
+       const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId);
 
        m_ratio = wave.getSize() / (float) datasize;
 
@@ -100,16 +99,16 @@ int geWaveform::alloc(int datasize, bool force)
                m_ratio = 1;
        }
 
-       if (datasize == m_data.size && !force)
+       if (datasize == m_waveform.size && !force)
                return 0;
 
        clearData();
 
-       m_data.size = datasize;
-       m_data.sup.resize(m_data.size);
-       m_data.inf.resize(m_data.size);
+       m_waveform.size = datasize;
+       m_waveform.sup.resize(m_waveform.size);
+       m_waveform.inf.resize(m_waveform.size);
 
-       u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio);
+       u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_waveform.size, m_ratio);
 
        int offset = h() / 2;
        int zero   = y() + offset; // center, zero amplitude (-inf dB)
@@ -122,7 +121,7 @@ int geWaveform::alloc(int datasize, bool force)
        /* Resampling the waveform, hardcore way. Many thanks to 
        http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */
 
-       for (int i = 0; i < m_data.size; i++) {
+       for (int i = 0; i < m_waveform.size; i++) {
                
                /* Scan the original waveform in chunks [pc, pn]. */
 
@@ -156,13 +155,13 @@ int geWaveform::alloc(int datasize, bool force)
                                m_grid.points.push_back(k);
                }
 
-               m_data.sup[i] = zero - (peaksup * offset);
-               m_data.inf[i] = zero - (peakinf * offset);
+               m_waveform.sup[i] = zero - (peaksup * offset);
+               m_waveform.inf[i] = zero - (peakinf * offset);
 
                // avoid window overflow
 
-               if (m_data.sup[i] < y())       m_data.sup[i] = y();
-               if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1;
+               if (m_waveform.sup[i] < y())       m_waveform.sup[i] = y();
+               if (m_waveform.inf[i] > y()+h()-1) m_waveform.inf[i] = y()+h()-1;
        }
 
        recalcPoints();
@@ -175,11 +174,8 @@ int geWaveform::alloc(int datasize, bool force)
 
 void geWaveform::recalcPoints()
 {
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               m_chanStart = static_cast<m::SampleChannel&>(c).getBegin();
-               m_chanEnd   = static_cast<m::SampleChannel&>(c).getEnd();
-       });
+       m_chanStart = m_data->begin;
+       m_chanEnd   = m_data->end;
 }
 
 
@@ -215,10 +211,10 @@ void geWaveform::drawWaveform(int from, int to)
 
        fl_color(G_COLOR_BLACK);
        for (int i=from; i<to; i++) {
-               if (i >= m_data.size)
+               if (i >= m_waveform.size)
                        break;
-               fl_line(i+x(), zero, i+x(), m_data.sup[i]);
-               fl_line(i+x(), zero, i+x(), m_data.inf[i]);
+               fl_line(i+x(), zero, i+x(), m_waveform.sup[i]);
+               fl_line(i+x(), zero, i+x(), m_waveform.inf[i]);
        }
 }
 
@@ -286,13 +282,7 @@ void geWaveform::drawStartEndPoints()
 
 void geWaveform::drawPlayHead()
 {
-       float tp;       
-       m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
-       {
-               tp = static_cast<m::SampleChannel&>(c).trackerPreview;
-       });
-
-       int p = frameToPixel(tp) + x();
+       int p = frameToPixel(m_data->a_getPreviewTracker()) + x();
        fl_color(G_COLOR_LIGHT_2);
        fl_line(p, y() + 1, p, y() + h() - 2);
 }
@@ -303,8 +293,8 @@ void geWaveform::drawPlayHead()
 
 void geWaveform::draw()
 {
-       assert(m_data.sup.size() > 0);
-       assert(m_data.inf.size() > 0);
+       assert(m_waveform.sup.size() > 0);
+       assert(m_waveform.inf.size() > 0);
 
        fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2);  // blank canvas
 
@@ -332,9 +322,10 @@ void geWaveform::draw()
 
 int geWaveform::handle(int e)
 {
-       m::model::WavesLock lock(m::model::waves);
-       
-       const m::Wave& wave = m::model::get(m::model::waves, m_waveId);
+       /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */
+
+       m::model::WavesLock l(m::model::waves);
+       const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId);
 
        m_mouseX = pixelToFrame(Fl::event_x() - x());
        m_mouseY = pixelToFrame(Fl::event_y() - y());
@@ -346,7 +337,7 @@ int geWaveform::handle(int e)
                                static_cast<v::gdSampleEditor*>(window())->cb_togglePreview();
                        else
                        if (Fl::event_key() == FL_BackSpace)
-                               c::sampleEditor::rewindPreview(m_channelId);
+                               c::sampleEditor::setPreviewTracker(m_data->begin);
                        return 1;
                }
 
@@ -378,7 +369,7 @@ int geWaveform::handle(int e)
 
                case FL_RELEASE: {
 
-                       c::sampleEditor::setPlayHead(m_channelId, m_mouseX);
+                       c::sampleEditor::setPreviewTracker(m_mouseX);
 
                        /* If selection has been done (m_dragged or resized), make sure that point A 
                        is always lower than B. */
@@ -389,7 +380,7 @@ int geWaveform::handle(int e)
                        /* Handle begin/end markers interaction. */
 
                        if (m_chanStartLit || m_chanEndLit)
-                               c::sampleEditor::setBeginEnd(m_channelId, m_chanStart, m_chanEnd);
+                               c::sampleEditor::setBeginEnd(m_data->channelId, m_chanStart, m_chanEnd);
 
                        m_pushed   = false;
                        m_dragged  = false;
@@ -572,13 +563,10 @@ bool geWaveform::mouseOnSelectionB() const
 
 int geWaveform::pixelToFrame(int p) const
 {
-       Frame waveSize;
-       m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); });
-
        if (p <= 0)
                return 0;
-       if (p > m_data.size)
-               return waveSize - 1;
+       if (p > m_waveform.size)
+               return m_data->waveSize - 1;
        return p * m_ratio;
 }
 
@@ -600,7 +588,7 @@ void geWaveform::fixSelection()
        if (m_selection.a > m_selection.b) // inverted m_selection
                std::swap(m_selection.a, m_selection.b);
 
-       c::sampleEditor::setPlayHead(m_channelId, m_selection.a);
+       c::sampleEditor::setPreviewTracker(m_selection.a);
 }
 
 
@@ -619,10 +607,10 @@ void geWaveform::clearSelection()
 
 void geWaveform::setZoom(Zoom z)
 {
-       if (!alloc(z == Zoom::IN ? m_data.size * G_GUI_ZOOM_FACTOR : m_data.size / G_GUI_ZOOM_FACTOR)) 
+       if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR)) 
                return;
 
-       size(m_data.size, h());
+       size(m_waveform.size, h());
 
        /* Zoom to cursor. */
        
@@ -668,10 +656,11 @@ void geWaveform::stretchToWindow()
 /* -------------------------------------------------------------------------- */
 
 
-void geWaveform::rebuild()
+void geWaveform::rebuild(const c::sampleEditor::Data& d)
 {
+       m_data = &d;
        clearSelection();
-       alloc(m_data.size, /*force=*/true);
+       alloc(m_waveform.size, /*force=*/true);
        redraw();
 }
 
@@ -692,7 +681,7 @@ void geWaveform::setGridLevel(int l)
 {
        m_grid.points.clear();
        m_grid.level = l;
-       alloc(m_data.size, true); // force alloc
+       alloc(m_waveform.size, true); // force alloc
        redraw();
 }
 
@@ -711,7 +700,7 @@ bool geWaveform::isSelected() const
 
 void geWaveform::setSnap(bool v) { m_grid.snap = v; }
 bool geWaveform::getSnap() const { return m_grid.snap; }
-int geWaveform::getSize() const { return m_data.size; }
+int geWaveform::getSize() const { return m_waveform.size; }
 
 
 /* -------------------------------------------------------------------------- */
@@ -723,11 +712,8 @@ int geWaveform::getSelectionB() const { return m_selection.b; }
 
 void geWaveform::selectAll()
 {
-       Frame waveSize;
-       m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); });
-
        m_selection.a = 0;
-       m_selection.b = waveSize - 1;
+       m_selection.b = m_data->waveSize - 1;
        redraw();
 }
 }} // giada::v::
index d36691bb34e013631259943aa255c6fe7333f67f..4a928b99a87ea7c8e73b8445f528ee149bedbe25 100644 (file)
@@ -48,7 +48,7 @@ public:
 #endif
        enum class Zoom { IN, OUT };
 
-       geWaveform(ID channelId, ID waveId, int x, int y, int w, int h);
+       geWaveform(int x, int y, int w, int h);
 
        void draw() override;
        int  handle(int e) override;
@@ -82,7 +82,7 @@ public:
        /* rebuild
        Redraws the waveform. */
 
-       void rebuild();
+       void rebuild(const c::sampleEditor::Data& d);
 
        /* setGridLevel
        Sets a new frequency level for the grid. 0 means disabled. */
@@ -99,7 +99,7 @@ public:
        /* setWaveId
        Call this when the Wave ID has changed (e.g. after a reload). */
 
-       void setWaveId(ID id) { m_waveId = id; };
+       void setWaveId(ID id) { /* TODO m_waveId = id;*/};
 
 private:
 
@@ -125,7 +125,7 @@ private:
                std::vector<int> sup;   // upper part of the waveform
                std::vector<int> inf;   // lower part of the waveform
                int  size;  // width of the waveform to draw (in pixel)
-       } m_data;
+       } m_waveform;
 
        struct
        {
@@ -188,8 +188,7 @@ private:
 
        int alloc(int datasize, bool force=false);
 
-       ID m_channelId;
-       ID m_waveId;
+       const c::sampleEditor::Data* m_data;
 
        int   m_chanStart;
        bool  m_chanStartLit;
index 5db4c1ff45bab94ab8de18737d49a88e943ce0e3..c1fc884cdb0598b9e62fd830a12efac62d109a23 100644 (file)
@@ -40,7 +40,7 @@ void update(void* p)
 {
        if (m::model::waves.changed.load()    == true ||
                m::model::actions.changed.load()  == true ||
-               m::model::channels.changed.load() == true)
+               m::model::channels.changed.load()  == true)
        {
                u::gui::rebuild();
                m::model::waves.changed.store(false);
index d207f5fd7a05c8e4f243bce990ce6d23f06130d6..a0fa5bdfcacb2025e841356f5e79787b54ac5cf1 100644 (file)
@@ -33,7 +33,6 @@
 #elif defined(__linux__) || defined(__FreeBSD__)
        #include <X11/xpm.h>
 #endif
-#include "core/channels/channel.h"
 #include "core/mixer.h"
 #include "core/mixerHandler.h"
 #include "core/clock.h"
@@ -154,7 +153,7 @@ void updateStaticWidgets()
 
        G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars());
        G_MainWin->mainTimer->setBpm(clock::getBpm());
-       G_MainWin->mainTimer->setQuantizer(clock::getQuantize());
+       G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue());
 }
 
 
index 50840211f5519a7cdc0d2b7f1d8d6fc7478647c8..ce45677092325600e0e9c7455f771202793aaa4a 100644 (file)
@@ -59,30 +59,6 @@ TO map(TI x, TI b, TO z)
 {
        return (x / (double) b) * z;
 }
-
-
-/* -------------------------------------------------------------------------- */
-
-/* bound (1)
-Returns 'def' if 'x' is outside the range ('min', 'max'). */
-
-template <typename T>
-T bound(T x, T min, T max, T def)
-{
-    return x < min || x > max ? def : x;
-}
-
-
-/* bound (2)
-Clamps 'x' in the range ('min', 'max'). */
-
-template <typename T>
-T bound(T x, T min, T max)
-{
-    if (x < min) return min; 
-       if (x > max) return max;
-       return x;
-}
 }}}  // giada::u::math::
 
 
index 7e22758c3e7bcfb58a036cbbc149d030fd6c1f92..4d7757c93f50465ed7ba0be2f9cdd5c8d28b8898 100644 (file)
@@ -89,7 +89,7 @@ std::string trim(const std::string& s)
 
 std::string replace(std::string in, const std::string& search, const std::string& replace)
 {
-       size_t pos = 0;
+       std::size_t pos = 0;
        while ((pos = in.find(search, pos)) != std::string::npos) {
                in.replace(pos, search.length(), replace);
                pos += replace.length();
@@ -109,7 +109,7 @@ std::string format(const char* format, ...)
        into account). */
 
        va_start(args, format);
-       size_t size = vsnprintf(nullptr, 0, format, args) + 1;
+       std::size_t size = vsnprintf(nullptr, 0, format, args) + 1;
        va_end(args);
        
        /* Create a new temporary char array to hold the new expanded std::string. */
@@ -134,8 +134,8 @@ std::vector<std::string> split(std::string in, std::string sep)
        std::vector<std::string> out;
        std::string full  = in;
        std::string token = "";
-       size_t curr = 0;
-       size_t next = -1;
+       std::size_t curr = 0;
+       std::size_t next = -1;
        do {
                curr  = next + 1;
                next  = full.find_first_of(sep, curr);
index 583d7de915a2581128cf626a88a5c81415f276ba..4f6a7855f854bdf15a4dd5e8d78f11da69e79dfa 100644 (file)
@@ -39,19 +39,12 @@ namespace u {
 namespace vector 
 {
 template <typename T, typename P>
-size_t indexOf(T& v, const P& p)
+std::size_t indexOf(T& v, const P& p)
 {
        return std::distance(v.begin(), std::find(v.begin(), v.end(), p));
 }
 
 
-template <typename T, typename P>
-size_t indexOfIf(T& v, P p)
-{
-       return std::distance(v.begin(), std::find_if(v.begin(), v.end(), p));
-}
-
-
 /* -------------------------------------------------------------------------- */
 
 
index b9d14993638aef3714cc4b167f30b84417c242e9..189f4167342d285f91b2cefb31696d33f12d6e0c 100644 (file)
@@ -87,13 +87,6 @@ TEST_CASE("AudioBuffer")
                        REQUIRE(buffer[1024][0] == 2048.0f);
                }
 
-               SECTION("test copy frame")
-               {       
-                       buffer.copyFrame(16, &data[32]);
-                       REQUIRE(buffer[16][0] == 32.0f);
-                       REQUIRE(buffer[16][1] == 33.0f);
-               }
-
                delete[] data;
        }
 }
diff --git a/tests/sampleChannel.cpp b/tests/sampleChannel.cpp
deleted file mode 100644 (file)
index dcb59f3..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-#include "../src/core/channels/sampleChannel.h"
-#include "../src/core/model/model.h"
-#include <catch.hpp>
-
-
-TEST_CASE("sampleChannel")
-{
-       using namespace giada;
-       using namespace giada::m;
-
-       const int BUFFER_SIZE = 1024;
-       const int WAVE_SIZE   = 50000;
-
-       std::vector<ChannelMode> modes = { ChannelMode::LOOP_BASIC, 
-               ChannelMode::LOOP_ONCE, ChannelMode::LOOP_REPEAT, 
-               ChannelMode::LOOP_ONCE_BAR, ChannelMode::SINGLE_BASIC, 
-               ChannelMode::SINGLE_PRESS, ChannelMode::SINGLE_RETRIG, 
-               ChannelMode::SINGLE_ENDLESS };
-
-       model::channels.clear();
-       model::channels.push(std::make_unique<SampleChannel>(false, BUFFER_SIZE, 1, 1));
-
-       model::onSwap(model::channels, 1, [&](Channel& c)
-       {
-               static_cast<SampleChannel&>(c).pushWave(1, WAVE_SIZE);
-       });
-
-       model::channels.lock();
-       SampleChannel& ch = static_cast<SampleChannel&>(*model::channels.get(0));
-       model::channels.unlock();
-
-       SECTION("push wave")
-       {
-               REQUIRE(ch.playStatus == ChannelStatus::OFF);
-               REQUIRE(ch.begin == 0);
-               REQUIRE(ch.end == WAVE_SIZE);
-               REQUIRE(ch.name == "");         
-       }
-
-       SECTION("begin/end")
-       {
-               /* TODO - This section requires model::waves interaction. Let's wait for 
-               the non-virtual channel refactoring... */
-               /*
-               ch.setBegin(-100);
-
-               REQUIRE(ch.getBegin() == 0);
-               REQUIRE(ch.tracker == 0);
-               REQUIRE(ch.trackerPreview == 0);
-
-               ch.setBegin(100000);
-
-               REQUIRE(ch.getBegin() == WAVE_SIZE);
-               REQUIRE(ch.tracker == WAVE_SIZE);
-               REQUIRE(ch.trackerPreview == WAVE_SIZE);
-
-               ch.setBegin(16);
-
-               REQUIRE(ch.getBegin() == 16);
-               REQUIRE(ch.tracker == 16);
-               REQUIRE(ch.trackerPreview == 16);
-
-               ch.setEnd(0);
-
-               REQUIRE(ch.getEnd() == 17);
-
-               ch.setEnd(100000);
-
-               REQUIRE(ch.getEnd() == WAVE_SIZE - 1);
-
-               ch.setEnd(32);
-
-               REQUIRE(ch.getEnd() == 32);
-
-               ch.setBegin(64);
-
-               REQUIRE(ch.getBegin() == 31);
-               */
-       }
-
-       SECTION("pitch")
-       {
-               ch.setPitch(40.0f);
-
-               REQUIRE(ch.getPitch() == G_MAX_PITCH);
-
-               ch.setPitch(-2.0f);
-
-               REQUIRE(ch.getPitch() == G_MIN_PITCH);
-
-               ch.setPitch(0.8f);
-
-               REQUIRE(ch.getPitch() == 0.8f);
-       }
-
-       SECTION("position")
-       {
-               REQUIRE(ch.getPosition() == -1);  // Initially OFF
-
-               ch.playStatus = ChannelStatus::PLAY;
-               ch.tracker    = 1000;
-
-               REQUIRE(ch.getPosition() == 1000);
-
-               ch.begin = 700;
-
-               REQUIRE(ch.getPosition() == 300);
-       }
-
-       SECTION("empty")
-       {
-               ch.empty();
-
-               REQUIRE(ch.playStatus == ChannelStatus::EMPTY);
-               REQUIRE(ch.begin == 0);
-               REQUIRE(ch.end == 0);
-               REQUIRE(ch.tracker == 0);
-               REQUIRE(ch.volume == G_DEFAULT_VOL);
-               REQUIRE(ch.hasActions == false);
-               REQUIRE(ch.hasWave == false);
-               REQUIRE(ch.waveId == 0);
-       }
-
-       SECTION("can record audio")
-       {       
-               REQUIRE(ch.canInputRec() == false); // Can't record if not armed
-
-               ch.armed = true;
-
-               REQUIRE(ch.canInputRec() == false); // Can't record with Wave in it
-
-               ch.empty();
-
-               REQUIRE(ch.canInputRec() == true);
-       }
-
-       /* TODO - fillBuffer, isAnyLoopMode, isAnySingleMode, isOnLastFrame */
-}
index bc8bf21d0171a0b787d2b4384f3a70e5d7ae3717..ca25d016112560a5a56276bba3802815b79a5c58 100644 (file)
@@ -118,8 +118,8 @@ TEST_CASE("waveFx")
                int a = 47;
                int b = 500;
 
-               wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_IN);
-               wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_OUT);
+               wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::IN);
+               wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::OUT);
 
                REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[0] == 0.0f);
                REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[1] == 0.0f);
@@ -128,8 +128,8 @@ TEST_CASE("waveFx")
 
                SECTION("test fade (mono)")
                {
-                       wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_IN);
-                       wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_OUT);
+                       wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::IN);
+                       wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::OUT);
 
                        REQUIRE(getWave(WAVE_MONO_ID).getFrame(a)[0] == 0.0f);
                        REQUIRE(getWave(WAVE_MONO_ID).getFrame(b)[0] == 0.0f);