list(APPEND SOURCES
src/main.cpp
+ src/core/engine.cpp
src/core/worker.cpp
src/core/eventDispatcher.cpp
src/core/midiDispatcher.cpp
- src/core/midiMapConf.cpp
+ src/core/midiMapper.cpp
src/core/midiEvent.cpp
- src/core/audioBuffer.cpp
src/core/quantizer.cpp
src/core/conf.cpp
src/core/kernelAudio.cpp
+ src/core/jackTransport.cpp
src/core/mixerHandler.cpp
src/core/sequencer.cpp
src/core/metronome.cpp
src/core/kernelMidi.cpp
src/core/graphics.cpp
src/core/patch.cpp
- src/core/recorderHandler.cpp
- src/core/recorder.cpp
+ src/core/actions/actionRecorder.cpp
+ src/core/actions/actions.cpp
src/core/mixer.cpp
- src/core/clock.cpp
+ src/core/synchronizer.cpp
src/core/waveManager.cpp
- src/core/recManager.cpp
+ src/core/recorder.cpp
src/core/midiLearnParam.cpp
src/core/resampler.cpp
src/core/plugins/pluginHost.cpp
src/core/channels/midiReceiver.cpp
src/core/channels/channel.cpp
src/core/channels/channelManager.cpp
+ src/core/model/sequencer.cpp
+ src/core/model/mixer.cpp
+ src/core/model/recorder.cpp
src/core/model/model.cpp
src/core/model/storage.cpp
src/core/idManager.cpp
src/glue/sampleEditor.cpp
src/glue/actionEditor.cpp
src/glue/config.cpp
+ src/glue/layout.cpp
+ src/gui/ui.cpp
src/gui/dialogs/window.cpp
src/gui/dispatcher.cpp
src/gui/updater.cpp
- src/gui/model.cpp
src/gui/drawing.cpp
src/gui/dialogs/keyGrabber.cpp
src/gui/dialogs/about.cpp
src/gui/elems/actionEditor/velocityEditor.cpp
src/gui/elems/actionEditor/envelopePoint.cpp
src/gui/elems/actionEditor/pianoRoll.cpp
- src/gui/elems/actionEditor/noteEditor.cpp
src/gui/elems/actionEditor/pianoItem.cpp
src/gui/elems/actionEditor/sampleActionEditor.cpp
src/gui/elems/actionEditor/sampleAction.cpp
src/gui/elems/actionEditor/gridTool.cpp
+ src/gui/elems/actionEditor/splitScroll.cpp
src/gui/elems/mainWindow/mainIO.cpp
src/gui/elems/mainWindow/mainMenu.cpp
src/gui/elems/mainWindow/mainTimer.cpp
src/gui/elems/basics/slider.cpp
src/gui/elems/basics/progress.cpp
src/gui/elems/basics/check.cpp
+ src/gui/elems/basics/split.cpp
src/utils/log.cpp
src/utils/time.cpp
src/utils/math.cpp
src/utils/fs.cpp
src/utils/ver.cpp
src/utils/string.cpp
- src/deps/rtaudio/RtAudio.cpp)
+ src/deps/rtaudio/RtAudio.cpp
+ src/deps/mcl-audio-buffer/src/audioBuffer.cpp)
list(APPEND PREPROCESSOR_DEFS)
list(APPEND INCLUDE_DIRS
# ------------------------------------------------------------------------------
if(DEFINED OS_WINDOWS)
- list(APPEND COMPILER_OPTIONS /W4)
+ list(APPEND COMPILER_OPTIONS /W4 /bigobj /external:anglebrackets /external:W0)
else()
list(APPEND COMPILER_OPTIONS -Wall -Wextra -Wpedantic)
endif()
# Threads (system)
+set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
-list(APPEND LIBRARIES ${Threads_LIBRARY})
+list(APPEND LIBRARIES Threads::Threads)
# pkg-config/pkgconf, required to find some external dependencies on some
# platforms
message(FATAL_ERROR "Can't find RtMidi, aborting.")
endif()
- # RtMidi header path may vary accross OSes, so a fix is needed.
+ # RtMidi header path may vary across OSes, so a fix is needed.
# TODO - Find a way to avoid this additional step
find_path(LIBRARY_RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES rtmidi)
list(APPEND PREPROCESSOR_DEFS
WITH_VST
+ JUCE_DEBUG=$<BOOL:$<CONFIG:Debug>>
+ JUCE_MODAL_LOOPS_PERMITTED=1
JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1
JUCE_MODULE_AVAILABLE_juce_gui_basics=1
JUCE_STANDALONE_APPLICATION=1
--------------------------------------------------------------------------------
+0.19.1 --- 2021 . 12 . 15
+- Enable JUCE_DEBUG in Debug builds
+- New MidiLighter tests + compile-time dependency injection
+- Set limits to minimum zoom level in Action Editors (#425)
+- Refactoring and code cleanup for Channel class and other sub-components
+- Update JUCE to version 6.1.2
+- Update RtAudio to version 5.2.0
+- Sanitize MIDI ports values (fixes #515)
+- MidiLighter improvements and cleanups (fixes #517)
+- Fix off-the-beat metronome (#522)
+- Fix number of plug-ins found not being updated after a scan (fix #523)
+- Fix PluginManager initialization
+- Fix pthread linking in CMake (#520)
+- Fix build info not being printed correctly on startup
+- [Linux] Fix X error messages on closing some plug-in editors
+- [Linux] Fix wrong icon file in XDG desktop file
+
+
+0.19.0 --- 2021 . 11 . 01
+- New "One-shot Pause" channel mode
+- Refactoring: new component-based architecture
+- Fix crash on startup if recording from mono input
+- Improved event handling for plug-ins GUIs
+- Fix many compiler warnings on menu items initialization
+
+
+0.18.2 --- 2021 . 09 . 13
+- New stereo In/Out audio meters
+- Revamped Action Editor: better UI, improved usability
+- Show play head in Action Editor
+- Implement queue for MIDI events, fix issue #482
+- Simplified Event Dispatcher's Event type
+- Move JACK transport operations to new JackTransport class
+- Always pick sample rate from the first audio device when using JACK
+- Don't send MIDI events if MIDI channel is not playing (#499) or muted (#497)
+- Add AtomicSwapper as git submodule
+- Upgrade JUCE to version 6.1.0
+
+
0.18.1 --- 2021 . 07 . 25
- New resampler architecture: allows for changing quality also for live rendering (#288)
- Gracefully shutdown UI on close to random crashes on quit on Windows
0.16.0 --- 2019 . 12 . 02
-- Fix columns' resizer bar height on verical window resize
+- Fix columns' resizer bar height on vertical window resize
- Fix crash on MIDI learn global commands
- Fix wrong channel routing when triggering MIDI learnt commands
- Fix rewind button not rewinding sample channels in LOOP_* mode
- Update JUCE to version 5.1.2
- UI-less plug-in window refinements
- Update UI-less plug-in window on MIDI parameter's change
-- Strip .gptc/.gprj extention from patch name
+- Strip .gptc/.gprj extension from patch name
- [Sample Editor] Fix non-working 'cut' operation
- Fix missed MIDI events with more than 1 plug-in in the stack
- Fix File Browser path widget drawn incorrectly in OS X
- Enhanced patch/conf architecture
- Ability to edit a sample while playing
- Mutex controls in VST processing
-- Lots of security issues fixed while changing pitch dinamically
+- Lots of security issues fixed while changing pitch dynamically
- Enhanced sub-window system
- Several minor bugs fixed
- (giada) Startup no longer fails if a sample from the ini file is not found
- (giada) Internal optimization for the sample loading routine
- (giada) More graphical consistency between subwindows
-- (giada) The sample name is now trucated to fit into its box, preventing overflow
+- (giada) The sample name is now truncated to fit into its box, preventing overflow
- (giada) Other minor GUI tweaks
- (giada) Internal memory improvements to prevent a bad bug of allocation with malformed wave files
- (wa) More information about sample size
## License
Giada is available under the terms of the GNU General Public License.
-Take a look at the COPYING file for further informations.
+Take a look at the COPYING file for further information.
## Documentation
GenericName[es]=Caja de ritmos y secuenciador de loops
Exec=giada %f
Terminal=false
-Icon=giada-logo
+Icon=com.giadamusic.Giada
Categories=Music;AudioVideo;Audio;Midi;X-Digital_Processing;X-Jack;X-MIDI;
-Keywords=Giada;
\ No newline at end of file
+Keywords=Giada;
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_ACTION_H
-#define G_ACTION_H
-
-#include "midiEvent.h"
-#include "types.h"
-
-namespace giada::m
-{
-struct Action
-{
- ID id = 0; // Invalid
- ID channelId;
- Frame frame;
- MidiEvent event;
- ID pluginId = -1;
- int pluginParam = -1;
- ID prevId = 0;
- ID nextId = 0;
-
- const Action* prev = nullptr;
- const Action* next = nullptr;
-
- bool isValid() const
- {
- return id != 0;
- }
-
- bool isVolumeEnvelope() const
- {
- return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1;
- }
-};
-} // namespace giada::m
-
-#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTION_H
+#define G_ACTION_H
+
+#include "src/core/midiEvent.h"
+#include "src/core/types.h"
+
+namespace giada::m
+{
+struct Action
+{
+ ID id = 0; // Invalid
+ ID channelId;
+ Frame frame;
+ MidiEvent event;
+ ID pluginId = -1;
+ int pluginParam = -1;
+ ID prevId = 0;
+ ID nextId = 0;
+
+ const Action* prev = nullptr;
+ const Action* next = nullptr;
+
+ bool isValid() const
+ {
+ return id != 0;
+ }
+
+ bool isVolumeEnvelope() const
+ {
+ return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1;
+ }
+};
+} // namespace giada::m
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/actions/actionRecorder.h"
+#include "core/actions/action.h"
+#include "core/actions/actions.h"
+#include "core/const.h"
+#include "core/model/model.h"
+#include "core/patch.h"
+#include "utils/log.h"
+#include "utils/ver.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <unordered_map>
+
+namespace giada::m
+{
+namespace
+{
+constexpr int MAX_LIVE_RECS_CHUNK = 128;
+} // namespace
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+ActionRecorder::ActionRecorder(model::Model& m)
+: m_model(m)
+, m_actions(m)
+{
+ m_liveActions.reserve(MAX_LIVE_RECS_CHUNK);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::reset()
+{
+ m_liveActions.clear();
+ m_actions.reset();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::isBoundaryEnvelopeAction(const Action& a) const
+{
+ assert(a.prev != nullptr);
+ assert(a.next != nullptr);
+ return a.prev->frame > a.frame || a.next->frame < a.frame;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::updateBpm(float ratio, int quantizerStep)
+{
+ if (ratio == 1.0f)
+ return;
+
+ m_actions.updateKeyFrames([=](Frame old) {
+ /* The division here cannot be precise. A new frame can be 44099 and the
+ quantizer set to 44100. That would mean two recs completely useless. So we
+ compute a reject value ('delta'): if it's lower than 6 frames the new frame
+ is collapsed with a quantized frame. FIXME - maybe 6 frames are too low. */
+ Frame frame = static_cast<Frame>(old * ratio);
+ if (frame != 0)
+ {
+ Frame delta = quantizerStep % frame;
+ if (delta > 0 && delta <= 6)
+ frame = frame + delta;
+ }
+ return frame;
+ });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::updateSamplerate(int systemRate, int patchRate)
+{
+ if (systemRate == patchRate)
+ return;
+
+ float ratio = systemRate / (float)patchRate;
+
+ m_actions.updateKeyFrames([=](Frame old) { return floorf(old * ratio); });
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::cloneActions(ID channelId, ID newChannelId)
+{
+ bool cloned = false;
+ std::vector<Action> actions;
+ std::unordered_map<ID, ID> map; // Action ID mapper, old -> new
+
+ m_actions.forEachAction([&](const Action& a) {
+ if (a.channelId != channelId)
+ return;
+
+ ID newActionId = m_actions.getNewActionId();
+
+ map.insert({a.id, newActionId});
+
+ Action clone(a);
+ clone.id = newActionId;
+ clone.channelId = newChannelId;
+
+ actions.push_back(clone);
+ cloned = true;
+ });
+
+ /* Update nextId and prevId relationships given the new action ID. */
+
+ for (Action& a : actions)
+ {
+ if (a.prevId != 0)
+ a.prevId = map.at(a.prevId);
+ if (a.nextId != 0)
+ a.nextId = map.at(a.nextId);
+ }
+
+ m_actions.rec(actions);
+
+ return cloned;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::liveRec(ID channelId, MidiEvent e, Frame globalFrame)
+{
+ assert(e.isNoteOnOff()); // Can't record any other kind of events for now
+
+ /* TODO - this might allocate on the MIDI thread */
+ if (m_liveActions.size() >= m_liveActions.capacity())
+ m_liveActions.reserve(m_liveActions.size() + MAX_LIVE_RECS_CHUNK);
+
+ m_liveActions.push_back(m_actions.makeAction(m_actions.getNewActionId(), channelId, globalFrame, e));
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::unordered_set<ID> ActionRecorder::consolidate()
+{
+ for (auto it = m_liveActions.begin(); it != m_liveActions.end(); ++it)
+ consolidate(*it, it - m_liveActions.begin()); // Pass current index
+
+ m_actions.rec(m_liveActions);
+
+ std::unordered_set<ID> out;
+ for (const Action& action : m_liveActions)
+ out.insert(action.channelId);
+
+ m_liveActions.clear();
+ return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::clearAllActions()
+{
+ for (Channel& ch : m_model.get().channels)
+ ch.hasActions = false;
+ m_model.swap(model::SwapType::HARD);
+
+ m_actions.clearAll();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Actions::Map ActionRecorder::deserializeActions(const std::vector<Patch::Action>& pactions)
+{
+ Actions::Map out;
+
+ /* First pass: add actions with no relationship, that is with no prev/next
+ pointers filled in. */
+
+ for (const Patch::Action& paction : pactions)
+ out[paction.frame].push_back(m_actions.makeAction(paction));
+
+ /* Second pass: fill in previous and next actions, if any. Is this the
+ fastest/smartest way to do it? Maybe not. Optimizations are welcome. */
+
+ for (const Patch::Action& paction : pactions)
+ {
+ if (paction.nextId == 0 && paction.prevId == 0)
+ continue;
+ Action* curr = const_cast<Action*>(getActionPtrById(paction.id, out));
+ assert(curr != nullptr);
+ if (paction.nextId != 0)
+ {
+ curr->next = getActionPtrById(paction.nextId, out);
+ assert(curr->next != nullptr);
+ }
+ if (paction.prevId != 0)
+ {
+ curr->prev = getActionPtrById(paction.prevId, out);
+ assert(curr->prev != nullptr);
+ }
+ }
+
+ return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Patch::Action> ActionRecorder::serializeActions(const Actions::Map& actions)
+{
+ std::vector<Patch::Action> out;
+ for (const auto& kv : actions)
+ {
+ for (const Action& a : kv.second)
+ {
+ out.push_back({
+ a.id,
+ a.channelId,
+ a.frame,
+ a.event.getRaw(),
+ a.prevId,
+ a.nextId,
+ });
+ }
+ }
+ return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+const Action* ActionRecorder::getActionPtrById(int id, const Actions::Map& source)
+{
+ for (const auto& [_, actions] : source)
+ for (const Action& action : actions)
+ if (action.id == id)
+ return &action;
+ return nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool ActionRecorder::areComposite(const Action& a1, const Action& a2) const
+{
+ return a1.event.getStatus() == MidiEvent::NOTE_ON &&
+ a2.event.getStatus() == MidiEvent::NOTE_OFF &&
+ a1.event.getNote() == a2.event.getNote() &&
+ a1.channelId == a2.channelId;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void ActionRecorder::consolidate(const Action& a1, std::size_t i)
+{
+ /* This algorithm must start searching from the element next to 'a1': since
+ live actions are recorded in linear sequence, the potential partner of 'a1'
+ always lies beyond a1 itself. Without this trick (i.e. if it loops from
+ vector.begin() each time) the algorithm would end up matching wrong partners. */
+
+ for (auto it = m_liveActions.begin() + i; it != m_liveActions.end(); ++it)
+ {
+
+ const Action& a2 = *it;
+
+ if (!areComposite(a1, a2))
+ continue;
+
+ const_cast<Action&>(a1).nextId = a2.id;
+ const_cast<Action&>(a2).prevId = a1.id;
+
+ break;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+const std::vector<Action>* ActionRecorder::getActionsOnFrame(Frame f) const
+{
+ return m_actions.getActionsOnFrame(f);
+}
+
+bool ActionRecorder::hasActions(ID channelId, int type) const
+{
+ return m_actions.hasActions(channelId, type);
+}
+
+Action ActionRecorder::getClosestAction(ID channelId, Frame f, int type) const
+{
+ return m_actions.getClosestAction(channelId, f, type);
+}
+
+std::vector<Action> ActionRecorder::getActionsOnChannel(ID channelId) const
+{
+ return m_actions.getActionsOnChannel(channelId);
+}
+
+void ActionRecorder::clearChannel(ID channelId)
+{
+ m_actions.clearChannel(channelId);
+}
+
+void ActionRecorder::clearActions(ID channelId, int type)
+{
+ m_actions.clearActions(channelId, type);
+}
+
+Action ActionRecorder::rec(ID channelId, Frame frame, MidiEvent e)
+{
+ return m_actions.rec(channelId, frame, e);
+}
+
+void ActionRecorder::rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+{
+ return m_actions.rec(channelId, f1, f2, e1, e2);
+}
+
+void ActionRecorder::updateSiblings(ID id, ID prevId, ID nextId)
+{
+ m_actions.updateSiblings(id, prevId, nextId);
+}
+
+void ActionRecorder::deleteAction(ID id)
+{
+ m_actions.deleteAction(id);
+}
+
+void ActionRecorder::deleteAction(ID currId, ID nextId)
+{
+ m_actions.deleteAction(currId, nextId);
+}
+
+void ActionRecorder::updateEvent(ID id, MidiEvent e)
+{
+ m_actions.updateEvent(id, e);
+}
+} // namespace giada::m
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTION_RECORDER_H
+#define G_ACTION_RECORDER_H
+
+#include "core/actions/actions.h"
+#include "core/midiEvent.h"
+#include "core/types.h"
+#include <unordered_set>
+
+namespace giada::m::patch
+{
+struct Action;
+}
+
+namespace giada::m
+{
+struct Action;
+class ActionRecorder
+{
+public:
+ ActionRecorder(model::Model&);
+
+ /* reset
+ Brings everything back to the initial state. */
+
+ void reset();
+
+ bool isBoundaryEnvelopeAction(const Action& a) const;
+
+ /* updateBpm
+ Changes actions position by calculating the new bpm value. */
+
+ void updateBpm(float ratio, int quantizerStep);
+
+ /* updateSamplerate
+ Changes actions position by taking in account the new samplerate. If
+ f_system == f_patch nothing will change, otherwise the conversion is
+ mandatory. */
+
+ void updateSamplerate(int systemRate, int patchRate);
+
+ /* cloneActions
+ Clones actions in channel 'channelId', giving them a new channel ID. Returns
+ whether any action has been cloned. */
+
+ bool cloneActions(ID channelId, ID newChannelId);
+
+ /* liveRec
+ Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
+
+ void liveRec(ID channelId, MidiEvent e, Frame global);
+
+ /* consolidate
+ Records all live actions. Returns a set of channels IDs that have been
+ recorded. */
+
+ std::unordered_set<ID> consolidate();
+
+ /* clearAllActions
+ Deletes all recorded actions. */
+
+ void clearAllActions();
+
+ /* (de)serializeActions
+ Creates new Actions given the patch raw data and vice versa. */
+
+ Actions::Map deserializeActions(const std::vector<Patch::Action>& as);
+ std::vector<Patch::Action> serializeActions(const Actions::Map& as);
+
+ /* Pass-thru functions. See Actions.h */
+
+ const std::vector<Action>* getActionsOnFrame(Frame f) const;
+ bool hasActions(ID channelId, int type = 0) const;
+ Action getClosestAction(ID channelId, Frame f, int type) const;
+ std::vector<Action> getActionsOnChannel(ID channelId) const;
+ void clearChannel(ID channelId);
+ void clearActions(ID channelId, int type);
+ Action rec(ID channelId, Frame frame, MidiEvent e);
+ void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+ void updateSiblings(ID id, ID prevId, ID nextId);
+ void deleteAction(ID id);
+ void deleteAction(ID currId, ID nextId);
+ void updateEvent(ID id, MidiEvent e);
+
+private:
+ /* areComposite
+ Composite: NOTE_ON + NOTE_OFF on the same note. */
+
+ bool areComposite(const Action& a1, const Action& a2) const;
+
+ const Action* getActionPtrById(int id, const Actions::Map& source);
+
+ /* consolidate
+ Given an action 'a1' tries to find the matching NOTE_OFF and updates the
+ action accordingly. */
+
+ void consolidate(const Action& a1, std::size_t i);
+
+ model::Model& m_model;
+ Actions m_actions;
+ std::vector<Action> m_liveActions;
+};
+} // namespace giada::m
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "actions.h"
+#include "action.h"
+#include "core/idManager.h"
+#include "core/model/model.h"
+#include "utils/log.h"
+#include <algorithm>
+#include <cassert>
+#include <memory>
+
+namespace giada::m
+{
+Actions::Actions(model::Model& model)
+: m_model(model)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::reset()
+{
+ m_actionId = IdManager();
+ clearAll();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearAll()
+{
+ model::DataLock lock = m_model.lockData();
+ m_model.getAllShared<Map>().clear();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearChannel(ID channelId)
+{
+ removeIf([=](const Action& a) { return a.channelId == channelId; });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::clearActions(ID channelId, int type)
+{
+ removeIf([=](const Action& a) {
+ return a.channelId == channelId && a.event.getStatus() == type;
+ });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::deleteAction(ID id)
+{
+ removeIf([=](const Action& a) { return a.id == id; });
+}
+
+void Actions::deleteAction(ID currId, ID nextId)
+{
+ removeIf([=](const Action& a) { return a.id == currId || a.id == nextId; });
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateKeyFrames(std::function<Frame(Frame old)> f)
+{
+ Map temp;
+
+ /* Copy all existing actions in local map by cloning them, with just a
+ difference: they have a new frame value. */
+
+ for (const auto& [oldFrame, actions] : m_model.getAllShared<Map>())
+ {
+ Frame newFrame = f(oldFrame);
+ for (const Action& a : actions)
+ {
+ Action copy = a;
+ copy.frame = newFrame;
+ temp[newFrame].push_back(copy);
+ }
+ G_DEBUG(oldFrame << " -> " << newFrame);
+ }
+
+ updateMapPointers(temp);
+
+ model::DataLock lock = m_model.lockData();
+ m_model.getAllShared<Map>() = std::move(temp);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateEvent(ID id, MidiEvent e)
+{
+ model::DataLock lock = m_model.lockData();
+
+ findAction(m_model.getAllShared<Map>(), id)->event = e;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateSiblings(ID id, ID prevId, ID nextId)
+{
+ model::DataLock lock = m_model.lockData();
+
+ Action* pcurr = findAction(m_model.getAllShared<Map>(), id);
+ Action* pprev = findAction(m_model.getAllShared<Map>(), prevId);
+ Action* pnext = findAction(m_model.getAllShared<Map>(), nextId);
+
+ pcurr->prev = pprev;
+ pcurr->prevId = pprev->id;
+ pcurr->next = pnext;
+ pcurr->nextId = pnext->id;
+
+ if (pprev != nullptr)
+ {
+ pprev->next = pcurr;
+ pprev->nextId = pcurr->id;
+ }
+ if (pnext != nullptr)
+ {
+ pnext->prev = pcurr;
+ pnext->prevId = pcurr->id;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::hasActions(ID channelId, int type) const
+{
+ for (const auto& [frame, actions] : m_model.getAllShared<Map>())
+ for (const Action& a : actions)
+ if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
+ return true;
+ return false;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::makeAction(ID id, ID channelId, Frame frame, MidiEvent e)
+{
+ Action out{m_actionId.generate(id), channelId, frame, e, -1, -1};
+ m_actionId.set(id);
+ return out;
+}
+
+Action Actions::makeAction(const Patch::Action& a)
+{
+ m_actionId.set(a.id);
+ return Action{a.id, a.channelId, a.frame, a.event, -1, -1, a.prevId,
+ a.nextId};
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::rec(ID channelId, Frame frame, MidiEvent event)
+{
+ /* Skip duplicates. */
+
+ if (exists(channelId, frame, event))
+ return {};
+
+ Action a = makeAction(0, channelId, frame, event);
+
+ /* If key frame doesn't exist yet, the [] operator in std::map is smart
+ enough to insert a new item first. No plug-in data for now. */
+
+ model::DataLock lock = m_model.lockData();
+
+ m_model.getAllShared<Map>()[frame].push_back(a);
+ updateMapPointers(m_model.getAllShared<Map>());
+
+ return a;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::rec(std::vector<Action>& actions)
+{
+ if (actions.size() == 0)
+ return;
+
+ model::DataLock lock = m_model.lockData();
+
+ Map& map = m_model.getAllShared<Map>();
+
+ for (const Action& a : actions)
+ if (!exists(a.channelId, a.frame, a.event, map))
+ map[a.frame].push_back(a);
+ updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+{
+ model::DataLock lock = m_model.lockData();
+
+ Map& map = m_model.getAllShared<Map>();
+
+ map[f1].push_back(makeAction(0, channelId, f1, e1));
+ map[f2].push_back(makeAction(0, channelId, f2, e2));
+
+ Action* a1 = findAction(map, map[f1].back().id);
+ Action* a2 = findAction(map, map[f2].back().id);
+ a1->nextId = a2->id;
+ a2->prevId = a1->id;
+
+ updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+const std::vector<Action>* Actions::getActionsOnFrame(Frame frame) const
+{
+ if (m_model.getAllShared<Map>().count(frame) == 0)
+ return nullptr;
+ return &m_model.getAllShared<Map>().at(frame);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action Actions::getClosestAction(ID channelId, Frame f, int type) const
+{
+ Action out = {};
+ forEachAction([&](const Action& a) {
+ if (a.event.getStatus() != type || a.channelId != channelId)
+ return;
+ if (!out.isValid() || (a.frame <= f && a.frame > out.frame))
+ out = a;
+ });
+ return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Action> Actions::getActionsOnChannel(ID channelId) const
+{
+ std::vector<Action> out;
+ forEachAction([&](const Action& a) {
+ if (a.channelId == channelId)
+ out.push_back(a);
+ });
+ return out;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::forEachAction(std::function<void(const Action&)> f) const
+{
+ for (auto& [_, actions] : m_model.getAllShared<Map>())
+ for (const Action& action : actions)
+ f(action);
+}
+
+/* -------------------------------------------------------------------------- */
+
+ID Actions::getNewActionId()
+{
+ return m_actionId.generate();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Action* Actions::findAction(Map& src, ID id)
+{
+ for (auto& [frame, actions] : src)
+ for (Action& a : actions)
+ if (a.id == id)
+ return &a;
+ assert(false);
+ return nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::updateMapPointers(Map& src)
+{
+ for (auto& kv : src)
+ {
+ for (Action& action : kv.second)
+ {
+ if (action.nextId != 0)
+ action.next = findAction(src, action.nextId);
+ if (action.prevId != 0)
+ action.prev = findAction(src, action.prevId);
+ }
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::optimize(Map& map)
+{
+ for (auto it = map.cbegin(); it != map.cend();)
+ it->second.size() == 0 ? it = map.erase(it) : ++it;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Actions::removeIf(std::function<bool(const Action&)> f)
+{
+ model::DataLock lock = m_model.lockData();
+
+ Map& map = m_model.getAllShared<Map>();
+ for (auto& [frame, actions] : map)
+ actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
+ optimize(map);
+ updateMapPointers(map);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::exists(ID channelId, Frame frame, const MidiEvent& event, const Map& target) const
+{
+ for (const auto& [_, actions] : target)
+ for (const Action& a : actions)
+ if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw())
+ return true;
+ return false;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Actions::exists(ID channelId, Frame frame, const MidiEvent& event) const
+{
+ return exists(channelId, frame, event, m_model.getAllShared<Map>());
+}
+} // namespace giada::m
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ACTIONS_H
+#define G_ACTIONS_H
+
+#include "action.h"
+#include "core/idManager.h"
+#include "core/midiEvent.h"
+#include "core/patch.h"
+#include "core/types.h"
+#include <functional>
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace giada::m::model
+{
+class Model;
+}
+
+namespace giada::m
+{
+class Actions
+{
+public:
+ using Map = std::map<Frame, std::vector<Action>>;
+
+ Actions(model::Model& model);
+
+ /* forEachAction
+ Applies a read-only callback on each action recorded. NEVER do anything
+ inside the callback that might alter the ActionMap. */
+
+ void forEachAction(std::function<void(const Action&)> f) const;
+
+ /* getActionsOnChannel
+ Returns a vector of actions belonging to channel 'ch'. */
+
+ std::vector<Action> getActionsOnChannel(ID channelId) const;
+
+ /* getClosestAction
+ Given a frame 'f' returns the closest action. */
+
+ Action getClosestAction(ID channelId, Frame f, int type) const;
+
+ /* getActionsOnFrame
+ Returns a pointer to a vector of actions recorded on frame 'f', or nullptr
+ if the frame has no actions. */
+
+ const std::vector<Action>* getActionsOnFrame(Frame f) const;
+
+ /* hasActions
+ Checks if the channel has at least one action recorded. */
+
+ bool hasActions(ID channelId, int type = 0) const;
+
+ /* makeAction
+ Makes a new action given some data. */
+ //TODO - move to actionManager
+
+ Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e);
+ Action makeAction(const Patch::Action& a);
+
+ /* reset
+ Brings everything back to the initial state. */
+
+ void reset();
+
+ /* clearAll
+ Deletes all recorded actions. */
+
+ void clearAll();
+
+ /* clearChannel
+ Clears all actions from a channel. */
+
+ void clearChannel(ID channelId);
+
+ /* clearActions
+ Clears the actions by type from a channel. */
+
+ void clearActions(ID channelId, int type);
+
+ /* deleteAction (1)
+ Deletes a specific action. */
+
+ void deleteAction(ID id);
+
+ /* deleteAction (2)
+ Deletes a specific pair of actions. Useful for composite stuff (i.e. MIDI). */
+
+ void deleteAction(ID currId, ID nextId);
+
+ /* updateKeyFrames
+ Update all the key frames in the internal map of actions, according to a
+ lambda function 'f'. */
+
+ void updateKeyFrames(std::function<Frame(Frame old)> f);
+
+ /* updateEvent
+ Changes the event in action 'a'. */
+
+ void updateEvent(ID id, MidiEvent e);
+
+ /* updateSiblings
+ Changes previous and next actions in action with id 'id'. Mostly used for
+ chained actions such as envelopes. */
+
+ void updateSiblings(ID id, ID prevId, ID nextId);
+
+ /* rec (1)
+ Records an action and returns it. Used by the Action Editor. */
+
+ Action rec(ID channelId, Frame frame, MidiEvent e);
+
+ /* rec (2)
+ Transfer a vector of actions into the current ActionMap. This is called by
+ recordHandler when a live session is over and consolidation is required. */
+
+ void rec(std::vector<Action>& actions);
+
+ /* rec (3)
+ Records two actions on channel 'channel'. Useful when recording composite
+ actions in the Action Editor. */
+
+ void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+
+ /* getNewActionId
+ Returns a new action ID, internally generated. */
+ //TODO - move to actionManager
+
+ ID getNewActionId();
+
+private:
+ bool exists(ID channelId, Frame frame, const MidiEvent& event, const Map& target) const;
+ bool exists(ID channelId, Frame frame, const MidiEvent& event) const;
+
+ Action* findAction(Map& src, ID id);
+
+ /* updateMapPointers
+ Updates all prev/next actions pointers into the action map. This is required
+ after an action has been recorded, since pushing back new actions in a Action
+ vector makes it reallocating the existing ones. */
+
+ void updateMapPointers(Map& src);
+
+ /* optimize
+ Removes frames without actions. */
+
+ void optimize(Map& map);
+
+ void removeIf(std::function<bool(const Action&)> f);
+
+ model::Model& m_model;
+
+ //TODO - move to actionManager
+ IdManager m_actionId;
+};
+} // namespace giada::m
+
+#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "audioBuffer.h"
-#include <algorithm>
-#include <cassert>
-
-namespace giada::m
-{
-AudioBuffer::AudioBuffer()
-: m_data(nullptr)
-, m_size(0)
-, m_channels(0)
-, m_viewing(false)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(Frame size, int channels)
-: AudioBuffer()
-{
- alloc(size, channels);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(float* data, Frame size, int channels)
-: m_data(data)
-, m_size(size)
-, m_channels(channels)
-, m_viewing(true)
-{
- assert(channels <= NUM_CHANS);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(const AudioBuffer& o)
-{
- copy(o);
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::AudioBuffer(AudioBuffer&& o)
-{
- move(std::move(o));
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer::~AudioBuffer()
-{
- if (!m_viewing)
- free();
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer& AudioBuffer::operator=(const AudioBuffer& o)
-{
- if (this == &o)
- return *this;
- copy(o);
- return *this;
-}
-
-/* -------------------------------------------------------------------------- */
-
-AudioBuffer& AudioBuffer::operator=(AudioBuffer&& o)
-{
- if (this == &o)
- return *this;
- move(std::move(o));
- return *this;
-}
-
-/* -------------------------------------------------------------------------- */
-
-float* AudioBuffer::operator[](Frame offset) const
-{
- assert(m_data != nullptr);
- assert(offset < m_size);
- return m_data + (offset * m_channels);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::clear(Frame a, Frame b)
-{
- if (m_data == nullptr)
- return;
- if (b == -1)
- b = m_size;
- std::fill_n(m_data + (a * m_channels), (b - a) * m_channels, 0.0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame AudioBuffer::countFrames() const { return m_size; }
-int AudioBuffer::countSamples() const { return m_size * m_channels; }
-int AudioBuffer::countChannels() const { return m_channels; }
-bool AudioBuffer::isAllocd() const { return m_data != nullptr; }
-
-/* -------------------------------------------------------------------------- */
-
-float AudioBuffer::getPeak() const
-{
- float peak = 0.0f;
- for (int i = 0; i < countSamples(); i++)
- peak = std::max(peak, m_data[i]);
- return peak;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::alloc(Frame size, int channels)
-{
- assert(channels <= NUM_CHANS);
-
- free();
- m_size = size;
- m_channels = channels;
- m_data = new float[m_size * m_channels];
- clear();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::free()
-{
- if (m_data == nullptr)
- return;
- delete[] m_data;
- m_data = nullptr;
- m_size = 0;
- m_channels = 0;
- m_viewing = false;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::sum(const AudioBuffer& b, Frame framesToCopy, Frame srcOffset,
- Frame destOffset, float gain, Pan pan)
-{
- copyData<Operation::SUM>(b, framesToCopy, srcOffset, destOffset, gain, pan);
-}
-
-void AudioBuffer::set(const AudioBuffer& b, Frame framesToCopy, Frame srcOffset,
- Frame destOffset, float gain, Pan pan)
-{
- copyData<Operation::SET>(b, framesToCopy, srcOffset, destOffset, gain, pan);
-}
-
-void AudioBuffer::sum(const AudioBuffer& b, float gain, Pan pan)
-{
- copyData<Operation::SUM>(b, -1, 0, 0, gain, pan);
-}
-
-void AudioBuffer::set(const AudioBuffer& b, float gain, Pan pan)
-{
- copyData<Operation::SET>(b, -1, 0, 0, gain, pan);
-}
-
-/* -------------------------------------------------------------------------- */
-
-template <AudioBuffer::Operation O>
-void AudioBuffer::copyData(const AudioBuffer& b, Frame framesToCopy,
- Frame srcOffset, Frame destOffset, float gain, Pan pan)
-{
- const int srcChannels = b.countChannels();
- const int destChannels = countChannels();
- const bool sameChannels = srcChannels == destChannels;
-
- assert(m_data != nullptr);
- assert(destOffset >= 0 && destOffset < m_size);
- assert(srcChannels <= destChannels);
-
- /* Make sure the amount of frames to copy lies within the current buffer
- size. */
-
- framesToCopy = framesToCopy == -1 ? b.countFrames() : framesToCopy;
- framesToCopy = std::min(framesToCopy, m_size - destOffset);
-
- /* Case 1) source has less channels than this one: brutally spread source's
- channel 0 over this one (TODO - maybe mixdown source channels first?)
- Case 2) source has same amount of channels: copy them 1:1. */
-
- for (int destF = 0, srcF = srcOffset; destF < framesToCopy && destF < b.countFrames(); destF++, srcF++)
- {
- for (int ch = 0; ch < destChannels; ch++)
- {
- if constexpr (O == Operation::SUM)
- sum(destF + destOffset, ch, b[srcF][sameChannels ? ch : 0] * gain * pan[ch]);
- else
- set(destF + destOffset, ch, b[srcF][sameChannels ? ch : 0] * gain * pan[ch]);
- }
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::applyGain(float g)
-{
- for (int i = 0; i < countSamples(); i++)
- m_data[i] *= g;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::sum(Frame f, int channel, float val) { (*this)[f][channel] += val; }
-void AudioBuffer::set(Frame f, int channel, float val) { (*this)[f][channel] = val; }
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::move(AudioBuffer&& o)
-{
- assert(o.countChannels() <= NUM_CHANS);
-
- m_data = o.m_data;
- m_size = o.m_size;
- m_channels = o.m_channels;
- m_viewing = o.m_viewing;
-
- o.m_data = nullptr;
- o.m_size = 0;
- o.m_channels = 0;
- o.m_viewing = false;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void AudioBuffer::copy(const AudioBuffer& o)
-{
- m_data = new float[o.m_size * o.m_channels];
- m_size = o.m_size;
- m_channels = o.m_channels;
- m_viewing = o.m_viewing;
-
- std::copy(o.m_data, o.m_data + (o.m_size * o.m_channels), m_data);
-}
-
-/* -------------------------------------------------------------------------- */
-
-template void AudioBuffer::copyData<AudioBuffer::Operation::SUM>(const AudioBuffer&, Frame, Frame, Frame, float, Pan);
-template void AudioBuffer::copyData<AudioBuffer::Operation::SET>(const AudioBuffer&, Frame, Frame, Frame, float, Pan);
-} // namespace giada::m
\ No newline at end of file
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_AUDIO_BUFFER_H
-#define G_AUDIO_BUFFER_H
-
-#include "core/types.h"
-#include <array>
-
-namespace giada::m
-{
-/* AudioBuffer
-A class that holds a buffer filled with audio data. NOTE: currently it only
-supports 2 channels (stereo). Give it a mono stream and it will convert it to
-stereo. Give it a multichannel stream and it will throw an assertion. */
-
-class AudioBuffer
-{
-public:
- static constexpr int NUM_CHANS = 2;
-
- using Pan = std::array<float, NUM_CHANS>;
-
- /* AudioBuffer (1)
- Creates an empty (and invalid) audio buffer. */
-
- AudioBuffer();
-
- /* AudioBuffer (2)
- Creates an audio buffer and allocates memory for size * channels frames. */
-
- AudioBuffer(Frame size, int channels);
-
- /* AudioBuffer (3)
- Creates an audio buffer out of a raw pointer. AudioBuffer created this way
- is instructed not to free the owned data on destruction. */
-
- AudioBuffer(float* data, Frame size, int channels);
-
- /* AudioBuffer(const AudioBuffer&)
- Copy constructor. */
-
- AudioBuffer(const AudioBuffer& o);
-
- /* AudioBuffer(AudioBuffer&&)
- Move constructor. */
-
- AudioBuffer(AudioBuffer&& o);
-
- /* ~AudioBuffer
- Destructor. */
-
- ~AudioBuffer();
-
- /* operator = (const AudioBuffer& o)
- Copy assignment operator. */
-
- AudioBuffer& operator=(const AudioBuffer& o);
-
- /* operator = (AudioBuffer&& o)
- Move assignment operator. */
-
- AudioBuffer& operator=(AudioBuffer&& o);
-
- /* operator []
- Given a frame 'offset', returns a pointer to it. This is useful for digging
- inside a frame, i.e. parsing each channel. How to use it:
-
- for (int k=0; k<buffer->countFrames(), k++)
- for (int i=0; i<buffer->countChannels(); i++)
- ... buffer[k][i] ...
-
- Also note that buffer[0] will give you a pointer to the whole internal data
- array. */
-
- float* operator[](int offset) const;
-
- Frame countFrames() const;
- int countSamples() const;
- int countChannels() const;
- bool isAllocd() const;
-
- /* getPeak
- Returns the highest value from any channel. */
-
- float getPeak() const;
-
- void alloc(Frame size, int channels);
- void free();
-
- /* sum, set (1)
- Merges (sum) or copies (set) 'framesToCopy' frames of buffer 'b' onto this
- one. If 'framesToCopy' is -1 the whole buffer will be copied. If 'b' has
- less channels than this one, they will be spread over the current ones.
- Buffer 'b' MUST NOT contain more channels than this one. */
-
- void sum(const AudioBuffer& b, Frame framesToCopy = -1, Frame srcOffset = 0,
- Frame destOffset = 0, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
- void set(const AudioBuffer& b, Frame framesToCopy = -1, Frame srcOffset = 0,
- Frame destOffset = 0, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-
- /* sum, set (2)
- Same as sum, set (1) without boundaries or offsets: it just copies as much
- as possibile. */
-
- void sum(const AudioBuffer& b, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
- void set(const AudioBuffer& b, float gain = 1.0f, Pan pan = {1.0f, 1.0f});
-
- /* clear
- Clears the internal data by setting all bytes to 0.0f. Optional parameters
- 'a' and 'b' set the range. */
-
- void clear(Frame a = 0, Frame b = -1);
-
- void applyGain(float g);
-
-private:
- enum class Operation
- {
- SUM,
- SET
- };
-
- template <Operation O = Operation::SET>
- void copyData(const AudioBuffer& b, Frame framesToCopy = -1,
- Frame srcOffset = 0, Frame destOffset = 0, float gain = 1.0f,
- Pan pan = {1.0f, 1.0f});
-
- void move(AudioBuffer&& o);
- void copy(const AudioBuffer& o);
- void sum(Frame f, int channel, float val);
- void set(Frame f, int channel, float val);
-
- float* m_data;
- Frame m_size;
- int m_channels;
- bool m_viewing;
-};
-} // namespace giada::m
-
-#endif
\ No newline at end of file
#include "audioReceiver.h"
#include "core/channels/channel.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
-namespace giada::m::audioReceiver
+namespace giada::m
{
-Data::Data(const patch::Channel& p)
+AudioReceiver::AudioReceiver(const Patch::Channel& p)
: inputMonitor(p.inputMonitor)
, overdubProtection(p.overdubProtection)
{
/* -------------------------------------------------------------------------- */
-void render(const channel::Data& ch, const AudioBuffer& in)
+void AudioReceiver::render(const Channel& ch, const mcl::AudioBuffer& in) const
{
/* If armed and input monitor is on, copy input buffer to channel buffer:
this enables the input monitoring. The channel buffer will be overwritten
later on by pluginHost::processStack, so that you would record "clean" audio
(i.e. not plugin-processed). */
- if (ch.armed && ch.audioReceiver->inputMonitor)
- ch.buffer->audio.set(in, /*gain=*/1.0f); // add, don't overwrite
+ if (ch.armed && inputMonitor)
+ ch.shared->audioBuffer.set(in, /*gain=*/1.0f); // add, don't overwrite
}
-} // namespace giada::m::audioReceiver
\ No newline at end of file
+} // namespace giada::m
#ifndef G_CHANNEL_AUDIO_RECEIVER_H
#define G_CHANNEL_AUDIO_RECEIVER_H
-namespace giada::m
+#include "core/patch.h"
+
+namespace mcl
{
class AudioBuffer;
}
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::patch
-{
-struct Channel;
-}
-namespace giada::m::audioReceiver
+
+namespace giada::m
{
-struct Data
+class Channel;
+class AudioReceiver final
{
- Data() = default;
- Data(const patch::Channel& p);
- Data(const Data& o) = default;
+public:
+ AudioReceiver() = default;
+ AudioReceiver(const Patch::Channel& p);
+ AudioReceiver(const AudioReceiver& o) = default;
+
+ void render(const Channel& ch, const mcl::AudioBuffer& in) const;
bool inputMonitor;
bool overdubProtection;
};
-
-void render(const channel::Data& ch, const AudioBuffer& in);
-} // namespace giada::m::audioReceiver
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "channel.h"
+#include "core/channels/channel.h"
+#include "core/actions/actionRecorder.h"
+#include "core/channels/sampleAdvancer.h"
+#include "core/conf.h"
+#include "core/engine.h"
+#include "core/midiMapper.h"
#include "core/mixerHandler.h"
+#include "core/model/model.h"
#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
+#include "core/recorder.h"
#include <cassert>
-namespace giada::m::channel
+extern giada::m::Engine g_engine;
+
+namespace giada::m
{
namespace
{
-AudioBuffer::Pan calcPanning_(float pan)
+mcl::AudioBuffer::Pan calcPanning_(float pan)
{
/* TODO - precompute the AudioBuffer::Pan when pan value changes instead of
building it on the fly. */
return {1.0f, 1.0f};
return {1.0f - pan, pan};
}
-
-/* -------------------------------------------------------------------------- */
-
-void react_(Data& d, const eventDispatcher::Event& e)
-{
- switch (e.type)
- {
- case eventDispatcher::EventType::CHANNEL_VOLUME:
- d.volume = std::get<float>(e.data);
- break;
-
- case eventDispatcher::EventType::CHANNEL_PAN:
- d.pan = std::get<float>(e.data);
- break;
-
- case eventDispatcher::EventType::CHANNEL_MUTE:
- d.mute = !d.mute;
- break;
-
- case eventDispatcher::EventType::CHANNEL_TOGGLE_ARM:
- d.armed = !d.armed;
- break;
-
- case eventDispatcher::EventType::CHANNEL_SOLO:
- d.solo = !d.solo;
- m::mh::updateSoloCount();
- break;
-
- default:
- break;
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderMasterOut_(const Data& d, AudioBuffer& out)
-{
- d.buffer->audio.set(out, /*gain=*/1.0f);
-#ifdef WITH_VST
- if (d.plugins.size() > 0)
- pluginHost::processStack(d.buffer->audio, d.plugins, nullptr);
-#endif
- out.set(d.buffer->audio, d.volume);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderMasterIn_(const Data& d, AudioBuffer& in)
-{
-#ifdef WITH_VST
- if (d.plugins.size() > 0)
- pluginHost::processStack(in, d.plugins, nullptr);
-#endif
-}
-
-/* -------------------------------------------------------------------------- */
-
-void renderChannel_(const Data& d, AudioBuffer& out, AudioBuffer& in, bool audible)
-{
- d.buffer->audio.clear();
-
- if (d.samplePlayer)
- samplePlayer::render(d);
- if (d.audioReceiver)
- audioReceiver::render(d, in);
-
- /* If MidiReceiver exists, let it process the plug-in stack, as it can
- contain plug-ins that take MIDI events (i.e. synths). Otherwise process the
- plug-in stack internally with no MIDI events. */
-
-#ifdef WITH_VST
- if (d.midiReceiver)
- midiReceiver::render(d);
- else if (d.plugins.size() > 0)
- pluginHost::processStack(d.buffer->audio, d.plugins, nullptr);
-#endif
-
- if (audible)
- out.sum(d.buffer->audio, d.volume * d.volume_i, calcPanning_(d.pan));
-}
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Buffer::Buffer(Frame bufferSize)
-: audio(bufferSize, G_MAX_IO_CHANS)
+Channel::Shared::Shared(Frame bufferSize)
+: audioBuffer(bufferSize, G_MAX_IO_CHANS)
{
}
/* -------------------------------------------------------------------------- */
-Data::Data(ChannelType type, ID id, ID columnId, State& state, Buffer& buffer)
-: state(&state)
-, buffer(&buffer)
+Channel::Channel(ChannelType type, ID id, ID columnId, Shared& s)
+: shared(&s)
, id(id)
, type(type)
, columnId(columnId)
, volume(G_DEFAULT_VOL)
, volume_i(G_DEFAULT_VOL)
, pan(G_DEFAULT_PAN)
-, mute(false)
-, solo(false)
, armed(false)
, key(0)
, hasActions(false)
, height(G_GUI_UNIT)
+, midiLighter(g_engine.midiMapper)
+, m_mute(false)
+, m_solo(false)
{
switch (type)
{
case ChannelType::SAMPLE:
- samplePlayer.emplace(&state.resampler.value());
- sampleReactor.emplace(id);
+ samplePlayer.emplace(&(shared->resampler.value()));
+ sampleAdvancer.emplace();
+ sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
audioReceiver.emplace();
- sampleActionRecorder.emplace();
+ sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
break;
case ChannelType::PREVIEW:
- samplePlayer.emplace(&state.resampler.value());
- sampleReactor.emplace(id);
+ samplePlayer.emplace(&(shared->resampler.value()));
+ sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
break;
case ChannelType::MIDI:
midiController.emplace();
- midiSender.emplace();
- midiActionRecorder.emplace();
+ midiSender.emplace(g_engine.kernelMidi);
+ midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
#ifdef WITH_VST
midiReceiver.emplace();
#endif
default:
break;
}
+
+ initCallbacks();
}
/* -------------------------------------------------------------------------- */
-Data::Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio)
-: state(&state)
-, buffer(&buffer)
+Channel::Channel(const Patch::Channel& p, Shared& s, float samplerateRatio, Wave* wave)
+: shared(&s)
, id(p.id)
, type(p.type)
, columnId(p.columnId)
, volume(p.volume)
, volume_i(G_DEFAULT_VOL)
, pan(p.pan)
-, mute(p.mute)
-, solo(p.solo)
, armed(p.armed)
, key(p.key)
, hasActions(p.hasActions)
, name(p.name)
, height(p.height)
#ifdef WITH_VST
-, plugins(pluginManager::hydratePlugins(p.pluginIds))
+, plugins(g_engine.pluginManager.hydratePlugins(p.pluginIds, g_engine.model)) // TODO move outside, as constructor parameter
#endif
, midiLearner(p)
+, midiLighter(g_engine.midiMapper, p)
+, m_mute(p.mute)
+, m_solo(p.solo)
{
- state.readActions.store(p.readActions);
- state.recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF);
+ shared->readActions.store(p.readActions);
+ shared->recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF);
switch (type)
{
case ChannelType::SAMPLE:
- samplePlayer.emplace(p, samplerateRatio, &state.resampler.value());
- sampleReactor.emplace(id);
+ samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), wave);
+ sampleAdvancer.emplace();
+ sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
audioReceiver.emplace(p);
- sampleActionRecorder.emplace();
+ sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
break;
case ChannelType::PREVIEW:
- samplePlayer.emplace(p, samplerateRatio, &state.resampler.value());
- sampleReactor.emplace(id);
+ samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), nullptr);
+ sampleReactor.emplace(id, g_engine.sequencer, g_engine.model);
break;
case ChannelType::MIDI:
midiController.emplace();
- midiSender.emplace(p);
- midiActionRecorder.emplace();
+ midiSender.emplace(p, g_engine.kernelMidi);
+ midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer);
#ifdef WITH_VST
midiReceiver.emplace();
#endif
default:
break;
}
+
+ initCallbacks();
}
/* -------------------------------------------------------------------------- */
-bool Data::operator==(const Data& other)
+Channel::Channel(const Channel& other)
+: midiLighter(g_engine.midiMapper)
+{
+ *this = other;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Channel& Channel::operator=(const Channel& other)
+{
+ if (this == &other)
+ return *this;
+
+ shared = other.shared;
+ id = other.id;
+ type = other.type;
+ columnId = other.columnId;
+ volume = other.volume;
+ volume_i = other.volume_i;
+ pan = other.pan;
+ m_mute = other.m_mute;
+ m_solo = other.m_solo;
+ armed = other.armed;
+ key = other.key;
+ hasActions = other.hasActions;
+ name = other.name;
+ height = other.height;
+#ifdef WITH_VST
+ plugins = other.plugins;
+#endif
+
+ midiLearner = other.midiLearner;
+ midiLighter = other.midiLighter;
+ samplePlayer = other.samplePlayer;
+ sampleAdvancer = other.sampleAdvancer;
+ sampleReactor = other.sampleReactor;
+ audioReceiver = other.audioReceiver;
+ midiController = other.midiController;
+#ifdef WITH_VST
+ midiReceiver = other.midiReceiver;
+#endif
+ midiSender = other.midiSender;
+ sampleActionRecorder = other.sampleActionRecorder;
+ midiActionRecorder = other.midiActionRecorder;
+
+ initCallbacks();
+
+ return *this;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Channel::operator==(const Channel& other)
{
return id == other.id;
}
/* -------------------------------------------------------------------------- */
-bool Data::isInternal() const
+bool Channel::isInternal() const
{
return type == ChannelType::MASTER || type == ChannelType::PREVIEW;
}
-bool Data::isMuted() const
+bool Channel::isMuted() const
{
/* Internals can't be muted. */
- return !isInternal() && mute;
+ return !isInternal() && m_mute;
}
-bool Data::canInputRec() const
+bool Channel::isSoloed() const
+{
+ return m_solo;
+}
+
+bool Channel::canInputRec() const
{
if (type != ChannelType::SAMPLE)
return false;
return armed && canOverdub;
}
-bool Data::canActionRec() const
+bool Channel::canActionRec() const
{
return hasWave() && !samplePlayer->isAnyLoopMode();
}
-bool Data::hasWave() const
+bool Channel::hasWave() const
{
return samplePlayer && samplePlayer->hasWave();
}
-bool Data::isPlaying() const
+bool Channel::isPlaying() const
{
- ChannelStatus s = state->playStatus.load();
+ ChannelStatus s = shared->playStatus.load();
return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
}
-bool Data::isReadingActions() const
+bool Channel::isReadingActions() const
{
- ChannelStatus s = state->recStatus.load();
+ ChannelStatus s = shared->recStatus.load();
return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
}
/* -------------------------------------------------------------------------- */
-void advance(const Data& d, const sequencer::EventBuffer& events)
+void Channel::setMute(bool v)
+{
+ if (m_mute != v)
+ midiLighter.sendMute(v);
+ m_mute = v;
+}
+
+void Channel::setSolo(bool v)
+{
+ if (m_solo != v)
+ midiLighter.sendSolo(v);
+ m_solo = v;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::initCallbacks()
{
- for (const sequencer::Event& e : events)
+ shared->playStatus.onChange = [this](ChannelStatus status) {
+ midiLighter.sendStatus(status, g_engine.mixer.isChannelAudible(*this));
+ };
+
+ if (samplePlayer)
{
- if (d.midiController)
- midiController::advance(d, e);
- if (d.samplePlayer)
- samplePlayer::advance(d, e);
- if (d.midiSender)
- midiSender::advance(d, e);
+ samplePlayer->onLastFrame = [this]() {
+ sampleAdvancer->onLastFrame(*this, g_engine.sequencer.isRunning());
+ };
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::advance(const Sequencer::EventBuffer& events) const
+{
+ for (const Sequencer::Event& e : events)
+ {
+ if (midiController)
+ midiController->advance(*this, e);
+ if (samplePlayer)
+ sampleAdvancer->advance(*this, e);
+ if (midiSender)
+ midiSender->advance(*this, e);
#ifdef WITH_VST
- if (d.midiReceiver)
- midiReceiver::advance(d, e);
+ if (midiReceiver)
+ midiReceiver->advance(*this, e);
#endif
}
}
/* -------------------------------------------------------------------------- */
-void react(Data& d, const eventDispatcher::EventBuffer& events, bool audible)
+void Channel::react(const EventDispatcher::EventBuffer& events)
{
- for (const eventDispatcher::Event& e : events)
+ for (const EventDispatcher::Event& e : events)
{
- if (e.channelId > 0 && e.channelId != d.id)
+ if (e.channelId > 0 && e.channelId != id)
continue;
- react_(d, e);
- midiLighter::react(d, e, audible);
-
- if (d.midiController)
- midiController::react(d, e);
- if (d.midiSender)
- midiSender::react(d, e);
- if (d.samplePlayer)
- samplePlayer::react(d, e);
- if (d.midiActionRecorder)
- midiActionRecorder::react(d, e);
- if (d.sampleActionRecorder)
- sampleActionRecorder::react(d, e);
- if (d.sampleReactor)
- sampleReactor::react(d, e);
+ react(e);
+ if (midiController)
+ midiController->react(*this, e);
+ if (midiSender)
+ midiSender->react(*this, e);
+ if (samplePlayer)
+ samplePlayer->react(e);
+ if (midiActionRecorder)
+ midiActionRecorder->react(*this, e, g_engine.recorder.canRecordActions());
+ if (sampleActionRecorder)
+ sampleActionRecorder->react(*this, e, g_engine.conf.data.treatRecsAsLoops,
+ g_engine.sequencer.isRunning(), g_engine.recorder.canRecordActions());
+ if (sampleReactor)
+ sampleReactor->react(*this, e, g_engine.sequencer, g_engine.conf.data);
#ifdef WITH_VST
- if (d.midiReceiver)
- midiReceiver::react(d, e);
+ if (midiReceiver)
+ midiReceiver->react(*this, e);
#endif
}
}
/* -------------------------------------------------------------------------- */
-void render(const Data& d, AudioBuffer* out, AudioBuffer* in, bool audible)
+void Channel::react(const EventDispatcher::Event& e)
+{
+ switch (e.type)
+ {
+ case EventDispatcher::EventType::CHANNEL_VOLUME:
+ volume = std::get<float>(e.data);
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_PAN:
+ pan = std::get<float>(e.data);
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_MUTE:
+ setMute(!isMuted());
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_TOGGLE_ARM:
+ armed = !armed;
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_SOLO:
+ setSolo(!isSoloed());
+ g_engine.mixerHandler.updateSoloCount();
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::render(mcl::AudioBuffer* out, mcl::AudioBuffer* in, bool audible) const
{
- if (d.id == mixer::MASTER_OUT_CHANNEL_ID)
- renderMasterOut_(d, *out);
- else if (d.id == mixer::MASTER_IN_CHANNEL_ID)
- renderMasterIn_(d, *in);
+ if (id == Mixer::MASTER_OUT_CHANNEL_ID)
+ renderMasterOut(*out);
+#ifdef WITH_VST
+ else if (id == Mixer::MASTER_IN_CHANNEL_ID)
+ renderMasterIn(*in);
+#endif
else
- renderChannel_(d, *out, *in, audible);
+ renderChannel(*out, *in, audible);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::renderMasterOut(mcl::AudioBuffer& out) const
+{
+ shared->audioBuffer.set(out, /*gain=*/1.0f);
+#ifdef WITH_VST
+ if (plugins.size() > 0)
+ g_engine.pluginHost.processStack(shared->audioBuffer, plugins, nullptr);
+#endif
+ out.set(shared->audioBuffer, volume);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Channel::renderMasterIn(mcl::AudioBuffer& in) const
+{
+ if (plugins.size() > 0)
+ g_engine.pluginHost.processStack(in, plugins, nullptr);
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void Channel::renderChannel(mcl::AudioBuffer& out, mcl::AudioBuffer& in, bool audible) const
+{
+ shared->audioBuffer.clear();
+
+ if (samplePlayer)
+ samplePlayer->render(*this);
+ if (audioReceiver)
+ audioReceiver->render(*this, in);
+
+ /* If MidiReceiver exists, let it process the plug-in stack, as it can
+ contain plug-ins that take MIDI events (i.e. synths). Otherwise process the
+ plug-in stack internally with no MIDI events. */
+
+#ifdef WITH_VST
+ if (midiReceiver)
+ midiReceiver->render(*this, g_engine.pluginHost);
+ else if (plugins.size() > 0)
+ g_engine.pluginHost.processStack(shared->audioBuffer, plugins, nullptr);
+#endif
+
+ if (audible)
+ out.sum(shared->audioBuffer, volume * volume_i, calcPanning_(pan));
}
-} // namespace giada::m::channel
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#ifdef WITH_VST
#include "deps/juce-config.h"
#endif
-#include "core/audioBuffer.h"
#include "core/channels/audioReceiver.h"
#include "core/channels/midiActionRecorder.h"
#include "core/channels/midiController.h"
#include "core/channels/midiLighter.h"
#include "core/channels/midiSender.h"
#include "core/channels/sampleActionRecorder.h"
+#include "core/channels/sampleAdvancer.h"
#include "core/channels/samplePlayer.h"
+#include "core/channels/sampleReactor.h"
#include "core/const.h"
#include "core/eventDispatcher.h"
+#include "core/midiEvent.h"
#include "core/mixer.h"
+#include "core/patch.h"
+#include "core/queue.h"
#include "core/resampler.h"
#include "core/sequencer.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#ifdef WITH_VST
#include "core/channels/midiReceiver.h"
#endif
namespace giada::m
{
class Plugin;
-}
-namespace giada::m::channel
+class Channel final
{
-struct State
-{
- WeakAtomic<Frame> tracker = 0;
- WeakAtomic<ChannelStatus> playStatus = ChannelStatus::OFF;
- WeakAtomic<ChannelStatus> recStatus = ChannelStatus::OFF;
- WeakAtomic<bool> readActions = false;
- bool rewinding = false;
- Frame offset = 0;
-
- /* Optional resampler for sample-based channels. Unfortunately a Resampler
+public:
+ struct Shared
+ {
+ Shared(Frame bufferSize);
+
+ mcl::AudioBuffer audioBuffer;
+#ifdef WITH_VST
+ juce::MidiBuffer midiBuffer;
+ Queue<MidiEvent, 32> midiQueue;
+#endif
+
+ WeakAtomic<Frame> tracker = 0;
+ WeakAtomic<ChannelStatus> playStatus = ChannelStatus::OFF;
+ WeakAtomic<ChannelStatus> recStatus = ChannelStatus::OFF;
+ WeakAtomic<bool> readActions = false;
+ bool rewinding = false;
+ Frame offset = 0;
+
+ /* Optional resampler for sample-based channels. Unfortunately a Resampler
object (based on libsamplerate) doesn't like to get copied while rendering
audio, so can't live inside WaveReader object (which is copied on model
changes by the Swapper mechanism). Let's put it in the shared state here. */
- std::optional<Resampler> resampler = {};
-};
+ std::optional<Resampler> resampler = {};
+ };
-struct Buffer
-{
- Buffer(Frame bufferSize);
+ Channel(ChannelType t, ID id, ID columnId, Shared&);
+ Channel(const Patch::Channel& p, Shared&, float samplerateRatio, Wave* w);
+ Channel(const Channel& o);
+ Channel(Channel&& o) = default;
- AudioBuffer audio;
-#ifdef WITH_VST
- juce::MidiBuffer midi;
-#endif
-};
+ Channel& operator=(const Channel&);
+ Channel& operator=(Channel&&) = default;
+ bool operator==(const Channel&);
-struct Data
-{
- Data(ChannelType t, ID id, ID columnId, State& state, Buffer& buffer);
- Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio);
- Data(const Data& o) = default;
- Data(Data&& o) = default;
- Data& operator=(const Data&) = default;
- Data& operator=(Data&&) = default;
+ /* advance
+ Advances internal state by processing static events (e.g. pre-recorded
+ actions or sequencer events) in the current block. */
+
+ void advance(const Sequencer::EventBuffer& e) const;
+
+ /* render
+ Renders audio data to I/O buffers. */
- bool operator==(const Data&);
+ void render(mcl::AudioBuffer* out, mcl::AudioBuffer* in, bool audible) const;
+
+ /* react
+ Reacts to live events coming from the EventDispatcher (human events) and
+ updates itself accordingly. */
+
+ void react(const EventDispatcher::EventBuffer& e);
bool isPlaying() const;
bool isReadingActions() const;
bool isInternal() const;
bool isMuted() const;
+ bool isSoloed() const;
bool canInputRec() const;
bool canActionRec() const;
bool hasWave() const;
- State* state;
- Buffer* buffer;
+ void setMute(bool);
+ void setSolo(bool);
+
+ Shared* shared;
ID id;
ChannelType type;
ID columnId;
float volume;
float volume_i; // Internal volume used for velocity-drives-volume mode on Sample Channels
float pan;
- bool mute;
- bool solo;
bool armed;
int key;
bool hasActions;
std::vector<Plugin*> plugins;
#endif
- midiLearner::Data midiLearner;
- midiLighter::Data midiLighter;
+ MidiLearner midiLearner;
+ MidiLighter<KernelMidi> midiLighter;
- std::optional<samplePlayer::Data> samplePlayer;
- std::optional<sampleReactor::Data> sampleReactor;
- std::optional<audioReceiver::Data> audioReceiver;
- std::optional<midiController::Data> midiController;
+ std::optional<SamplePlayer> samplePlayer;
+ std::optional<SampleAdvancer> sampleAdvancer;
+ std::optional<SampleReactor> sampleReactor;
+ std::optional<AudioReceiver> audioReceiver;
+ std::optional<MidiController> midiController;
#ifdef WITH_VST
- std::optional<midiReceiver::Data> midiReceiver;
+ std::optional<MidiReceiver> midiReceiver;
#endif
- std::optional<midiSender::Data> midiSender;
- std::optional<sampleActionRecorder::Data> sampleActionRecorder;
- std::optional<midiActionRecorder::Data> midiActionRecorder;
-};
-
-/* advance
-Advances internal state by processing static events (e.g. pre-recorded
-actions or sequencer events) in the current block. */
-
-void advance(const Data& d, const sequencer::EventBuffer& e);
+ std::optional<MidiSender> midiSender;
+ std::optional<SampleActionRecorder> sampleActionRecorder;
+ std::optional<MidiActionRecorder> midiActionRecorder;
-/* react
-Reacts to live events coming from the EventDispatcher (human events) and
-updates itself accordingly. */
+private:
+ void renderMasterOut(mcl::AudioBuffer&) const;
+ void renderMasterIn(mcl::AudioBuffer&) const;
+ void renderChannel(mcl::AudioBuffer& out, mcl::AudioBuffer& in, bool audible) const;
-void react(Data& d, const eventDispatcher::EventBuffer& e, bool audible);
+ void initCallbacks();
+ void react(const EventDispatcher::Event&);
-/* render
-Renders audio data to I/O buffers. */
-
-void render(const Data& d, AudioBuffer* out, AudioBuffer* in, bool audible);
-} // namespace giada::m::channel
+ bool m_mute;
+ bool m_solo;
+};
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "channelManager.h"
-#include "core/action.h"
+#include "core/channels/channelManager.h"
#include "core/channels/channel.h"
#include "core/channels/samplePlayer.h"
#include "core/conf.h"
#include "core/const.h"
-#include "core/idManager.h"
-#include "core/kernelAudio.h"
-#include "core/mixer.h"
#include "core/model/model.h"
#include "core/patch.h"
#include "core/plugins/plugin.h"
#include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
#include "core/wave.h"
-#include "core/waveManager.h"
-#include "utils/fs.h"
#include <cassert>
-namespace giada::m::channelManager
+namespace giada::m
{
-namespace
+ChannelManager::ChannelManager(const Conf::Data& c, model::Model& m)
+: m_conf(c)
+, m_model(m)
{
-IdManager channelId_;
-
-/* -------------------------------------------------------------------------- */
-
-channel::State& makeState_(ChannelType type)
-{
- std::unique_ptr<channel::State> state = std::make_unique<channel::State>();
-
- if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW)
- state->resampler = Resampler(static_cast<Resampler::Quality>(conf::conf.rsmpQuality), G_MAX_IO_CHANS);
-
- model::add(std::move(state));
- return model::back<channel::State>();
}
/* -------------------------------------------------------------------------- */
-channel::Buffer& makeBuffer_()
+ID ChannelManager::getNextId() const
{
- model::add(std::make_unique<channel::Buffer>(kernelAudio::getRealBufSize()));
- return model::back<channel::Buffer>();
+ return m_channelId.getNext();
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init()
+void ChannelManager::reset()
{
- channelId_ = IdManager();
+ m_channelId = IdManager();
}
/* -------------------------------------------------------------------------- */
-channel::Data create(ID channelId, ChannelType type, ID columnId)
+Channel ChannelManager::create(ID channelId, ChannelType type, ID columnId, int bufferSize)
{
- channel::Data out = channel::Data(type, channelId_.generate(channelId),
- columnId, makeState_(type), makeBuffer_());
+ Channel out = Channel(type, m_channelId.generate(channelId), columnId,
+ makeShared(type, bufferSize));
if (out.audioReceiver)
- out.audioReceiver->overdubProtection = conf::conf.overdubProtectionDefaultOn;
+ out.audioReceiver->overdubProtection = m_conf.overdubProtectionDefaultOn;
return out;
}
/* -------------------------------------------------------------------------- */
-channel::Data create(const channel::Data& o)
+Channel ChannelManager::create(const Channel& o, int bufferSize)
{
- channel::Data out = channel::Data(o);
+ Channel out = Channel(o);
- out.id = channelId_.generate();
- out.state = &makeState_(o.type);
- out.buffer = &makeBuffer_();
+ out.id = m_channelId.generate();
+ out.shared = &makeShared(o.type, bufferSize);
return out;
}
/* -------------------------------------------------------------------------- */
-channel::Data deserializeChannel(const patch::Channel& pch, float samplerateRatio)
+Channel ChannelManager::deserializeChannel(const Patch::Channel& pch, float samplerateRatio, int bufferSize)
{
- channelId_.set(pch.id);
- return channel::Data(pch, makeState_(pch.type), makeBuffer_(), samplerateRatio);
+ m_channelId.set(pch.id);
+ return Channel(pch, makeShared(pch.type, bufferSize), samplerateRatio, m_model.findShared<Wave>(pch.waveId));
}
/* -------------------------------------------------------------------------- */
-const patch::Channel serializeChannel(const channel::Data& c)
+const Patch::Channel ChannelManager::serializeChannel(const Channel& c)
{
- patch::Channel pc;
+ Patch::Channel pc;
#ifdef WITH_VST
for (const Plugin* p : c.plugins)
pc.height = c.height;
pc.name = c.name;
pc.key = c.key;
- pc.mute = c.mute;
- pc.solo = c.solo;
+ pc.mute = c.isMuted();
+ pc.solo = c.isSoloed();
pc.volume = c.volume;
pc.pan = c.pan;
pc.hasActions = c.hasActions;
- pc.readActions = c.state->readActions.load();
+ pc.readActions = c.shared->readActions.load();
pc.armed = c.armed;
pc.midiIn = c.midiLearner.enabled;
pc.midiInFilter = c.midiLearner.filter;
return pc;
}
-} // namespace giada::m::channelManager
+
+/* -------------------------------------------------------------------------- */
+
+Channel::Shared& ChannelManager::makeShared(ChannelType type, int bufferSize)
+{
+ std::unique_ptr<Channel::Shared> shared = std::make_unique<Channel::Shared>(bufferSize);
+
+ if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW)
+ shared->resampler = Resampler(static_cast<Resampler::Quality>(m_conf.rsmpQuality), G_MAX_IO_CHANS);
+
+ m_model.addShared(std::move(shared));
+ return m_model.backShared<Channel::Shared>();
+}
+} // namespace giada::m
#ifndef G_CHANNEL_MANAGER_H
#define G_CHANNEL_MANAGER_H
+#include "core/channels/channel.h"
+#include "core/conf.h"
+#include "core/idManager.h"
+#include "core/patch.h"
#include "core/types.h"
-namespace giada::m::channel
+namespace giada::m::model
{
-struct Data;
+class Model;
}
-namespace giada::m::conf
+
+namespace giada::m
{
-struct Conf;
-}
-namespace giada::m::patch
+class KernelAudio;
+class ChannelManager final
{
-struct Channel;
-}
-namespace giada::m::channelManager
-{
-/* init
-Initializes internal data. */
+public:
+ ChannelManager(const Conf::Data&, model::Model&);
+
+ /* getNextId
+ Returns the next channel ID that will be assigned to a new channel. */
+
+ ID getNextId() const;
+
+ /* reset
+ Resets internal ID generator. */
+
+ void reset();
+
+ /* create (1)
+ Creates a new channel. If channelId == 0 generates a new ID, reuse the one
+ passed in otherwise. */
+
+ Channel create(ID channelId, ChannelType type, ID columnId, int bufferSize);
-void init();
+ /* create (2)
+ Creates a new channel given an existing one (i.e. clone). */
-/* create (1)
-Creates a new channel. If channelId == 0 generates a new ID, reuse the one
-passed in otherwise. */
+ Channel create(const Channel& ch, int bufferSize);
-channel::Data create(ID channelId, ChannelType type, ID columnId);
+ /* (de)serializeWave
+ Creates a new channel given the patch raw data and vice versa. */
-/* create (2)
-Creates a new channel given an existing one (i.e. clone). */
+ Channel deserializeChannel(const Patch::Channel& c, float samplerateRatio, int bufferSize);
+ const Patch::Channel serializeChannel(const Channel& c);
-channel::Data create(const channel::Data& ch);
+private:
+ Channel::Shared& makeShared(ChannelType type, int bufferSize);
-/* (de)serializeWave
-Creates a new channel given the patch raw data and vice versa. */
+ IdManager m_channelId;
-channel::Data deserializeChannel(const patch::Channel& c, float samplerateRatio);
-const patch::Channel serializeChannel(const channel::Data& c);
-} // namespace giada::m::channelManager
+ const Conf::Data& m_conf;
+ model::Model& m_model;
+};
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "midiActionRecorder.h"
-#include "core/action.h"
+#include "core/channels/midiActionRecorder.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/eventDispatcher.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
-#include "core/recorderHandler.h"
-#include <cassert>
+#include "core/sequencer.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
-namespace giada::m::midiActionRecorder
+namespace giada::m
{
-namespace
+MidiActionRecorder::MidiActionRecorder(ActionRecorder& a, Sequencer& s)
+: m_actionRecorder(&a)
+, m_sequencer(&s)
{
-void record_(channel::Data& ch, const MidiEvent& e)
-{
- MidiEvent flat(e);
- flat.setChannel(0);
- recorderHandler::liveRec(ch.id, flat, clock::quantize(clock::getCurrentFrame()));
- ch.hasActions = true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canRecord_()
-{
- return recManager::isRecordingAction() &&
- clock::isRunning() &&
- !recManager::isRecordingInput();
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void MidiActionRecorder::react(Channel& ch, const EventDispatcher::Event& e, bool canRecordActions)
{
- if (e.type == eventDispatcher::EventType::MIDI && canRecord_())
- record_(ch, std::get<Action>(e.data).event);
+ if (e.type == EventDispatcher::EventType::MIDI && canRecordActions)
+ {
+ MidiEvent flat(std::get<Action>(e.data).event);
+ flat.setChannel(0);
+ m_actionRecorder->liveRec(ch.id, flat, m_sequencer->getCurrentFrameQuantized());
+ ch.hasActions = true;
+ }
}
-} // namespace giada::m::midiActionRecorder
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_MIDI_ACTION_RECORDER_H
#define G_CHANNEL_MIDI_ACTION_RECORDER_H
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::midiActionRecorder
+#include "core/eventDispatcher.h"
+
+namespace giada::m
{
-struct Data
+class ActionRecorder;
+class Sequencer;
+class Channel;
+class MidiActionRecorder final
{
+public:
+ MidiActionRecorder(ActionRecorder&, Sequencer&);
+
+ void react(Channel&, const EventDispatcher::Event&, bool canRecordActions);
+
+private:
+ ActionRecorder* m_actionRecorder;
+ Sequencer* m_sequencer;
};
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::midiActionRecorder
+} // namespace giada::m
#endif
#include "core/conf.h"
#include <cassert>
-namespace giada::m::midiController
+namespace giada::m
{
-namespace
+void MidiController::react(Channel& ch, const EventDispatcher::Event& e) const
{
-ChannelStatus onFirstBeat_(const channel::Data& ch)
+ switch (e.type)
+ {
+ case EventDispatcher::EventType::KEY_PRESS:
+ ch.shared->playStatus.store(press(ch));
+ break;
+
+ case EventDispatcher::EventType::KEY_KILL:
+ case EventDispatcher::EventType::SEQUENCER_STOP:
+ ch.shared->playStatus.store(ChannelStatus::OFF);
+ break;
+
+ case EventDispatcher::EventType::SEQUENCER_REWIND:
+ ch.shared->playStatus.store(onFirstBeat(ch));
+
+ default:
+ break;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiController::advance(const Channel& ch, const Sequencer::Event& e) const
+{
+ if (e.type == Sequencer::EventType::FIRST_BEAT)
+ ch.shared->playStatus.store(onFirstBeat(ch));
+}
+
+/* -------------------------------------------------------------------------- */
+
+ChannelStatus MidiController::onFirstBeat(const Channel& ch) const
{
- ChannelStatus playStatus = ch.state->playStatus.load();
+ ChannelStatus playStatus = ch.shared->playStatus.load();
if (playStatus == ChannelStatus::ENDING)
playStatus = ChannelStatus::OFF;
/* -------------------------------------------------------------------------- */
-ChannelStatus press_(const channel::Data& ch)
+ChannelStatus MidiController::press(const Channel& ch) const
{
- ChannelStatus playStatus = ch.state->playStatus.load();
+ ChannelStatus playStatus = ch.shared->playStatus.load();
switch (playStatus)
{
return playStatus;
}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void react(channel::Data& ch, const eventDispatcher::Event& e)
-{
- switch (e.type)
- {
-
- case eventDispatcher::EventType::KEY_PRESS:
- ch.state->playStatus.store(press_(ch));
- break;
-
- case eventDispatcher::EventType::KEY_KILL:
- case eventDispatcher::EventType::SEQUENCER_STOP:
- ch.state->playStatus.store(ChannelStatus::OFF);
- break;
-
- case eventDispatcher::EventType::SEQUENCER_REWIND:
- ch.state->playStatus.store(onFirstBeat_(ch));
-
- default:
- break;
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
-{
- if (e.type == sequencer::EventType::FIRST_BEAT)
- ch.state->playStatus.store(onFirstBeat_(ch));
-}
-} // namespace giada::m::midiController
+} // namespace giada::m
#ifndef G_CHANNEL_MIDI_CONTROLLER_H
#define G_CHANNEL_MIDI_CONTROLLER_H
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiController
+#include "core/sequencer.h"
+
+namespace giada::m
{
-struct Data
+class Channel;
+class MidiController final
{
-};
+public:
+ void advance(const Channel& ch, const Sequencer::Event& e) const;
+ void react(Channel& ch, const EventDispatcher::Event& e) const;
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::midiController
+private:
+ ChannelStatus onFirstBeat(const Channel& ch) const;
+ ChannelStatus press(const Channel& ch) const;
+};
+} // namespace giada::m
#endif
#include "midiLearner.h"
#include "core/patch.h"
-namespace giada::m::midiLearner
+namespace giada::m
{
-Data::Data(const patch::Channel& p)
+MidiLearner::MidiLearner()
+: enabled(false)
+, filter(-1)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+MidiLearner::MidiLearner(const Patch::Channel& p)
: enabled(p.midiIn)
, filter(p.midiInFilter)
, keyPress(p.midiInKeyPress)
/* -------------------------------------------------------------------------- */
-bool Data::isAllowed(int c) const
+bool MidiLearner::isAllowed(int c) const
{
return enabled && (filter == -1 || filter == c);
}
-} // namespace giada::m::midiLearner
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#define G_CHANNEL_MIDI_LEARNER_H
#include "core/midiLearnParam.h"
+#include "core/patch.h"
-namespace giada::m::patch
+namespace giada::m
{
-struct Channel;
-}
-namespace giada::m::midiLearner
+class MidiLearner final
{
-struct Data
-{
- Data() = default;
- Data(const patch::Channel&);
- Data(const Data&) = default;
+public:
+ MidiLearner();
+ MidiLearner(const Patch::Channel&);
+ MidiLearner(const MidiLearner&) = default;
/* isAllowed
Tells whether the MIDI channel 'c' is enabled to receive MIDI data. */
MidiLearnParam readActions; // Sample Channels only
MidiLearnParam pitch; // Sample Channels only
};
-} // namespace giada::m::midiLearner
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "midiLighter.h"
-#include "core/channels/channel.h"
+#include "core/channels/midiLighter.h"
#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
-#include "core/mixer.h"
+#include "core/midiMapper.h"
-namespace giada::m::midiLighter
+namespace giada::m
{
-namespace
+template <typename KernelMidiI>
+MidiLighter<KernelMidiI>::MidiLighter(MidiMapper<KernelMidiI>& m)
+: enabled(false)
+, m_midiMapper(&m)
{
-void sendMute_(channel::Data& ch, uint32_t l_mute)
-{
- if (ch.mute)
- kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOn);
- else
- kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOff);
}
/* -------------------------------------------------------------------------- */
-void sendSolo_(channel::Data& ch, uint32_t l_solo)
+template <typename KernelMidiI>
+MidiLighter<KernelMidiI>::MidiLighter(MidiMapper<KernelMidiI>& m, const Patch::Channel& p)
+: enabled(p.midiOutL)
+, playing(p.midiOutLplaying)
+, mute(p.midiOutLmute)
+, solo(p.midiOutLsolo)
+, m_midiMapper(&m)
{
- if (ch.solo)
- kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOn);
- else
- kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOff);
}
/* -------------------------------------------------------------------------- */
-void sendStatus_(channel::Data& ch, uint32_t l_playing, bool audible)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendStatus(ChannelStatus status, bool audible)
{
- switch (ch.state->playStatus.load())
- {
+ const MidiMap& midiMap = m_midiMapper->currentMap;
+ const uint32_t l_playing = playing.getValue();
+
+ if (l_playing == 0x0)
+ return;
+ switch (status)
+ {
case ChannelStatus::OFF:
- kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopped);
+ m_midiMapper->sendMidiLightning(l_playing, midiMap.stopped);
break;
case ChannelStatus::WAIT:
- kernelMidi::sendMidiLightning(l_playing, midimap::midimap.waiting);
+ m_midiMapper->sendMidiLightning(l_playing, midiMap.waiting);
break;
case ChannelStatus::ENDING:
- kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopping);
+ m_midiMapper->sendMidiLightning(l_playing, midiMap.stopping);
break;
case ChannelStatus::PLAY:
- kernelMidi::sendMidiLightning(l_playing, audible ? midimap::midimap.playing : midimap::midimap.playingInaudible);
+ m_midiMapper->sendMidiLightning(l_playing, audible ? midiMap.playing : midiMap.playingInaudible);
break;
default:
break;
}
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Data::Data(const patch::Channel& p)
-: enabled(p.midiOutL)
-, playing(p.midiOutLplaying)
-, mute(p.midiOutLmute)
-, solo(p.midiOutLsolo)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendMute(bool isMuted)
{
+ const MidiMap& midiMap = m_midiMapper->currentMap;
+ const uint32_t l_mute = mute.getValue();
+
+ if (l_mute != 0x0)
+ m_midiMapper->sendMidiLightning(l_mute, isMuted ? midiMap.muteOn : midiMap.muteOff);
}
/* -------------------------------------------------------------------------- */
-void react(channel::Data& ch, const eventDispatcher::Event& e, bool audible)
+template <typename KernelMidiI>
+void MidiLighter<KernelMidiI>::sendSolo(bool isSoloed)
{
- if (!ch.midiLighter.enabled)
- return;
-
- uint32_t l_playing = ch.midiLighter.playing.getValue();
- uint32_t l_mute = ch.midiLighter.mute.getValue();
- uint32_t l_solo = ch.midiLighter.solo.getValue();
-
- switch (e.type)
- {
-
- case eventDispatcher::EventType::KEY_PRESS:
- case eventDispatcher::EventType::KEY_RELEASE:
- case eventDispatcher::EventType::KEY_KILL:
- case eventDispatcher::EventType::SEQUENCER_STOP:
- if (l_playing != 0x0)
- sendStatus_(ch, l_playing, audible);
- break;
+ const MidiMap& midiMap = m_midiMapper->currentMap;
+ const uint32_t l_solo = solo.getValue();
- case eventDispatcher::EventType::CHANNEL_MUTE:
- if (l_mute != 0x0)
- sendMute_(ch, l_mute);
- break;
+ if (l_solo != 0x0)
+ m_midiMapper->sendMidiLightning(l_solo, isSoloed ? midiMap.soloOn : midiMap.soloOff);
+}
- case eventDispatcher::EventType::CHANNEL_SOLO:
- if (l_solo != 0x0)
- sendSolo_(ch, l_solo);
- break;
+/* -------------------------------------------------------------------------- */
- default:
- break;
- }
-}
-} // namespace giada::m::midiLighter
\ No newline at end of file
+template struct MidiLighter<KernelMidi>;
+#ifdef WITH_TESTS
+template struct MidiLighter<KernelMidiMock>;
+#endif
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_MIDI_LIGHTER_H
#define G_CHANNEL_MIDI_LIGHTER_H
+#include "core/eventDispatcher.h"
#include "core/midiLearnParam.h"
+#include "core/midiMapper.h"
+#include "core/patch.h"
-namespace giada::m::channel
+namespace giada::m
{
-struct Data;
-}
-namespace giada::m::patch
+template <typename KernelMidiI>
+class MidiLighter final
{
-struct Channel;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::midiLighter
-{
-struct Data
-{
- Data() = default;
- Data(const patch::Channel& p);
- Data(const Data& o) = default;
+public:
+ MidiLighter(MidiMapper<KernelMidiI>&);
+ MidiLighter(MidiMapper<KernelMidiI>&, const Patch::Channel&);
+ MidiLighter(const MidiLighter& o) = default;
+
+ void sendStatus(ChannelStatus, bool audible);
+ void sendMute(bool isMuted);
+ void sendSolo(bool isSoloed);
/* enabled
- Tells whether MIDI ligthing is enabled or not. */
+ Tells whether MIDI lighting is enabled or not. */
bool enabled;
- /* MIDI learning fields for MIDI ligthing. */
+ /* MIDI learning fields for MIDI lighting. */
MidiLearnParam playing;
MidiLearnParam mute;
MidiLearnParam solo;
+
+private:
+ MidiMapper<KernelMidiI>* m_midiMapper;
};
-void react(channel::Data& ch, const eventDispatcher::Event& e, bool audible);
-} // namespace giada::m::midiLighter
+extern template struct MidiLighter<KernelMidi>;
+#ifdef WITH_TESTS
+extern template struct MidiLighter<KernelMidiMock>;
+#endif
+} // namespace giada::m
#endif
#include "core/mixer.h"
#include "core/plugins/pluginHost.h"
-namespace giada::m::midiReceiver
+namespace giada::m
{
-namespace
+void MidiReceiver::react(const Channel& ch, const EventDispatcher::Event& e) const
{
-void sendToPlugins_(const channel::Data& ch, const MidiEvent& e, Frame localFrame)
-{
- juce::MidiMessage message = juce::MidiMessage(
- e.getStatus(),
- e.getNote(),
- e.getVelocity());
- ch.buffer->midi.addEvent(message, localFrame);
+ switch (e.type)
+ {
+ case EventDispatcher::EventType::MIDI:
+ parseMidi(ch, std::get<Action>(e.data).event);
+ break;
+
+ case EventDispatcher::EventType::KEY_KILL:
+ case EventDispatcher::EventType::SEQUENCER_STOP:
+ case EventDispatcher::EventType::SEQUENCER_REWIND:
+ sendToPlugins(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF), 0);
+ break;
+
+ default:
+ break;
+ }
}
/* -------------------------------------------------------------------------- */
-void parseMidi_(const channel::Data& ch, const MidiEvent& e)
+void MidiReceiver::advance(const Channel& ch, const Sequencer::Event& e) const
{
- /* Now all messages are turned into Channel-0 messages. Giada doesn't care
- about holding MIDI channel information. Moreover, having all internal
- messages on channel 0 is way easier. Then send it to plug-ins. */
-
- MidiEvent flat(e);
- flat.setChannel(0);
- sendToPlugins_(ch, flat, /*delta=*/0);
+ if (e.type == Sequencer::EventType::ACTIONS && ch.isPlaying())
+ for (const Action& action : *e.actions)
+ if (action.channelId == ch.id)
+ sendToPlugins(ch, action.event, e.delta);
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void react(const channel::Data& ch, const eventDispatcher::Event& e)
+void MidiReceiver::render(const Channel& ch, PluginHost& pluginHost) const
{
- switch (e.type)
- {
-
- case eventDispatcher::EventType::MIDI:
- parseMidi_(ch, std::get<Action>(e.data).event);
- break;
+ ch.shared->midiBuffer.clear();
- case eventDispatcher::EventType::KEY_KILL:
- case eventDispatcher::EventType::SEQUENCER_STOP:
- case eventDispatcher::EventType::SEQUENCER_REWIND:
- sendToPlugins_(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF), 0);
- break;
-
- default:
- break;
+ MidiEvent e;
+ while (ch.shared->midiQueue.pop(e))
+ {
+ juce::MidiMessage message = juce::MidiMessage(
+ e.getStatus(),
+ e.getNote(),
+ e.getVelocity());
+ ch.shared->midiBuffer.addEvent(message, e.getDelta());
}
+
+ pluginHost.processStack(ch.shared->audioBuffer, ch.plugins, &ch.shared->midiBuffer);
}
/* -------------------------------------------------------------------------- */
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void MidiReceiver::sendToPlugins(const Channel& ch, const MidiEvent& e, Frame localFrame) const
{
- if (e.type == sequencer::EventType::ACTIONS && ch.isPlaying())
- for (const Action& action : *e.actions)
- if (action.channelId == ch.id)
- sendToPlugins_(ch, action.event, e.delta);
+ ch.shared->midiQueue.push(MidiEvent(e.getRaw(), localFrame));
}
/* -------------------------------------------------------------------------- */
-void render(const channel::Data& ch)
+void MidiReceiver::parseMidi(const Channel& ch, const MidiEvent& e) const
{
- pluginHost::processStack(ch.buffer->audio, ch.plugins, &ch.buffer->midi);
- ch.buffer->midi.clear();
+ /* Now all messages are turned into Channel-0 messages. Giada doesn't care
+ about holding MIDI channel information. Moreover, having all internal
+ messages on channel 0 is way easier. Then send it to plug-ins. */
+
+ MidiEvent flat(e);
+ flat.setChannel(0);
+ sendToPlugins(ch, flat, /*delta=*/0);
}
-} // namespace giada::m::midiReceiver
+} // namespace giada::m
#endif // WITH_VST
#ifdef WITH_VST
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiReceiver
+#include "core/sequencer.h"
+
+namespace giada::m
{
-struct Data
+class PluginHost;
+class Channel;
+class MidiReceiver final
{
-};
+public:
+ void react(const Channel& ch, const EventDispatcher::Event& e) const;
+ void advance(const Channel& ch, const Sequencer::Event& e) const;
+ void render(const Channel& ch, PluginHost& plugiHost) const;
-void react(const channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-void render(const channel::Data& ch);
-} // namespace giada::m::midiReceiver
+private:
+ void sendToPlugins(const Channel& ch, const MidiEvent& e, Frame localFrame) const;
+ void parseMidi(const Channel& ch, const MidiEvent& e) const;
+};
+} // namespace giada::m
#endif // WITH_VST
*
* -------------------------------------------------------------------------- */
-#include "midiSender.h"
+#include "core/channels/midiSender.h"
#include "core/channels/channel.h"
#include "core/kernelMidi.h"
#include "core/mixer.h"
-namespace giada::m::midiSender
+namespace giada::m
{
-namespace
+MidiSender::MidiSender(KernelMidi& k)
+: kernelMidi(&k)
+, enabled(false)
+, filter(0)
{
-void send_(const channel::Data& ch, MidiEvent e)
-{
- e.setChannel(ch.midiSender->filter);
- kernelMidi::send(e.getRaw());
}
/* -------------------------------------------------------------------------- */
-void parseActions_(const channel::Data& ch, const std::vector<Action>& as)
+MidiSender::MidiSender(const Patch::Channel& p, KernelMidi& k)
+: kernelMidi(&k)
+, enabled(p.midiOut)
+, filter(p.midiOutChan)
{
- for (const Action& a : as)
- if (a.channelId == ch.id)
- send_(ch, a.event);
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Data::Data(const patch::Channel& p)
-: enabled(p.midiOut)
-, filter(p.midiOutChan)
+void MidiSender::react(const Channel& ch, const EventDispatcher::Event& e)
{
+ if (!ch.isPlaying() || !enabled || ch.isMuted())
+ return;
+
+ if (e.type == EventDispatcher::EventType::KEY_KILL ||
+ e.type == EventDispatcher::EventType::SEQUENCER_STOP)
+ send(MidiEvent(G_MIDI_ALL_NOTES_OFF));
}
/* -------------------------------------------------------------------------- */
-void react(const channel::Data& ch, const eventDispatcher::Event& e)
+void MidiSender::advance(const Channel& ch, const Sequencer::Event& e) const
{
- if (!ch.isPlaying() || !ch.midiSender->enabled)
+ if (!ch.isPlaying() || !enabled || ch.isMuted())
return;
+ if (e.type == Sequencer::EventType::ACTIONS)
+ parseActions(ch, *e.actions);
+}
+
+/* -------------------------------------------------------------------------- */
- if (e.type == eventDispatcher::EventType::KEY_KILL ||
- e.type == eventDispatcher::EventType::SEQUENCER_STOP)
- send_(ch, MidiEvent(G_MIDI_ALL_NOTES_OFF));
+void MidiSender::send(MidiEvent e) const
+{
+ e.setChannel(filter);
+ kernelMidi->send(e.getRaw());
}
/* -------------------------------------------------------------------------- */
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void MidiSender::parseActions(const Channel& ch, const std::vector<Action>& as) const
{
- if (!ch.midiSender->enabled)
- return;
- if (e.type == sequencer::EventType::ACTIONS)
- parseActions_(ch, *e.actions);
+ for (const Action& a : as)
+ if (a.channelId == ch.id)
+ send(a.event);
}
-} // namespace giada::m::midiSender
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_MIDI_SENDER_H
#define G_CHANNEL_MIDI_SENDER_H
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::patch
-{
-struct Channel;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sequencer
-{
-struct Event;
-}
-namespace giada::m::midiSender
+#include "core/patch.h"
+#include "core/sequencer.h"
+
+namespace giada::m
{
-struct Data
+class KernelMidi;
+class Channel;
+class MidiSender final
{
- Data() = default;
- Data(const patch::Channel& p);
- Data(const Data& o) = default;
+public:
+ MidiSender(KernelMidi&);
+ MidiSender(const Patch::Channel& p, KernelMidi&);
+ MidiSender(const MidiSender& o) = default;
+
+ void react(const Channel& ch, const EventDispatcher::Event& e);
+ void advance(const Channel& ch, const Sequencer::Event& e) const;
+
+ KernelMidi* kernelMidi;
/* enabled
Tells whether MIDI output is enabled or not. */
Which MIDI channel data should be sent to. */
int filter;
-};
-void react(const channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::midiSender
+private:
+ void send(MidiEvent e) const;
+ void parseActions(const Channel& ch, const std::vector<Action>& as) const;
+};
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "sampleActionRecorder.h"
-#include "core/action.h"
+#include "core/channels/sampleActionRecorder.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
-#include "core/conf.h"
#include "core/eventDispatcher.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
-#include "core/recorderHandler.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
#include <cassert>
-namespace giada::m::sampleActionRecorder
+namespace giada::m
{
-namespace
+SampleActionRecorder::SampleActionRecorder(ActionRecorder& a, Sequencer& s)
+: m_actionRecorder(&a)
+, m_sequencer(&s)
{
-void record_(channel::Data& ch, int note);
-void onKeyPress_(channel::Data& ch);
-void toggleReadActions_(channel::Data& ch);
-void startReadActions_(channel::Data& ch);
-void stopReadActions_(channel::Data& ch, ChannelStatus curRecStatus);
-void killReadActions_(channel::Data& ch);
-bool canRecord_(const channel::Data& ch);
-
-/* -------------------------------------------------------------------------- */
-
-bool canRecord_(const channel::Data& ch)
-{
- return recManager::isRecordingAction() &&
- clock::isRunning() &&
- !recManager::isRecordingInput() &&
- !ch.samplePlayer->isAnyLoopMode();
}
/* -------------------------------------------------------------------------- */
-void onKeyPress_(channel::Data& ch)
+void SampleActionRecorder::react(Channel& ch, const EventDispatcher::Event& e, bool treatRecsAsLoops,
+ bool seqIsRunning, bool canRecordActions) const
{
- if (!canRecord_(ch))
+ if (!ch.hasWave())
return;
- record_(ch, MidiEvent::NOTE_ON);
- /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to
- prevent existing actions to interfere with the keypress/keyrel combo. */
+ canRecordActions = canRecordActions && !ch.samplePlayer->isAnyLoopMode();
- if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
- ch.state->readActions.store(false);
+ switch (e.type)
+ {
+ case EventDispatcher::EventType::KEY_PRESS:
+ if (canRecordActions)
+ onKeyPress(ch);
+ break;
+
+ case EventDispatcher::EventType::KEY_RELEASE:
+ /* Record a stop event only if channel is SINGLE_PRESS. For any other
+ mode the key release event is meaningless. */
+ if (canRecordActions && ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
+ record(ch, MidiEvent::NOTE_OFF);
+ break;
+
+ case EventDispatcher::EventType::KEY_KILL:
+ if (canRecordActions)
+ record(ch, MidiEvent::NOTE_KILL);
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+ if (ch.hasActions)
+ toggleReadActions(ch, treatRecsAsLoops, seqIsRunning);
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS:
+ /* Killing Read Actions, i.e. shift + click on 'R' button is meaningful
+ only when the conf::treatRecsAsLoops is true. */
+ if (treatRecsAsLoops)
+ killReadActions(ch);
+ break;
+
+ default:
+ break;
+ }
}
/* -------------------------------------------------------------------------- */
-void record_(channel::Data& ch, int note)
+void SampleActionRecorder::record(Channel& ch, int note) const
{
- recorderHandler::liveRec(ch.id, MidiEvent(note, 0, 0),
- clock::quantize(clock::getCurrentFrame()));
-
+ m_actionRecorder->liveRec(ch.id, MidiEvent(note, 0, 0), m_sequencer->getCurrentFrameQuantized());
ch.hasActions = true;
}
/* -------------------------------------------------------------------------- */
-void toggleReadActions_(channel::Data& ch)
+void SampleActionRecorder::onKeyPress(Channel& ch) const
{
- /* When you start reading actions while conf::treatRecsAsLoops is true, the
- value ch.state->readActions actually is not set to true immediately, because
- the channel is in wait mode (REC_WAITING). readActions will become true on
- the next first beat. So a 'stop rec' command should occur also when
- readActions is false but the channel is in wait mode; this check will
- handle the case of when you press 'R', the channel goes into REC_WAITING and
- then you press 'R' again to undo the status. */
+ record(ch, MidiEvent::NOTE_ON);
- if (!ch.hasActions)
- return;
-
- const bool readActions = ch.state->readActions.load();
- const ChannelStatus recStatus = ch.state->recStatus.load();
+ /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to
+ prevent existing actions to interfere with the keypress/keyrel combo. */
- if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
- stopReadActions_(ch, recStatus);
- else
- startReadActions_(ch);
+ if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
+ ch.shared->readActions.store(false);
}
/* -------------------------------------------------------------------------- */
-void startReadActions_(channel::Data& ch)
+void SampleActionRecorder::startReadActions(Channel& ch, bool treatRecsAsLoops) const
{
- if (conf::conf.treatRecsAsLoops)
- ch.state->recStatus.store(ChannelStatus::WAIT);
+ if (treatRecsAsLoops)
+ ch.shared->recStatus.store(ChannelStatus::WAIT);
else
{
- ch.state->recStatus.store(ChannelStatus::PLAY);
- ch.state->readActions.store(true);
+ ch.shared->recStatus.store(ChannelStatus::PLAY);
+ ch.shared->readActions.store(true);
}
}
/* -------------------------------------------------------------------------- */
-void stopReadActions_(channel::Data& ch, ChannelStatus curRecStatus)
+void SampleActionRecorder::stopReadActions(Channel& ch, ChannelStatus curRecStatus,
+ bool treatRecsAsLoops, bool seqIsRunning) const
{
- /* First of all, if the clock is not running or treatRecsAsLoops is off,
+ /* First of all, if the sequencer is not running or treatRecsAsLoops is off,
just stop and disable everything. Otherwise make sure a channel with actions
behave like a dynamic one. */
- if (!clock::isRunning() || !conf::conf.treatRecsAsLoops)
+ if (!seqIsRunning || !treatRecsAsLoops)
{
- ch.state->recStatus.store(ChannelStatus::OFF);
- ch.state->readActions.store(false);
+ ch.shared->recStatus.store(ChannelStatus::OFF);
+ ch.shared->readActions.store(false);
}
else if (curRecStatus == ChannelStatus::WAIT)
- ch.state->recStatus.store(ChannelStatus::OFF);
+ ch.shared->recStatus.store(ChannelStatus::OFF);
else if (curRecStatus == ChannelStatus::ENDING)
- ch.state->recStatus.store(ChannelStatus::PLAY);
+ ch.shared->recStatus.store(ChannelStatus::PLAY);
else
- ch.state->recStatus.store(ChannelStatus::ENDING);
+ ch.shared->recStatus.store(ChannelStatus::ENDING);
}
/* -------------------------------------------------------------------------- */
-void killReadActions_(channel::Data& ch)
+void SampleActionRecorder::toggleReadActions(Channel& ch, bool treatRecsAsLoops, bool seqIsRunning) const
{
- /* Killing Read Actions, i.e. shift + click on 'R' button is meaninful only
- when the conf::treatRecsAsLoops is true. */
+ /* When you start reading actions while conf::treatRecsAsLoops is true, the
+ value ch.shared->readActions actually is not set to true immediately, because
+ the channel is in wait mode (REC_WAITING). readActions will become true on
+ the next first beat. So a 'stop rec' command should occur also when
+ readActions is false but the channel is in wait mode; this check will
+ handle the case of when you press 'R', the channel goes into REC_WAITING and
+ then you press 'R' again to undo the status. */
- if (!conf::conf.treatRecsAsLoops)
- return;
- ch.state->recStatus.store(ChannelStatus::OFF);
- ch.state->readActions.store(false);
+ const bool readActions = ch.shared->readActions.load();
+ const ChannelStatus recStatus = ch.shared->recStatus.load();
+
+ if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
+ stopReadActions(ch, recStatus, treatRecsAsLoops, seqIsRunning);
+ else
+ startReadActions(ch, treatRecsAsLoops);
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void SampleActionRecorder::killReadActions(Channel& ch) const
{
- if (!ch.hasWave())
- return;
-
- switch (e.type)
- {
-
- case eventDispatcher::EventType::KEY_PRESS:
- onKeyPress_(ch);
- break;
-
- /* Record a stop event only if channel is SINGLE_PRESS. For any other
- mode the key release event is meaningless. */
-
- case eventDispatcher::EventType::KEY_RELEASE:
- if (canRecord_(ch) && ch.samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS)
- record_(ch, MidiEvent::NOTE_OFF);
- break;
-
- case eventDispatcher::EventType::KEY_KILL:
- if (canRecord_(ch))
- record_(ch, MidiEvent::NOTE_KILL);
- break;
-
- case eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
- toggleReadActions_(ch);
- break;
-
- case eventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS:
- killReadActions_(ch);
- break;
-
- default:
- break;
- }
+ ch.shared->recStatus.store(ChannelStatus::OFF);
+ ch.shared->readActions.store(false);
}
-} // namespace giada::m::sampleActionRecorder
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_SAMPLE_ACTION_RECORDER_H
#define G_CHANNEL_SAMPLE_ACTION_RECORDER_H
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::eventDispatcher
-{
-struct Event;
-}
-namespace giada::m::sampleActionRecorder
+#include "core/eventDispatcher.h"
+
+namespace giada::m
{
-struct Data
+class ActionRecorder;
+class Sequencer;
+class Channel;
+class SampleActionRecorder final
{
+public:
+ SampleActionRecorder(ActionRecorder&, Sequencer&);
+
+ void react(Channel&, const EventDispatcher::Event&, bool treatRecsAsLoops,
+ bool seqIsRunning, bool canRecordActions) const;
+
+private:
+ void record(Channel&, int note) const;
+ void onKeyPress(Channel&) const;
+ void startReadActions(Channel&, bool treatRecsAsLoops) const;
+ void stopReadActions(Channel&, ChannelStatus, bool treatRecsAsLoops, bool seqIsRunning) const;
+ void toggleReadActions(Channel&, bool treatRecsAsLoops, bool seqIsRunning) const;
+ void killReadActions(Channel& ch) const;
+
+ ActionRecorder* m_actionRecorder;
+ Sequencer* m_sequencer;
};
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::sampleActionRecorder
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "sampleAdvancer.h"
+#include "core/channels/sampleAdvancer.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include <cassert>
-namespace giada::m::sampleAdvancer
+namespace giada::m
{
-namespace
+void SampleAdvancer::onLastFrame(const Channel& ch, bool seqIsRunning) const
{
-void rewind_(const channel::Data& ch, Frame localFrame)
+ const SamplePlayerMode mode = ch.samplePlayer->mode;
+ const bool isLoop = ch.samplePlayer->isAnyLoopMode();
+
+ switch (ch.shared->playStatus.load())
+ {
+ case ChannelStatus::PLAY:
+ /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
+ SINGLE_ENDLESS, which runs forever unless it's in ENDING mode.
+ Other loop once modes are put in wait mode. */
+ if ((mode == SamplePlayerMode::SINGLE_BASIC ||
+ mode == SamplePlayerMode::SINGLE_BASIC_PAUSE ||
+ mode == SamplePlayerMode::SINGLE_PRESS ||
+ mode == SamplePlayerMode::SINGLE_RETRIG) ||
+ (isLoop && !seqIsRunning))
+ stop(ch, 0);
+ else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+ wait(ch);
+ break;
+
+ case ChannelStatus::ENDING:
+ stop(ch, 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::advance(const Channel& ch, const Sequencer::Event& e) const
{
- ch.state->rewinding = true;
- ch.state->offset = localFrame;
+ switch (e.type)
+ {
+ case Sequencer::EventType::FIRST_BEAT:
+ onFirstBeat(ch, e.delta);
+ break;
+
+ case Sequencer::EventType::BAR:
+ onBar(ch, e.delta);
+ break;
+
+ case Sequencer::EventType::REWIND:
+ rewind(ch, e.delta);
+ break;
+
+ case Sequencer::EventType::ACTIONS:
+ if (ch.shared->readActions.load() == true)
+ parseActions(ch, *e.actions, e.delta);
+ break;
+
+ default:
+ break;
+ }
}
/* -------------------------------------------------------------------------- */
-void stop_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::rewind(const Channel& ch, Frame localFrame) const
{
- ch.state->playStatus.store(ChannelStatus::OFF);
- ch.state->tracker.store(ch.samplePlayer->begin);
+ ch.shared->rewinding = true;
+ ch.shared->offset = localFrame;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::stop(const Channel& ch, Frame localFrame) const
+{
+ ch.shared->playStatus.store(ChannelStatus::OFF);
+ ch.shared->tracker.store(ch.samplePlayer->begin);
/* Clear data in range [localFrame, (buffer.size)) if the event occurs in
the middle of the buffer. TODO - samplePlayer should be responsible for this*/
if (localFrame != 0)
- ch.buffer->audio.clear(localFrame);
+ ch.shared->audioBuffer.clear(localFrame);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SampleAdvancer::play(const Channel& ch, Frame localFrame) const
+{
+ ch.shared->playStatus.store(ChannelStatus::PLAY);
+ ch.shared->offset = localFrame;
}
/* -------------------------------------------------------------------------- */
-void play_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::wait(const Channel& ch) const
{
- ch.state->playStatus.store(ChannelStatus::PLAY);
- ch.state->offset = localFrame;
+ ch.shared->playStatus.store(ChannelStatus::WAIT);
+ ch.shared->tracker.store(ch.samplePlayer->begin);
}
/* -------------------------------------------------------------------------- */
-void onFirstBeat_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onFirstBeat(const Channel& ch, Frame localFrame) const
{
G_DEBUG("onFirstBeat ch=" << ch.id << ", localFrame=" << localFrame);
- ChannelStatus playStatus = ch.state->playStatus.load();
- ChannelStatus recStatus = ch.state->recStatus.load();
- bool isLoop = ch.samplePlayer->isAnyLoopMode();
+ const ChannelStatus playStatus = ch.shared->playStatus.load();
+ const ChannelStatus recStatus = ch.shared->recStatus.load();
+ const bool isLoop = ch.samplePlayer->isAnyLoopMode();
switch (playStatus)
{
case ChannelStatus::PLAY:
if (isLoop)
- rewind_(ch, localFrame);
+ rewind(ch, localFrame);
break;
case ChannelStatus::WAIT:
- play_(ch, localFrame);
+ play(ch, localFrame);
break;
case ChannelStatus::ENDING:
if (isLoop)
- stop_(ch, localFrame);
+ stop(ch, localFrame);
break;
default:
switch (recStatus)
{
case ChannelStatus::WAIT:
- ch.state->recStatus.store(ChannelStatus::PLAY);
- ch.state->readActions.store(true);
+ ch.shared->recStatus.store(ChannelStatus::PLAY);
+ ch.shared->readActions.store(true);
break;
case ChannelStatus::ENDING:
- ch.state->recStatus.store(ChannelStatus::OFF);
- ch.state->readActions.store(false);
+ ch.shared->recStatus.store(ChannelStatus::OFF);
+ ch.shared->readActions.store(false);
break;
default:
/* -------------------------------------------------------------------------- */
-void onBar_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onBar(const Channel& ch, Frame localFrame) const
{
G_DEBUG("onBar ch=" << ch.id << ", localFrame=" << localFrame);
- ChannelStatus playStatus = ch.state->playStatus.load();
- SamplePlayerMode mode = ch.samplePlayer->mode;
+ const ChannelStatus playStatus = ch.shared->playStatus.load();
+ const SamplePlayerMode mode = ch.samplePlayer->mode;
if (playStatus == ChannelStatus::PLAY && (mode == SamplePlayerMode::LOOP_REPEAT ||
mode == SamplePlayerMode::LOOP_ONCE_BAR))
- rewind_(ch, localFrame);
+ rewind(ch, localFrame);
else if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR)
- ch.state->playStatus.store(ChannelStatus::PLAY);
+ play(ch, localFrame);
}
/* -------------------------------------------------------------------------- */
-void onNoteOn_(const channel::Data& ch, Frame localFrame)
+void SampleAdvancer::onNoteOn(const Channel& ch, Frame localFrame) const
{
- ChannelStatus playStatus = ch.state->playStatus.load();
-
- if (playStatus == ChannelStatus::OFF)
- {
- playStatus = ChannelStatus::PLAY;
- }
- else if (playStatus == ChannelStatus::PLAY)
+ switch (ch.shared->playStatus.load())
{
+ case ChannelStatus::OFF:
+ play(ch, localFrame);
+ break;
+
+ case ChannelStatus::PLAY:
if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_RETRIG)
- rewind_(ch, localFrame);
+ rewind(ch, localFrame);
else
- playStatus = ChannelStatus::OFF;
- }
-
- ch.state->playStatus.store(playStatus);
- ch.state->offset = localFrame;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void onNoteOff_(const channel::Data& ch, Frame localFrame)
-{
- ch.state->playStatus.store(ChannelStatus::OFF);
- ch.state->tracker.store(ch.samplePlayer->begin);
-
- /* Clear data in range [localFrame, (buffer.size)) if the kill event occurs
- in the middle of the buffer. */
+ stop(ch, localFrame);
+ break;
- if (localFrame != 0)
- ch.buffer->audio.clear(localFrame);
+ default:
+ break;
+ }
}
/* -------------------------------------------------------------------------- */
-void parseActions_(const channel::Data& ch, const std::vector<Action>& as, Frame localFrame)
+void SampleAdvancer::parseActions(const Channel& ch,
+ const std::vector<Action>& as, Frame localFrame) const
{
if (ch.samplePlayer->isAnyLoopMode() || !ch.isReadingActions())
return;
switch (a.event.getStatus())
{
case MidiEvent::NOTE_ON:
- onNoteOn_(ch, localFrame);
+ onNoteOn(ch, localFrame);
break;
case MidiEvent::NOTE_OFF:
- onNoteOff_(ch, localFrame);
- break;
-
case MidiEvent::NOTE_KILL:
- onNoteOff_(ch, localFrame);
+ stop(ch, localFrame);
break;
default:
}
}
}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void onLastFrame(const channel::Data& ch)
-{
- ChannelStatus playStatus = ch.state->playStatus.load();
- SamplePlayerMode mode = ch.samplePlayer->mode;
- bool isLoop = ch.samplePlayer->isAnyLoopMode();
- bool running = clock::isRunning();
-
- if (playStatus == ChannelStatus::PLAY)
- {
- /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
- SINGLE_ENDLESS, which runs forever unless it's in ENDING mode.
- Other loop once modes are put in wait mode. */
- if ((mode == SamplePlayerMode::SINGLE_BASIC ||
- mode == SamplePlayerMode::SINGLE_PRESS ||
- mode == SamplePlayerMode::SINGLE_RETRIG) ||
- (isLoop && !running))
- playStatus = ChannelStatus::OFF;
- else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
- playStatus = ChannelStatus::WAIT;
- }
- else if (playStatus == ChannelStatus::ENDING)
- playStatus = ChannelStatus::OFF;
-
- ch.state->playStatus.store(playStatus);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
-{
- switch (e.type)
- {
- case sequencer::EventType::FIRST_BEAT:
- onFirstBeat_(ch, e.delta);
- break;
-
- case sequencer::EventType::BAR:
- onBar_(ch, e.delta);
- break;
-
- case sequencer::EventType::REWIND:
- rewind_(ch, e.delta);
- break;
-
- case sequencer::EventType::ACTIONS:
- if (ch.state->readActions.load() == true)
- parseActions_(ch, *e.actions, e.delta);
- break;
-
- default:
- break;
- }
-}
-} // namespace giada::m::sampleAdvancer
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#include "core/sequencer.h"
-namespace giada::m::channel
+namespace giada::m
{
-struct Data;
-}
-namespace giada::m::sampleAdvancer
+class Channel;
+class SampleAdvancer final
{
-void onLastFrame(const channel::Data& ch);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-} // namespace giada::m::sampleAdvancer
+public:
+ void onLastFrame(const Channel& ch, bool seqIsRunning) const;
+ void advance(const Channel& ch, const Sequencer::Event& e) const;
+
+private:
+ void rewind(const Channel& ch, Frame localFrame) const;
+ void stop(const Channel& ch, Frame localFrame) const;
+ void play(const Channel& ch, Frame localFrame) const;
+ void wait(const Channel& ch) const;
+ void onFirstBeat(const Channel& ch, Frame localFrame) const;
+ void onBar(const Channel& ch, Frame localFrame) const;
+ void onNoteOn(const Channel& ch, Frame localFrame) const;
+ void parseActions(const Channel& ch, const std::vector<Action>& as, Frame localFrame) const;
+};
+} // namespace giada::m
#endif
#include "samplePlayer.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include "core/wave.h"
-#include "core/waveManager.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include <algorithm>
#include <cassert>
-namespace giada::m::samplePlayer
+namespace giada::m
{
-namespace
-{
-bool shouldLoop_(const channel::Data& ch)
-{
- ChannelStatus playStatus = ch.state->playStatus.load();
- SamplePlayerMode mode = ch.samplePlayer->mode;
-
- return (mode == SamplePlayerMode::LOOP_BASIC ||
- mode == SamplePlayerMode::LOOP_REPEAT ||
- mode == SamplePlayerMode::SINGLE_ENDLESS) &&
- playStatus == ChannelStatus::PLAY;
-}
-
-/* -------------------------------------------------------------------------- */
-
-WaveReader::Result fillBuffer_(const channel::Data& ch, Frame start, Frame offset)
-{
- AudioBuffer& buffer = ch.buffer->audio;
- const WaveReader& waveReader = ch.samplePlayer->waveReader;
-
- return waveReader.fill(buffer, start, ch.samplePlayer->end, offset, ch.samplePlayer->pitch);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isPlaying_(const channel::Data& ch)
-{
- return ch.samplePlayer->waveReader.wave != nullptr && ch.isPlaying();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setWave_(samplePlayer::Data& sp, Wave* w, float samplerateRatio)
-{
- if (w == nullptr)
- {
- sp.waveReader.wave = nullptr;
- return;
- }
-
- sp.waveReader.wave = w;
-
- if (samplerateRatio != 1.0f)
- {
- sp.begin *= samplerateRatio;
- sp.end *= samplerateRatio;
- sp.shift *= samplerateRatio;
- }
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Data::Data(Resampler* r)
+SamplePlayer::SamplePlayer(Resampler* r)
: pitch(G_DEFAULT_PITCH)
, mode(SamplePlayerMode::SINGLE_BASIC)
+, shift(0)
+, begin(0)
+, end(0)
, velocityAsVol(false)
, waveReader(r)
{
/* -------------------------------------------------------------------------- */
-Data::Data(const patch::Channel& p, float samplerateRatio, Resampler* r)
+SamplePlayer::SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w)
: pitch(p.pitch)
, mode(p.mode)
, shift(p.shift)
, end(p.end)
, velocityAsVol(p.midiInVeloAsVol)
, waveReader(r)
+, onLastFrame(nullptr)
{
- setWave_(*this, waveManager::hydrateWave(p.waveId), samplerateRatio);
+ setWave(w, samplerateRatio);
}
/* -------------------------------------------------------------------------- */
-bool Data::hasWave() const { return waveReader.wave != nullptr; }
-bool Data::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); }
-bool Data::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); }
+bool SamplePlayer::hasWave() const { return waveReader.wave != nullptr; }
+bool SamplePlayer::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); }
+bool SamplePlayer::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); }
/* -------------------------------------------------------------------------- */
-bool Data::isAnyLoopMode() const
+bool SamplePlayer::isAnyLoopMode() const
{
return mode == SamplePlayerMode::LOOP_BASIC ||
mode == SamplePlayerMode::LOOP_ONCE ||
/* -------------------------------------------------------------------------- */
-Wave* Data::getWave() const
+Wave* SamplePlayer::getWave() const
{
return waveReader.wave;
}
-ID Data::getWaveId() const
+ID SamplePlayer::getWaveId() const
{
if (hasWave())
return waveReader.wave->id;
/* -------------------------------------------------------------------------- */
-Frame Data::getWaveSize() const
+Frame SamplePlayer::getWaveSize() const
{
return hasWave() ? waveReader.wave->getBuffer().countFrames() : 0;
}
/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void react(channel::Data& ch, const eventDispatcher::Event& e)
-{
- if (e.type == eventDispatcher::EventType::CHANNEL_PITCH)
- ch.samplePlayer->pitch = std::get<float>(e.data);
-}
-/* -------------------------------------------------------------------------- */
-
-void advance(const channel::Data& ch, const sequencer::Event& e)
+void SamplePlayer::react(const EventDispatcher::Event& e)
{
- sampleAdvancer::advance(ch, e);
+ if (e.type == EventDispatcher::EventType::CHANNEL_PITCH)
+ pitch = std::get<float>(e.data);
}
/* -------------------------------------------------------------------------- */
-void render(const channel::Data& ch)
+void SamplePlayer::render(const Channel& ch) const
{
- if (!isPlaying_(ch))
+ if (!isPlaying(ch))
return;
- const Frame begin = ch.samplePlayer->begin;
- const Frame end = ch.samplePlayer->end;
-
/* Make sure tracker stays within begin-end range. */
- Frame tracker = std::clamp(ch.state->tracker.load(), begin, end);
+ Frame tracker = std::clamp(ch.shared->tracker.load(), begin, end);
/* If rewinding, fill the tail first, then reset the tracker to the begin
point. The rest is performed as usual. */
- if (ch.state->rewinding)
+ if (ch.shared->rewinding)
{
if (tracker < end)
{
- fillBuffer_(ch, tracker, 0);
- ch.samplePlayer->waveReader.last();
+ fillBuffer(ch, tracker, 0);
+ waveReader.last();
}
- ch.state->rewinding = false;
+ ch.shared->rewinding = false;
tracker = begin;
}
- WaveReader::Result res = fillBuffer_(ch, tracker, ch.state->offset);
+ WaveReader::Result res = fillBuffer(ch, tracker, ch.shared->offset);
tracker += res.used;
/* If tracker has looped, special care is needed for the rendering. If the
if (tracker >= end)
{
- ch.samplePlayer->waveReader.last();
+ assert(onLastFrame != nullptr);
+
tracker = begin;
- sampleAdvancer::onLastFrame(ch); // TODO - better moving this to samplerAdvancer::advance
- if (shouldLoop_(ch) && res.generated < ch.buffer->audio.countFrames())
- tracker += fillBuffer_(ch, tracker, res.generated).used;
+ waveReader.last();
+ onLastFrame();
+ if (shouldLoop(ch) && res.generated < ch.shared->audioBuffer.countFrames())
+ tracker += fillBuffer(ch, tracker, res.generated).used;
}
- ch.state->offset = 0;
- ch.state->tracker.store(tracker);
+ ch.shared->offset = 0;
+ ch.shared->tracker.store(tracker);
}
/* -------------------------------------------------------------------------- */
-void loadWave(channel::Data& ch, Wave* w)
+void SamplePlayer::loadWave(Channel& ch, Wave* w)
{
- ch.samplePlayer->waveReader.wave = w;
+ waveReader.wave = w;
- ch.state->tracker.store(0);
- ch.samplePlayer->shift = 0;
- ch.samplePlayer->begin = 0;
+ ch.shared->tracker.store(0);
+ shift = 0;
+ begin = 0;
if (w != nullptr)
{
- ch.state->playStatus.store(ChannelStatus::OFF);
- ch.name = w->getBasename(/*ext=*/false);
- ch.samplePlayer->end = w->getBuffer().countFrames() - 1;
+ ch.shared->playStatus.store(ChannelStatus::OFF);
+ ch.name = w->getBasename(/*ext=*/false);
+ end = w->getBuffer().countFrames() - 1;
}
else
{
- ch.state->playStatus.store(ChannelStatus::EMPTY);
- ch.name = "";
- ch.samplePlayer->end = 0;
+ ch.shared->playStatus.store(ChannelStatus::EMPTY);
+ ch.name = "";
+ end = 0;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void SamplePlayer::setWave(Wave* w, float samplerateRatio)
+{
+ if (w == nullptr)
+ {
+ waveReader.wave = nullptr;
+ return;
+ }
+
+ waveReader.wave = w;
+
+ if (samplerateRatio != 1.0f)
+ {
+ begin *= samplerateRatio;
+ end *= samplerateRatio;
+ shift *= samplerateRatio;
}
}
/* -------------------------------------------------------------------------- */
-void setWave(channel::Data& ch, Wave* w, float samplerateRatio)
+void SamplePlayer::kickIn(Channel& ch, Frame f)
+{
+ ch.shared->tracker.store(f);
+ ch.shared->playStatus.store(ChannelStatus::PLAY);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool SamplePlayer::isPlaying(const Channel& ch) const
{
- setWave_(ch.samplePlayer.value(), w, samplerateRatio);
+ return waveReader.wave != nullptr && ch.isPlaying();
}
/* -------------------------------------------------------------------------- */
-void kickIn(channel::Data& ch, Frame f)
+WaveReader::Result SamplePlayer::fillBuffer(const Channel& ch, Frame start, Frame offset) const
{
- ch.state->tracker.store(f);
- ch.state->playStatus.store(ChannelStatus::PLAY);
+ return waveReader.fill(ch.shared->audioBuffer, start, end, offset, pitch);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool SamplePlayer::shouldLoop(const Channel& ch) const
+{
+ const ChannelStatus playStatus = ch.shared->playStatus.load();
+
+ return (mode == SamplePlayerMode::LOOP_BASIC ||
+ mode == SamplePlayerMode::LOOP_REPEAT ||
+ mode == SamplePlayerMode::SINGLE_ENDLESS) &&
+ playStatus == ChannelStatus::PLAY;
}
-} // namespace giada::m::samplePlayer
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_SAMPLE_PLAYER_H
#define G_CHANNEL_SAMPLE_PLAYER_H
-#include "core/channels/sampleAdvancer.h"
-#include "core/channels/sampleReactor.h"
#include "core/channels/waveReader.h"
#include "core/const.h"
+#include "core/eventDispatcher.h"
+#include "core/patch.h"
+#include "core/sequencer.h"
#include "core/types.h"
+#include <functional>
-namespace giada::m::channel
+namespace giada::m
{
-struct Data;
-}
-namespace giada::m::patch
+class Channel;
+class SamplePlayer final
{
-struct Channel;
-}
-namespace giada::m::samplePlayer
-{
-struct Data
-{
- Data(Resampler* r);
- Data(const patch::Channel& p, float samplerateRatio, Resampler* r);
- Data(const Data& o) = default;
- Data(Data&& o) = default;
- Data& operator=(const Data&) = default;
- Data& operator=(Data&&) = default;
+public:
+ SamplePlayer(Resampler* r);
+ SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w);
bool hasWave() const;
bool hasLogicalWave() const;
ID getWaveId() const;
Frame getWaveSize() const;
Wave* getWave() const;
+ void render(const Channel& ch) const;
+
+ void react(const EventDispatcher::Event& e);
+
+ /* loadWave
+ Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */
+
+ void loadWave(Channel& ch, Wave* w);
+
+ /* setWave
+ Just sets the pointer to a Wave object. Used during de-serialization. The
+ ratio is used to adjust begin/end points in case of patch vs. conf sample
+ rate mismatch. If nullptr, set the wave to invalid. */
+
+ void setWave(Wave* w, float samplerateRatio);
+
+ /* kickIn
+ Starts the player right away at frame 'f'. Used when launching a loop after
+ being live recorded. */
+
+ void kickIn(Channel& ch, Frame f);
float pitch;
SamplePlayerMode mode;
Frame end;
bool velocityAsVol; // Velocity drives volume
WaveReader waveReader;
-};
-
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-void advance(const channel::Data& ch, const sequencer::Event& e);
-void render(const channel::Data& ch);
-
-/* loadWave
-Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */
-void loadWave(channel::Data& ch, Wave* w);
+ std::function<void()> onLastFrame;
-/* setWave
-Just sets the pointer to a Wave object. Used during de-serialization. The
-ratio is used to adjust begin/end points in case of patch vs. conf sample
-rate mismatch. If nullptr, set the wave to invalid. */
-
-void setWave(channel::Data& ch, Wave* w, float samplerateRatio);
-
-/* kickIn
-Starts the player right away at frame 'f'. Used when launching a loop after
-being live recorded. */
-
-void kickIn(channel::Data& ch, Frame f);
-} // namespace giada::m::samplePlayer
+private:
+ bool isPlaying(const Channel& ch) const;
+ WaveReader::Result fillBuffer(const Channel& ch, Frame start, Frame offset) const;
+ bool shouldLoop(const Channel& ch) const;
+};
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "sampleReactor.h"
+#include "core/channels/sampleReactor.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include "core/conf.h"
-#include "src/core/model/model.h"
+#include "core/model/model.h"
#include "utils/math.h"
#include <cassert>
-namespace giada::m::sampleReactor
+namespace giada::m
{
namespace
{
constexpr int Q_ACTION_PLAY = 0;
constexpr int Q_ACTION_REWIND = 1;
+} // namespace
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+SampleReactor::SampleReactor(ID channelId, Sequencer& sequencer, model::Model& model)
+{
+ sequencer.quantizer.schedule(Q_ACTION_PLAY + channelId, [channelId, &model](Frame delta) {
+ Channel& ch = model.get().getChannel(channelId);
+ ch.shared->offset = delta;
+ ch.shared->playStatus.store(ChannelStatus::PLAY);
+ });
-void press_(channel::Data& ch, int velocity);
-void release_(channel::Data& ch);
-void kill_(channel::Data& ch);
-void onStopBySeq_(channel::Data& ch);
-void toggleReadActions_(channel::Data& ch);
-ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop);
-ChannelStatus pressWhilePlay_(channel::Data& ch, SamplePlayerMode mode, bool isLoop);
-void rewind_(channel::Data& ch, Frame localFrame = 0);
+ sequencer.quantizer.schedule(Q_ACTION_REWIND + channelId, [this, channelId, &model](Frame delta) {
+ Channel& ch = model.get().getChannel(channelId);
+ ch.isPlaying() ? rewind(ch, delta) : reset(ch);
+ });
+}
/* -------------------------------------------------------------------------- */
-void press_(channel::Data& ch, int velocity)
+void SampleReactor::react(Channel& ch, const EventDispatcher::Event& e,
+ Sequencer& sequencer, const Conf::Data& conf) const
{
- ChannelStatus playStatus = ch.state->playStatus.load();
- SamplePlayerMode mode = ch.samplePlayer->mode;
- bool isLoop = ch.samplePlayer->isAnyLoopMode();
+ if (!ch.hasWave())
+ return;
- switch (playStatus)
+ switch (e.type)
{
- case ChannelStatus::OFF:
- playStatus = pressWhileOff_(ch, velocity, isLoop);
+ case EventDispatcher::EventType::KEY_PRESS:
+ press(ch, sequencer, std::get<int>(e.data));
break;
- case ChannelStatus::PLAY:
- playStatus = pressWhilePlay_(ch, mode, isLoop);
+ case EventDispatcher::EventType::KEY_RELEASE:
+ release(ch, sequencer);
break;
- case ChannelStatus::WAIT:
- playStatus = ChannelStatus::OFF;
+ case EventDispatcher::EventType::KEY_KILL:
+ kill(ch);
break;
- case ChannelStatus::ENDING:
- playStatus = ChannelStatus::PLAY;
+ case EventDispatcher::EventType::SEQUENCER_STOP:
+ onStopBySeq(ch, conf.chansStopOnSeqHalt);
+ break;
+
+ case EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+ toggleReadActions(ch, sequencer.isRunning(), conf.treatRecsAsLoops);
break;
default:
break;
}
-
- ch.state->playStatus.store(playStatus);
}
/* -------------------------------------------------------------------------- */
-void release_(channel::Data& ch)
+void SampleReactor::rewind(Channel& ch, Frame localFrame) const
{
- /* Key release is meaningful only for SINGLE_PRESS modes. */
-
- if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS)
- return;
-
- /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a
- quantization step in progress that would play the channel later on:
- disable it. */
-
- if (ch.state->playStatus.load() == ChannelStatus::PLAY)
- kill_(ch);
- else if (sequencer::quantizer.hasBeenTriggered())
- sequencer::quantizer.clear();
+ ch.shared->rewinding = true;
+ ch.shared->offset = localFrame;
}
/* -------------------------------------------------------------------------- */
-void kill_(channel::Data& ch)
+void SampleReactor::reset(Channel& ch) const
{
- ch.state->playStatus.store(ChannelStatus::OFF);
- ch.state->tracker.store(ch.samplePlayer->begin);
+ ch.shared->tracker.store(ch.samplePlayer->begin);
}
/* -------------------------------------------------------------------------- */
-void onStopBySeq_(channel::Data& ch)
-{
- G_DEBUG("onStopBySeq ch=" << ch.id);
-
- ChannelStatus playStatus = ch.state->playStatus.load();
- bool isReadingActions = ch.state->readActions.load();
- bool isLoop = ch.samplePlayer->isAnyLoopMode();
-
- switch (playStatus)
- {
-
- case ChannelStatus::WAIT:
- /* Loop-mode channels in wait status get stopped right away. */
- if (isLoop)
- ch.state->playStatus.store(ChannelStatus::OFF);
- break;
-
- case ChannelStatus::PLAY:
- if (conf::conf.chansStopOnSeqHalt && (isLoop || isReadingActions))
- kill_(ch);
- break;
-
- default:
- break;
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop)
+ChannelStatus SampleReactor::pressWhileOff(Channel& ch, Sequencer& sequencer,
+ int velocity, bool isLoop) const
{
if (isLoop)
return ChannelStatus::WAIT;
if (ch.samplePlayer->velocityAsVol)
ch.volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME);
- if (clock::canQuantize())
+ if (sequencer.canQuantize())
{
- sequencer::quantizer.trigger(Q_ACTION_PLAY + ch.id);
+ sequencer.quantizer.trigger(Q_ACTION_PLAY + ch.id);
return ChannelStatus::OFF;
}
else
/* -------------------------------------------------------------------------- */
-ChannelStatus pressWhilePlay_(channel::Data& ch, SamplePlayerMode mode, bool isLoop)
+ChannelStatus SampleReactor::pressWhilePlay(Channel& ch, Sequencer& sequencer,
+ SamplePlayerMode mode, bool isLoop) const
{
- if (mode == SamplePlayerMode::SINGLE_RETRIG)
+ if (isLoop)
+ return ChannelStatus::ENDING;
+
+ switch (mode)
{
- if (clock::canQuantize())
- sequencer::quantizer.trigger(Q_ACTION_REWIND + ch.id);
+ case SamplePlayerMode::SINGLE_RETRIG:
+ if (sequencer.canQuantize())
+ sequencer.quantizer.trigger(Q_ACTION_REWIND + ch.id);
else
- rewind_(ch);
+ rewind(ch, /*localFrame=*/0);
return ChannelStatus::PLAY;
- }
- if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS)
+ case SamplePlayerMode::SINGLE_ENDLESS:
return ChannelStatus::ENDING;
- if (mode == SamplePlayerMode::SINGLE_BASIC)
- {
- rewind_(ch);
+ case SamplePlayerMode::SINGLE_BASIC:
+ reset(ch);
return ChannelStatus::OFF;
- }
- return ChannelStatus::OFF;
+ default:
+ return ChannelStatus::OFF;
+ }
}
/* -------------------------------------------------------------------------- */
-void toggleReadActions_(channel::Data& ch)
+void SampleReactor::press(Channel& ch, Sequencer& sequencer, int velocity) const
{
- if (clock::isRunning() && ch.state->recStatus.load() == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops)
- kill_(ch);
-}
+ const SamplePlayerMode mode = ch.samplePlayer->mode;
+ const bool isLoop = ch.samplePlayer->isAnyLoopMode();
-/* -------------------------------------------------------------------------- */
+ ChannelStatus playStatus = ch.shared->playStatus.load();
-void rewind_(channel::Data& ch, Frame localFrame)
-{
- if (ch.isPlaying())
+ switch (playStatus)
{
- ch.state->rewinding = true;
- ch.state->offset = localFrame;
+ case ChannelStatus::OFF:
+ playStatus = pressWhileOff(ch, sequencer, velocity, isLoop);
+ break;
+
+ case ChannelStatus::PLAY:
+ playStatus = pressWhilePlay(ch, sequencer, mode, isLoop);
+ break;
+
+ case ChannelStatus::WAIT:
+ playStatus = ChannelStatus::OFF;
+ break;
+
+ case ChannelStatus::ENDING:
+ playStatus = ChannelStatus::PLAY;
+ break;
+
+ default:
+ break;
}
- else
- ch.state->tracker.store(ch.samplePlayer->begin);
+
+ ch.shared->playStatus.store(playStatus);
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Data::Data(ID channelId)
+void SampleReactor::kill(Channel& ch) const
{
- sequencer::quantizer.schedule(Q_ACTION_PLAY + channelId, [channelId](Frame delta) {
- channel::Data& ch = model::get().getChannel(channelId);
- ch.state->offset = delta;
- ch.state->playStatus.store(ChannelStatus::PLAY);
- });
-
- sequencer::quantizer.schedule(Q_ACTION_REWIND + channelId, [channelId](Frame delta) {
- channel::Data& ch = model::get().getChannel(channelId);
- rewind_(ch, delta);
- });
+ ch.shared->playStatus.store(ChannelStatus::OFF);
+ ch.shared->tracker.store(ch.samplePlayer->begin);
}
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void react(channel::Data& ch, const eventDispatcher::Event& e)
+void SampleReactor::release(Channel& ch, Sequencer& sequencer) const
{
- if (!ch.hasWave())
+ /* Key release is meaningful only for SINGLE_PRESS modes. */
+
+ if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS)
return;
- switch (e.type)
- {
+ /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a
+ quantization step in progress that would play the channel later on:
+ disable it. */
- case eventDispatcher::EventType::KEY_PRESS:
- press_(ch, std::get<int>(e.data));
- break;
+ if (ch.shared->playStatus.load() == ChannelStatus::PLAY)
+ kill(ch);
+ else if (sequencer.quantizer.hasBeenTriggered())
+ sequencer.quantizer.clear();
+}
- case eventDispatcher::EventType::KEY_RELEASE:
- release_(ch);
- break;
+/* -------------------------------------------------------------------------- */
- case eventDispatcher::EventType::KEY_KILL:
- kill_(ch);
- break;
+void SampleReactor::onStopBySeq(Channel& ch, bool chansStopOnSeqHalt) const
+{
+ G_DEBUG("onStopBySeq ch=" << ch.id);
+
+ ChannelStatus playStatus = ch.shared->playStatus.load();
+ bool isReadingActions = ch.shared->readActions.load();
+ bool isLoop = ch.samplePlayer->isAnyLoopMode();
+
+ switch (playStatus)
+ {
- case eventDispatcher::EventType::SEQUENCER_STOP:
- onStopBySeq_(ch);
+ case ChannelStatus::WAIT:
+ /* Loop-mode channels in wait status get stopped right away. */
+ if (isLoop)
+ ch.shared->playStatus.store(ChannelStatus::OFF);
break;
- case eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
- toggleReadActions_(ch);
+ case ChannelStatus::PLAY:
+ if (chansStopOnSeqHalt && (isLoop || isReadingActions))
+ kill(ch);
break;
default:
break;
}
}
-} // namespace giada::m::sampleReactor
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+void SampleReactor::toggleReadActions(Channel& ch, bool isSequencerRunning, bool treatRecsAsLoops) const
+{
+ if (isSequencerRunning && ch.shared->recStatus.load() == ChannelStatus::PLAY && !treatRecsAsLoops)
+ kill(ch);
+}
+} // namespace giada::m
\ No newline at end of file
#ifndef G_CHANNEL_SAMPLE_REACTOR_H
#define G_CHANNEL_SAMPLE_REACTOR_H
+#include "core/conf.h"
#include "core/eventDispatcher.h"
#include "core/quantizer.h"
-namespace giada::m::channel
+namespace giada::m::model
{
-struct Data;
+class Model;
}
-/* sampleReactor
+namespace giada::m
+{
+class Channel;
+class Sequencer;
+
+/* SampleReactor
Reacts to manual events sent to Sample Channels: key press, key release,
sequencer stop, ... . */
-namespace giada::m::sampleReactor
-{
-struct Data
+class SampleReactor final
{
- Data(ID channelId);
- Data(const Data&) = default;
- Data(Data&&) = default;
- Data& operator=(const Data&) = default;
- Data& operator=(Data&&) = default;
+public:
+ SampleReactor(ID channelId, Sequencer&, model::Model&);
+
+ void react(Channel&, const EventDispatcher::Event&, Sequencer&, const Conf::Data&) const;
+
+private:
+ void toggleReadActions(Channel&, bool isSequencerRunning, bool treatRecsAsLoops) const;
+ void onStopBySeq(Channel&, bool chansStopOnSeqHalt) const;
+ void release(Channel&, Sequencer&) const;
+ void kill(Channel&) const;
+ void press(Channel&, Sequencer&, int velocity) const;
+ ChannelStatus pressWhilePlay(Channel&, Sequencer&, SamplePlayerMode, bool isLoop) const;
+ ChannelStatus pressWhileOff(Channel&, Sequencer&, int velocity, bool isLoop) const;
+ void reset(Channel&) const;
+ void rewind(Channel&, Frame localFrame) const;
};
-void react(channel::Data& ch, const eventDispatcher::Event& e);
-} // namespace giada::m::sampleReactor
+} // namespace giada::m
#endif
* -------------------------------------------------------------------------- */
#include "waveReader.h"
-#include "core/audioBuffer.h"
#include "core/const.h"
#include "core/model/model.h"
#include "core/wave.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include "utils/log.h"
#include <algorithm>
#include <cassert>
/* -------------------------------------------------------------------------- */
-WaveReader::Result WaveReader::fill(AudioBuffer& out, Frame start, Frame max,
+WaveReader::Result WaveReader::fill(mcl::AudioBuffer& out, Frame start, Frame max,
Frame offset, float pitch) const
{
assert(wave != nullptr);
/* -------------------------------------------------------------------------- */
-WaveReader::Result WaveReader::fillResampled(AudioBuffer& dest, Frame start,
+WaveReader::Result WaveReader::fillResampled(mcl::AudioBuffer& dest, Frame start,
Frame max, Frame offset, float pitch) const
{
Resampler::Result res = m_resampler->process(
/* -------------------------------------------------------------------------- */
-WaveReader::Result WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame max, Frame offset) const
+WaveReader::Result WaveReader::fillCopy(mcl::AudioBuffer& dest, Frame start,
+ Frame max, Frame offset) const
{
Frame used = dest.countFrames() - offset;
if (used > max - start)
#include "core/types.h"
+namespace mcl
+{
+class AudioBuffer;
+}
+
namespace giada::m
{
class Wave;
-class AudioBuffer;
class Resampler;
class WaveReader final
{
Fills audio buffer 'out' with data coming from Wave, copying it from 'start'
frame up to 'max'. The buffer is filled starting at 'offset'. */
- Result fill(AudioBuffer& out, Frame start, Frame max, Frame offset,
+ Result fill(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset,
float pitch) const;
/* last
Wave* wave;
private:
- Result fillResampled(AudioBuffer& out, Frame start, Frame max, Frame offset,
+ Result fillResampled(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset,
float pitch) const;
- Result fillCopy(AudioBuffer& out, Frame start, Frame max, Frame offset) const;
+ Result fillCopy(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset) const;
Resampler* m_resampler;
};
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "clock.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "glue/events.h"
-#include "utils/log.h"
-#include "utils/math.h"
-#include <atomic>
-#include <cassert>
-
-namespace giada::m::clock
-{
-namespace
-{
-/* quantizerStep_
-Tells how many frames to wait to perform a quantized action. */
-
-int quantizerStep_ = 1;
-
-/* midiTC*
-MIDI timecode variables. */
-
-int midiTCrate_ = 0; // Send MTC data every midiTCrate_ frames
-int midiTCframes_ = 0;
-int midiTCseconds_ = 0;
-int midiTCminutes_ = 0;
-int midiTChours_ = 0;
-
-#ifdef WITH_AUDIO_JACK
-kernelAudio::JackState jackStatePrev_;
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-/* recomputeFrames_
-Updates bpm, frames, beats and so on. Private version. */
-
-void recomputeFrames_(model::Clock& c)
-{
- c.framesInLoop = static_cast<int>((conf::conf.samplerate * (60.0f / c.bpm)) * c.beats);
- c.framesInBar = static_cast<int>(c.framesInLoop / (float)c.bars);
- c.framesInBeat = static_cast<int>(c.framesInLoop / (float)c.beats);
- c.framesInSeq = c.framesInBeat * G_MAX_BEATS;
-
- if (c.quantize != 0)
- quantizerStep_ = c.framesInBeat / c.quantize;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setBpm_(float current)
-{
- float ratio = model::get().clock.bpm / current;
-
- model::get().clock.bpm = current;
- recomputeFrames_(model::get().clock);
-
- m::recorderHandler::updateBpm(ratio, quantizerStep_);
-
- model::swap(model::SwapType::HARD);
-
- u::log::print("[clock::setBpm_] Bpm changed to %f\n", current);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init(int sampleRate, float midiTCfps)
-{
- midiTCrate_ = static_cast<int>((sampleRate / midiTCfps) * G_MAX_IO_CHANS); // stereo values
-
- model::get().clock.bars = G_DEFAULT_BARS;
- model::get().clock.beats = G_DEFAULT_BEATS;
- model::get().clock.bpm = G_DEFAULT_BPM;
- model::get().clock.quantize = G_DEFAULT_QUANTIZE;
- recomputeFrames_(model::get().clock);
-
- model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void recomputeFrames()
-{
- recomputeFrames_(model::get().clock);
- model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isRunning()
-{
- return model::get().clock.status == ClockStatus::RUNNING;
-}
-
-bool isActive()
-{
- const model::Clock& c = model::get().clock;
- return c.status == ClockStatus::RUNNING || c.status == ClockStatus::WAITING;
-}
-
-bool quantoHasPassed()
-{
- const model::Clock& c = model::get().clock;
- return clock::getQuantizerValue() != 0 && c.state->currentFrame.load() % quantizerStep_ == 0;
-}
-
-bool isOnBar()
-{
- const model::Clock& c = model::get().clock;
-
- int currentFrame = c.state->currentFrame.load();
-
- if (c.status == ClockStatus::WAITING || currentFrame == 0)
- return false;
- return currentFrame % c.framesInBar == 0;
-}
-
-bool isOnBeat()
-{
- const model::Clock& c = model::get().clock;
-
- if (c.status == ClockStatus::WAITING)
- return c.state->currentFrameWait.load() % c.framesInBeat == 0;
- return c.state->currentFrame.load() % c.framesInBeat == 0;
-}
-
-bool isOnFirstBeat()
-{
- return model::get().clock.state->currentFrame.load() == 0;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setBpm(float b)
-{
- b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
-
- /* If JACK is being used, let it handle the bpm change. */
-
-#ifdef WITH_AUDIO_JACK
- if (kernelAudio::getAPI() == G_SYS_API_JACK)
- {
- kernelAudio::jackSetBpm(b);
- return;
- }
-#endif
-
- setBpm_(b);
-}
-
-void setBeats(int newBeats, int newBars)
-{
- newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
- newBars = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
-
- model::get().clock.beats = newBeats;
- model::get().clock.bars = newBars;
- recomputeFrames_(model::get().clock);
-
- model::swap(model::SwapType::HARD);
-}
-
-void setQuantize(int q)
-{
- model::get().clock.quantize = q;
- recomputeFrames_(model::get().clock);
-
- model::swap(model::SwapType::HARD);
-}
-
-void setStatus(ClockStatus s)
-{
- model::get().clock.status = s;
- model::swap(model::SwapType::SOFT);
-
- if (s == ClockStatus::RUNNING)
- {
- if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- {
- kernelMidi::send(MIDI_START, -1, -1);
- kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
- }
- }
- else if (s == ClockStatus::STOPPED)
- {
- if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- kernelMidi::send(MIDI_STOP, -1, -1);
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void advance(Frame amount)
-{
- const model::Clock& c = model::get().clock;
-
- if (c.status == ClockStatus::WAITING)
- {
- int f = (c.state->currentFrameWait.load() + amount) % c.framesInLoop;
- c.state->currentFrameWait.store(f);
- return;
- }
-
- int f = (c.state->currentFrame.load() + amount) % c.framesInLoop;
- int b = f / c.framesInBeat;
-
- c.state->currentFrame.store(f);
- c.state->currentBeat.store(b);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void rewind()
-{
- const model::Clock& c = model::get().clock;
-
- c.state->currentFrame.store(0);
- c.state->currentBeat.store(0);
- c.state->currentFrameWait.store(0);
-
- sendMIDIrewind();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void sendMIDIsync()
-{
- const model::Clock& c = model::get().clock;
-
- /* Sending MIDI sync while waiting is meaningless. */
-
- if (c.status == ClockStatus::WAITING)
- return;
-
- int currentFrame = c.state->currentFrame.load();
-
- /* TODO - only Master (_M) is implemented so far. */
-
- if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- {
- if (currentFrame % (c.framesInBeat / 24) == 0)
- kernelMidi::send(MIDI_CLOCK, -1, -1);
- return;
- }
-
- if (conf::conf.midiSync == MIDI_SYNC_MTC_M)
- {
-
- /* check if a new timecode frame has passed. If so, send MIDI TC
- * quarter frames. 8 quarter frames, divided in two branches:
- * 1-4 and 5-8. We check timecode frame's parity: if even, send
- * range 1-4, if odd send 5-8. */
-
- if (currentFrame % midiTCrate_ != 0) // no timecode frame passed
- return;
-
- /* frame low nibble
- * frame high nibble
- * seconds low nibble
- * seconds high nibble */
-
- if (midiTCframes_ % 2 == 0)
- {
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes_ & 0x0F) | 0x00, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCframes_ >> 4) | 0x10, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds_ & 0x0F) | 0x20, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCseconds_ >> 4) | 0x30, -1);
- }
-
- /* minutes low nibble
- * minutes high nibble
- * hours low nibble
- * hours high nibble SMPTE frame rate */
-
- else
- {
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes_ & 0x0F) | 0x40, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTCminutes_ >> 4) | 0x50, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours_ & 0x0F) | 0x60, -1);
- kernelMidi::send(MIDI_MTC_QUARTER, (midiTChours_ >> 4) | 0x70, -1);
- }
-
- midiTCframes_++;
-
- /* check if total timecode frames are greater than timecode fps:
- * if so, a second has passed */
-
- if (midiTCframes_ > conf::conf.midiTCfps)
- {
- midiTCframes_ = 0;
- midiTCseconds_++;
- if (midiTCseconds_ >= 60)
- {
- midiTCminutes_++;
- midiTCseconds_ = 0;
- if (midiTCminutes_ >= 60)
- {
- midiTChours_++;
- midiTCminutes_ = 0;
- }
- }
- //u::log::print("%d:%d:%d:%d\n", midiTChours_, midiTCminutes_, midiTCseconds_, midiTCframes_);
- }
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void sendMIDIrewind()
-{
- midiTCframes_ = 0;
- midiTCseconds_ = 0;
- midiTCminutes_ = 0;
- midiTChours_ = 0;
-
- /* For cueing the slave to a particular start point, Quarter Frame
- * messages are not used. Instead, an MTC Full Frame message should
- * be sent. The Full Frame is a SysEx message that encodes the entire
- * SMPTE time in one message */
-
- if (conf::conf.midiSync == MIDI_SYNC_MTC_M)
- {
- kernelMidi::send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0
- kernelMidi::send(0x01, 0x01, 0x00); // hours 0
- kernelMidi::send(0x00, 0x00, 0x00); // mins, secs, frames 0
- kernelMidi::send(MIDI_EOX, -1, -1); // end of sysex
- }
- else if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-void recvJackSync()
-{
- /* TODO - these things should be processed by a higher level,
- above clock:: ----> clockManager */
-
- kernelAudio::JackState jackStateCurr = kernelAudio::jackTransportQuery();
-
- if (jackStateCurr != jackStatePrev_)
- {
- if (jackStateCurr.frame != jackStatePrev_.frame && jackStateCurr.frame == 0)
- {
- G_DEBUG("JackState received - rewind to frame 0");
- sequencer::rawRewind();
- }
-
- // jackStateCurr.bpm == 0 if JACK doesn't send that info
- if (jackStateCurr.bpm != jackStatePrev_.bpm && jackStateCurr.bpm > 1.0f)
- {
- G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
- setBpm_(jackStateCurr.bpm);
- }
-
- if (jackStateCurr.running != jackStatePrev_.running)
- {
- G_DEBUG("JackState received - running=" << jackStateCurr.running);
- jackStateCurr.running ? sequencer::rawStart() : sequencer::rawStop();
- }
- }
-
- jackStatePrev_ = jackStateCurr;
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-bool canQuantize()
-{
- const model::Clock& c = model::get().clock;
-
- return c.quantize > 0 && c.status == ClockStatus::RUNNING;
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame quantize(Frame f)
-{
- if (!canQuantize())
- return f;
- return u::math::quantize(f, quantizerStep_) % getFramesInLoop(); // No overflow
-}
-
-/* -------------------------------------------------------------------------- */
-
-int getCurrentFrame() { return model::get().clock.state->currentFrame.load(); }
-int getCurrentBeat() { return model::get().clock.state->currentBeat.load(); }
-int getQuantizerStep() { return quantizerStep_; }
-ClockStatus getStatus() { return model::get().clock.status; }
-int getFramesInLoop() { return model::get().clock.framesInLoop; }
-int getFramesInBar() { return model::get().clock.framesInBar; }
-int getFramesInBeat() { return model::get().clock.framesInBeat; }
-int getFramesInSeq() { return model::get().clock.framesInSeq; }
-int getQuantizerValue() { return model::get().clock.quantize; }
-float getBpm() { return model::get().clock.bpm; }
-int getBeats() { return model::get().clock.beats; }
-int getBars() { return model::get().clock.bars; }
-
-/* -------------------------------------------------------------------------- */
-
-float getCurrentSecond()
-{
- return getCurrentFrame() / static_cast<float>(conf::conf.samplerate);
-}
-
-/* -------------------------------------------------------------------------- */
-
-Frame getMaxFramesInLoop()
-{
- return (conf::conf.samplerate * (60.0f / G_MIN_BPM)) * getBeats();
-}
-
-/* -------------------------------------------------------------------------- */
-
-float calcBpmFromRec(Frame recordedFrames)
-{
- return (60.0f * getBeats()) / (recordedFrames / static_cast<float>(conf::conf.samplerate));
-}
-} // namespace giada::m::clock
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_CLOCK_H
-#define G_CLOCK_H
-
-#include "types.h"
-
-namespace giada::m::clock
-{
-void init(int sampleRate, float midiTCfps);
-
-/* recomputeFrames
-Updates bpm, frames, beats and so on. */
-
-void recomputeFrames();
-
-/* sendMIDIsync
-Generates MIDI sync output data. */
-/*TODO - move this to giada::m::sync*/
-void sendMIDIsync();
-
-/* sendMIDIrewind
-Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */
-/*TODO - move this to giada::m::sync*/
-void sendMIDIrewind();
-
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) || defined(G_OS_MAC)
-/*TODO - move this to giada::m::sync*/
-void recvJackSync();
-#endif
-
-float getBpm();
-int getBeats();
-int getBars();
-int getCurrentBeat();
-int getCurrentFrame();
-float getCurrentSecond();
-int getFramesInBar();
-int getFramesInBeat();
-int getFramesInLoop();
-int getFramesInSeq();
-int getQuantizerValue();
-int getQuantizerStep();
-ClockStatus getStatus();
-
-/* getMaxFramesInLoop
-Returns how many frames the current loop length might contain at the slowest
-speed possible (G_MIN_BPM). Call this whenever you change the number or beats. */
-
-Frame getMaxFramesInLoop();
-
-/* advance
-Increases current frame by a specific amount. */
-
-void advance(Frame amount);
-
-/* quantoHasPassed
-Tells whether a quantizer unit has passed yet. */
-
-bool quantoHasPassed();
-
-/* canQuantize
-Tells whether the quantizer value is > 0 and the clock is running. */
-
-bool canQuantize();
-
-/* quantize
-Quantizes the global frame 'f'. */
-
-Frame quantize(Frame f);
-
-void setBpm(float b);
-void setBeats(int beats, int bars);
-void setQuantize(int q);
-
-/* isRunning
-When clock is actually moving forward, i.e. ClockStatus == RUNNING. */
-
-bool isRunning();
-
-/* isActive
-Clock is enabled, but might be in wait mode, i.e. ClockStatus == RUNNING or
-ClockStatus == WAITING. */
-
-bool isActive();
-
-bool isOnBeat();
-bool isOnBar();
-bool isOnFirstBeat();
-
-void rewind();
-void setStatus(ClockStatus s);
-
-/* calcBpmFromRec
-Given the amount of recorded frames, returns the speed of the current
-performance. Used while input recording in FREE mode. */
-
-float calcBpmFromRec(Frame recordedFrames);
-} // namespace giada::m::clock
-
-#endif
*
* -------------------------------------------------------------------------- */
-#include "conf.h"
+#include "core/conf.h"
#include "core/const.h"
#include "core/types.h"
#include "deps/json/single_include/nlohmann/json.hpp"
namespace nl = nlohmann;
-namespace giada::m::conf
+namespace giada::m
{
-namespace
+Conf::Conf()
{
-std::string confFilePath_ = "";
-std::string confDirPath_ = "";
+ /* Initialize m_confFilePath, i.e. the configuration file. In windows it is
+ in the same dir of the .exe, while in Linux and OS X in ~/.giada */
-/* -------------------------------------------------------------------------- */
-
-void sanitize_()
-{
- conf.soundDeviceOut = std::max(0, conf.soundDeviceOut);
- conf.channelsOutCount = G_MAX_IO_CHANS;
- conf.channelsOutStart = std::max(0, conf.channelsOutStart);
- conf.channelsInCount = std::max(1, conf.channelsInCount);
- conf.channelsInStart = std::max(0, conf.channelsInStart);
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* createConfigFolder
-Creates local folder where to put the configuration file. Path differs from OS
-to OS. */
-
-int createConfigFolder_()
-{
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
- if (u::fs::dirExists(confDirPath_))
- return 1;
-
- u::log::print("[conf::createConfigFolder] .giada folder not present. Updating...\n");
-
- if (u::fs::mkdir(confDirPath_))
- {
- u::log::print("[conf::createConfigFolder] status: ok\n");
- return 1;
- }
- else
- {
- u::log::print("[conf::createConfigFolder] status: error!\n");
- return 0;
- }
-
-#else // Windows: nothing to do
-
- return 1;
-
-#endif
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Conf conf;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
- conf = Conf();
-
- /* Initialize confFilePath_, i.e. the configuration file. In windows it is in
- * the same dir of the .exe, while in Linux and OS X in ~/.giada */
-
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
-
- confFilePath_ = u::fs::getHomePath() + G_SLASH + CONF_FILENAME;
- confDirPath_ = u::fs::getHomePath() + G_SLASH;
+ m_confFilePath = u::fs::getHomePath() + G_SLASH + CONF_FILENAME;
+ m_confDirPath = u::fs::getHomePath() + G_SLASH;
#elif defined(_WIN32)
- confFilePath_ = CONF_FILENAME;
- confDirPath_ = "";
+ m_confFilePath = CONF_FILENAME;
+ m_confDirPath = "";
#endif
}
/* -------------------------------------------------------------------------- */
-bool read()
+bool Conf::read()
{
- init();
+ data = Data(); // Reset it first
- std::ifstream ifs(confFilePath_);
+ std::ifstream ifs(m_confFilePath);
if (!ifs.good())
return false;
nl::json j = nl::json::parse(ifs);
- conf.logMode = j.value(CONF_KEY_LOG_MODE, conf.logMode);
- conf.showTooltips = j.value(CONF_KEY_SHOW_TOOLTIPS, conf.showTooltips);
- conf.soundSystem = j.value(CONF_KEY_SOUND_SYSTEM, conf.soundSystem);
- conf.soundDeviceOut = j.value(CONF_KEY_SOUND_DEVICE_OUT, conf.soundDeviceOut);
- conf.soundDeviceIn = j.value(CONF_KEY_SOUND_DEVICE_IN, conf.soundDeviceIn);
- conf.channelsOutCount = j.value(CONF_KEY_CHANNELS_OUT_COUNT, conf.channelsOutCount);
- conf.channelsOutStart = j.value(CONF_KEY_CHANNELS_OUT_START, conf.channelsOutStart);
- conf.channelsInCount = j.value(CONF_KEY_CHANNELS_IN_COUNT, conf.channelsInCount);
- conf.channelsInStart = j.value(CONF_KEY_CHANNELS_IN_START, conf.channelsInStart);
- conf.samplerate = j.value(CONF_KEY_SAMPLERATE, conf.samplerate);
- conf.buffersize = j.value(CONF_KEY_BUFFER_SIZE, conf.buffersize);
- conf.limitOutput = j.value(CONF_KEY_LIMIT_OUTPUT, conf.limitOutput);
- conf.rsmpQuality = j.value(CONF_KEY_RESAMPLE_QUALITY, conf.rsmpQuality);
- conf.midiSystem = j.value(CONF_KEY_MIDI_SYSTEM, conf.midiSystem);
- conf.midiPortOut = j.value(CONF_KEY_MIDI_PORT_OUT, conf.midiPortOut);
- conf.midiPortIn = j.value(CONF_KEY_MIDI_PORT_IN, conf.midiPortIn);
- conf.midiMapPath = j.value(CONF_KEY_MIDIMAP_PATH, conf.midiMapPath);
- conf.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap);
- conf.midiSync = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync);
- conf.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps);
- conf.chansStopOnSeqHalt = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, conf.chansStopOnSeqHalt);
- conf.treatRecsAsLoops = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, conf.treatRecsAsLoops);
- conf.inputMonitorDefaultOn = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, conf.inputMonitorDefaultOn);
- conf.overdubProtectionDefaultOn = j.value(CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON, conf.overdubProtectionDefaultOn);
- conf.pluginPath = j.value(CONF_KEY_PLUGINS_PATH, conf.pluginPath);
- conf.patchPath = j.value(CONF_KEY_PATCHES_PATH, conf.patchPath);
- conf.samplePath = j.value(CONF_KEY_SAMPLES_PATH, conf.samplePath);
- conf.mainWindowX = j.value(CONF_KEY_MAIN_WINDOW_X, conf.mainWindowX);
- conf.mainWindowY = j.value(CONF_KEY_MAIN_WINDOW_Y, conf.mainWindowY);
- conf.mainWindowW = j.value(CONF_KEY_MAIN_WINDOW_W, conf.mainWindowW);
- conf.mainWindowH = j.value(CONF_KEY_MAIN_WINDOW_H, conf.mainWindowH);
- conf.browserX = j.value(CONF_KEY_BROWSER_X, conf.browserX);
- conf.browserY = j.value(CONF_KEY_BROWSER_Y, conf.browserY);
- conf.browserW = j.value(CONF_KEY_BROWSER_W, conf.browserW);
- conf.browserH = j.value(CONF_KEY_BROWSER_H, conf.browserH);
- conf.browserPosition = j.value(CONF_KEY_BROWSER_POSITION, conf.browserPosition);
- conf.browserLastPath = j.value(CONF_KEY_BROWSER_LAST_PATH, conf.browserLastPath);
- conf.browserLastValue = j.value(CONF_KEY_BROWSER_LAST_VALUE, conf.browserLastValue);
- conf.actionEditorX = j.value(CONF_KEY_ACTION_EDITOR_X, conf.actionEditorX);
- conf.actionEditorY = j.value(CONF_KEY_ACTION_EDITOR_Y, conf.actionEditorY);
- conf.actionEditorW = j.value(CONF_KEY_ACTION_EDITOR_W, conf.actionEditorW);
- conf.actionEditorH = j.value(CONF_KEY_ACTION_EDITOR_H, conf.actionEditorH);
- conf.actionEditorZoom = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, conf.actionEditorZoom);
- conf.actionEditorGridVal = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, conf.actionEditorGridVal);
- conf.actionEditorGridOn = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, conf.actionEditorGridOn);
- conf.sampleEditorX = j.value(CONF_KEY_SAMPLE_EDITOR_X, conf.sampleEditorX);
- conf.sampleEditorY = j.value(CONF_KEY_SAMPLE_EDITOR_Y, conf.sampleEditorY);
- conf.sampleEditorW = j.value(CONF_KEY_SAMPLE_EDITOR_W, conf.sampleEditorW);
- conf.sampleEditorH = j.value(CONF_KEY_SAMPLE_EDITOR_H, conf.sampleEditorH);
- conf.sampleEditorGridVal = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, conf.sampleEditorGridVal);
- conf.sampleEditorGridOn = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, conf.sampleEditorGridOn);
- conf.pianoRollY = j.value(CONF_KEY_PIANO_ROLL_Y, conf.pianoRollY);
- conf.pianoRollH = j.value(CONF_KEY_PIANO_ROLL_H, conf.pianoRollH);
- conf.sampleActionEditorH = j.value(CONF_KEY_SAMPLE_ACTION_EDITOR_H, conf.sampleActionEditorH);
- conf.velocityEditorH = j.value(CONF_KEY_VELOCITY_EDITOR_H, conf.velocityEditorH);
- conf.envelopeEditorH = j.value(CONF_KEY_ENVELOPE_EDITOR_H, conf.envelopeEditorH);
- conf.pluginListX = j.value(CONF_KEY_PLUGIN_LIST_X, conf.pluginListX);
- conf.pluginListY = j.value(CONF_KEY_PLUGIN_LIST_Y, conf.pluginListY);
- conf.midiInputX = j.value(CONF_KEY_MIDI_INPUT_X, conf.midiInputX);
- conf.midiInputY = j.value(CONF_KEY_MIDI_INPUT_Y, conf.midiInputY);
- conf.midiInputW = j.value(CONF_KEY_MIDI_INPUT_W, conf.midiInputW);
- conf.midiInputH = j.value(CONF_KEY_MIDI_INPUT_H, conf.midiInputH);
- conf.recTriggerMode = j.value(CONF_KEY_REC_TRIGGER_MODE, conf.recTriggerMode);
- conf.recTriggerLevel = j.value(CONF_KEY_REC_TRIGGER_LEVEL, conf.recTriggerLevel);
- conf.inputRecMode = j.value(CONF_KEY_INPUT_REC_MODE, conf.inputRecMode);
- conf.midiInEnabled = j.value(CONF_KEY_MIDI_IN, conf.midiInEnabled);
- conf.midiInFilter = j.value(CONF_KEY_MIDI_IN_FILTER, conf.midiInFilter);
- conf.midiInRewind = j.value(CONF_KEY_MIDI_IN_REWIND, conf.midiInRewind);
- conf.midiInStartStop = j.value(CONF_KEY_MIDI_IN_START_STOP, conf.midiInStartStop);
- conf.midiInActionRec = j.value(CONF_KEY_MIDI_IN_ACTION_REC, conf.midiInActionRec);
- conf.midiInInputRec = j.value(CONF_KEY_MIDI_IN_INPUT_REC, conf.midiInInputRec);
- conf.midiInMetronome = j.value(CONF_KEY_MIDI_IN_METRONOME, conf.midiInMetronome);
- conf.midiInVolumeIn = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, conf.midiInVolumeIn);
- conf.midiInVolumeOut = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, conf.midiInVolumeOut);
- conf.midiInBeatDouble = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, conf.midiInBeatDouble);
- conf.midiInBeatHalf = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, conf.midiInBeatHalf);
+ data.logMode = j.value(CONF_KEY_LOG_MODE, data.logMode);
+ data.showTooltips = j.value(CONF_KEY_SHOW_TOOLTIPS, data.showTooltips);
+ data.soundSystem = j.value(CONF_KEY_SOUND_SYSTEM, data.soundSystem);
+ data.soundDeviceOut = j.value(CONF_KEY_SOUND_DEVICE_OUT, data.soundDeviceOut);
+ data.soundDeviceIn = j.value(CONF_KEY_SOUND_DEVICE_IN, data.soundDeviceIn);
+ data.channelsOutCount = j.value(CONF_KEY_CHANNELS_OUT_COUNT, data.channelsOutCount);
+ data.channelsOutStart = j.value(CONF_KEY_CHANNELS_OUT_START, data.channelsOutStart);
+ data.channelsInCount = j.value(CONF_KEY_CHANNELS_IN_COUNT, data.channelsInCount);
+ data.channelsInStart = j.value(CONF_KEY_CHANNELS_IN_START, data.channelsInStart);
+ data.samplerate = j.value(CONF_KEY_SAMPLERATE, data.samplerate);
+ data.buffersize = j.value(CONF_KEY_BUFFER_SIZE, data.buffersize);
+ data.limitOutput = j.value(CONF_KEY_LIMIT_OUTPUT, data.limitOutput);
+ data.rsmpQuality = j.value(CONF_KEY_RESAMPLE_QUALITY, data.rsmpQuality);
+ data.midiSystem = j.value(CONF_KEY_MIDI_SYSTEM, data.midiSystem);
+ data.midiPortOut = j.value(CONF_KEY_MIDI_PORT_OUT, data.midiPortOut);
+ data.midiPortIn = j.value(CONF_KEY_MIDI_PORT_IN, data.midiPortIn);
+ data.midiMapPath = j.value(CONF_KEY_MIDIMAP_PATH, data.midiMapPath);
+ data.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, data.lastFileMap);
+ data.midiSync = j.value(CONF_KEY_MIDI_SYNC, data.midiSync);
+ data.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, data.midiTCfps);
+ data.chansStopOnSeqHalt = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, data.chansStopOnSeqHalt);
+ data.treatRecsAsLoops = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, data.treatRecsAsLoops);
+ data.inputMonitorDefaultOn = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, data.inputMonitorDefaultOn);
+ data.overdubProtectionDefaultOn = j.value(CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON, data.overdubProtectionDefaultOn);
+ data.pluginPath = j.value(CONF_KEY_PLUGINS_PATH, data.pluginPath);
+ data.patchPath = j.value(CONF_KEY_PATCHES_PATH, data.patchPath);
+ data.samplePath = j.value(CONF_KEY_SAMPLES_PATH, data.samplePath);
+ data.mainWindowX = j.value(CONF_KEY_MAIN_WINDOW_X, data.mainWindowX);
+ data.mainWindowY = j.value(CONF_KEY_MAIN_WINDOW_Y, data.mainWindowY);
+ data.mainWindowW = j.value(CONF_KEY_MAIN_WINDOW_W, data.mainWindowW);
+ data.mainWindowH = j.value(CONF_KEY_MAIN_WINDOW_H, data.mainWindowH);
+ data.browserX = j.value(CONF_KEY_BROWSER_X, data.browserX);
+ data.browserY = j.value(CONF_KEY_BROWSER_Y, data.browserY);
+ data.browserW = j.value(CONF_KEY_BROWSER_W, data.browserW);
+ data.browserH = j.value(CONF_KEY_BROWSER_H, data.browserH);
+ data.browserPosition = j.value(CONF_KEY_BROWSER_POSITION, data.browserPosition);
+ data.browserLastPath = j.value(CONF_KEY_BROWSER_LAST_PATH, data.browserLastPath);
+ data.browserLastValue = j.value(CONF_KEY_BROWSER_LAST_VALUE, data.browserLastValue);
+ data.actionEditorX = j.value(CONF_KEY_ACTION_EDITOR_X, data.actionEditorX);
+ data.actionEditorY = j.value(CONF_KEY_ACTION_EDITOR_Y, data.actionEditorY);
+ data.actionEditorW = j.value(CONF_KEY_ACTION_EDITOR_W, data.actionEditorW);
+ data.actionEditorH = j.value(CONF_KEY_ACTION_EDITOR_H, data.actionEditorH);
+ data.actionEditorZoom = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, data.actionEditorZoom);
+ data.actionEditorSplitH = j.value(CONF_KEY_ACTION_EDITOR_SPLIT_H, data.actionEditorSplitH);
+ data.actionEditorGridVal = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, data.actionEditorGridVal);
+ data.actionEditorGridOn = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, data.actionEditorGridOn);
+ data.actionEditorPianoRollY = j.value(CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y, data.actionEditorPianoRollY);
+ data.sampleEditorX = j.value(CONF_KEY_SAMPLE_EDITOR_X, data.sampleEditorX);
+ data.sampleEditorY = j.value(CONF_KEY_SAMPLE_EDITOR_Y, data.sampleEditorY);
+ data.sampleEditorW = j.value(CONF_KEY_SAMPLE_EDITOR_W, data.sampleEditorW);
+ data.sampleEditorH = j.value(CONF_KEY_SAMPLE_EDITOR_H, data.sampleEditorH);
+ data.sampleEditorGridVal = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, data.sampleEditorGridVal);
+ data.sampleEditorGridOn = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, data.sampleEditorGridOn);
+ data.pluginListX = j.value(CONF_KEY_PLUGIN_LIST_X, data.pluginListX);
+ data.pluginListY = j.value(CONF_KEY_PLUGIN_LIST_Y, data.pluginListY);
+ data.midiInputX = j.value(CONF_KEY_MIDI_INPUT_X, data.midiInputX);
+ data.midiInputY = j.value(CONF_KEY_MIDI_INPUT_Y, data.midiInputY);
+ data.midiInputW = j.value(CONF_KEY_MIDI_INPUT_W, data.midiInputW);
+ data.midiInputH = j.value(CONF_KEY_MIDI_INPUT_H, data.midiInputH);
+ data.recTriggerMode = j.value(CONF_KEY_REC_TRIGGER_MODE, data.recTriggerMode);
+ data.recTriggerLevel = j.value(CONF_KEY_REC_TRIGGER_LEVEL, data.recTriggerLevel);
+ data.inputRecMode = j.value(CONF_KEY_INPUT_REC_MODE, data.inputRecMode);
+ data.midiInEnabled = j.value(CONF_KEY_MIDI_IN, data.midiInEnabled);
+ data.midiInFilter = j.value(CONF_KEY_MIDI_IN_FILTER, data.midiInFilter);
+ data.midiInRewind = j.value(CONF_KEY_MIDI_IN_REWIND, data.midiInRewind);
+ data.midiInStartStop = j.value(CONF_KEY_MIDI_IN_START_STOP, data.midiInStartStop);
+ data.midiInActionRec = j.value(CONF_KEY_MIDI_IN_ACTION_REC, data.midiInActionRec);
+ data.midiInInputRec = j.value(CONF_KEY_MIDI_IN_INPUT_REC, data.midiInInputRec);
+ data.midiInMetronome = j.value(CONF_KEY_MIDI_IN_METRONOME, data.midiInMetronome);
+ data.midiInVolumeIn = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, data.midiInVolumeIn);
+ data.midiInVolumeOut = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, data.midiInVolumeOut);
+ data.midiInBeatDouble = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, data.midiInBeatDouble);
+ data.midiInBeatHalf = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, data.midiInBeatHalf);
#ifdef WITH_VST
- conf.pluginChooserX = j.value(CONF_KEY_PLUGIN_CHOOSER_X, conf.pluginChooserX);
- conf.pluginChooserY = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, conf.pluginChooserY);
- conf.pluginChooserW = j.value(CONF_KEY_PLUGIN_CHOOSER_W, conf.pluginChooserW);
- conf.pluginChooserH = j.value(CONF_KEY_PLUGIN_CHOOSER_H, conf.pluginChooserH);
- conf.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, conf.pluginSortMethod);
+ data.pluginChooserX = j.value(CONF_KEY_PLUGIN_CHOOSER_X, data.pluginChooserX);
+ data.pluginChooserY = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, data.pluginChooserY);
+ data.pluginChooserW = j.value(CONF_KEY_PLUGIN_CHOOSER_W, data.pluginChooserW);
+ data.pluginChooserH = j.value(CONF_KEY_PLUGIN_CHOOSER_H, data.pluginChooserH);
+ data.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, data.pluginSortMethod);
#endif
- sanitize_();
+ sanitize();
return true;
}
/* -------------------------------------------------------------------------- */
-bool write()
+bool Conf::write() const
{
- if (!createConfigFolder_())
+ if (!createConfigFolder())
return false;
nl::json j;
j[CONF_KEY_HEADER] = "GIADACFG";
- j[CONF_KEY_LOG_MODE] = conf.logMode;
- j[CONF_KEY_SHOW_TOOLTIPS] = conf.showTooltips;
- j[CONF_KEY_SOUND_SYSTEM] = conf.soundSystem;
- j[CONF_KEY_SOUND_DEVICE_OUT] = conf.soundDeviceOut;
- j[CONF_KEY_SOUND_DEVICE_IN] = conf.soundDeviceIn;
- j[CONF_KEY_CHANNELS_OUT_COUNT] = conf.channelsOutCount;
- j[CONF_KEY_CHANNELS_OUT_START] = conf.channelsOutStart;
- j[CONF_KEY_CHANNELS_IN_COUNT] = conf.channelsInCount;
- j[CONF_KEY_CHANNELS_IN_START] = conf.channelsInStart;
- j[CONF_KEY_SAMPLERATE] = conf.samplerate;
- j[CONF_KEY_BUFFER_SIZE] = conf.buffersize;
- j[CONF_KEY_LIMIT_OUTPUT] = conf.limitOutput;
- j[CONF_KEY_RESAMPLE_QUALITY] = conf.rsmpQuality;
- j[CONF_KEY_MIDI_SYSTEM] = conf.midiSystem;
- j[CONF_KEY_MIDI_PORT_OUT] = conf.midiPortOut;
- j[CONF_KEY_MIDI_PORT_IN] = conf.midiPortIn;
- j[CONF_KEY_MIDIMAP_PATH] = conf.midiMapPath;
- j[CONF_KEY_LAST_MIDIMAP] = conf.lastFileMap;
- j[CONF_KEY_MIDI_SYNC] = conf.midiSync;
- j[CONF_KEY_MIDI_TC_FPS] = conf.midiTCfps;
- j[CONF_KEY_MIDI_IN] = conf.midiInEnabled;
- j[CONF_KEY_MIDI_IN_FILTER] = conf.midiInFilter;
- j[CONF_KEY_MIDI_IN_REWIND] = conf.midiInRewind;
- j[CONF_KEY_MIDI_IN_START_STOP] = conf.midiInStartStop;
- j[CONF_KEY_MIDI_IN_ACTION_REC] = conf.midiInActionRec;
- j[CONF_KEY_MIDI_IN_INPUT_REC] = conf.midiInInputRec;
- j[CONF_KEY_MIDI_IN_METRONOME] = conf.midiInMetronome;
- j[CONF_KEY_MIDI_IN_VOLUME_IN] = conf.midiInVolumeIn;
- j[CONF_KEY_MIDI_IN_VOLUME_OUT] = conf.midiInVolumeOut;
- j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = conf.midiInBeatDouble;
- j[CONF_KEY_MIDI_IN_BEAT_HALF] = conf.midiInBeatHalf;
- j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT] = conf.chansStopOnSeqHalt;
- j[CONF_KEY_TREAT_RECS_AS_LOOPS] = conf.treatRecsAsLoops;
- j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON] = conf.inputMonitorDefaultOn;
- j[CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON] = conf.overdubProtectionDefaultOn;
- j[CONF_KEY_PLUGINS_PATH] = conf.pluginPath;
- j[CONF_KEY_PATCHES_PATH] = conf.patchPath;
- j[CONF_KEY_SAMPLES_PATH] = conf.samplePath;
- j[CONF_KEY_MAIN_WINDOW_X] = conf.mainWindowX;
- j[CONF_KEY_MAIN_WINDOW_Y] = conf.mainWindowY;
- j[CONF_KEY_MAIN_WINDOW_W] = conf.mainWindowW;
- j[CONF_KEY_MAIN_WINDOW_H] = conf.mainWindowH;
- j[CONF_KEY_BROWSER_X] = conf.browserX;
- j[CONF_KEY_BROWSER_Y] = conf.browserY;
- j[CONF_KEY_BROWSER_W] = conf.browserW;
- j[CONF_KEY_BROWSER_H] = conf.browserH;
- j[CONF_KEY_BROWSER_POSITION] = conf.browserPosition;
- j[CONF_KEY_BROWSER_LAST_PATH] = conf.browserLastPath;
- j[CONF_KEY_BROWSER_LAST_VALUE] = conf.browserLastValue;
- j[CONF_KEY_ACTION_EDITOR_X] = conf.actionEditorX;
- j[CONF_KEY_ACTION_EDITOR_Y] = conf.actionEditorY;
- j[CONF_KEY_ACTION_EDITOR_W] = conf.actionEditorW;
- j[CONF_KEY_ACTION_EDITOR_H] = conf.actionEditorH;
- j[CONF_KEY_ACTION_EDITOR_ZOOM] = conf.actionEditorZoom;
- j[CONF_KEY_ACTION_EDITOR_GRID_VAL] = conf.actionEditorGridVal;
- j[CONF_KEY_ACTION_EDITOR_GRID_ON] = conf.actionEditorGridOn;
- j[CONF_KEY_SAMPLE_EDITOR_X] = conf.sampleEditorX;
- j[CONF_KEY_SAMPLE_EDITOR_Y] = conf.sampleEditorY;
- j[CONF_KEY_SAMPLE_EDITOR_W] = conf.sampleEditorW;
- j[CONF_KEY_SAMPLE_EDITOR_H] = conf.sampleEditorH;
- j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL] = conf.sampleEditorGridVal;
- j[CONF_KEY_SAMPLE_EDITOR_GRID_ON] = conf.sampleEditorGridOn;
- j[CONF_KEY_PIANO_ROLL_Y] = conf.pianoRollY;
- j[CONF_KEY_PIANO_ROLL_H] = conf.pianoRollH;
- j[CONF_KEY_SAMPLE_ACTION_EDITOR_H] = conf.sampleActionEditorH;
- j[CONF_KEY_VELOCITY_EDITOR_H] = conf.velocityEditorH;
- j[CONF_KEY_ENVELOPE_EDITOR_H] = conf.envelopeEditorH;
- j[CONF_KEY_PLUGIN_LIST_X] = conf.pluginListX;
- j[CONF_KEY_PLUGIN_LIST_Y] = conf.pluginListY;
- j[CONF_KEY_MIDI_INPUT_X] = conf.midiInputX;
- j[CONF_KEY_MIDI_INPUT_Y] = conf.midiInputY;
- j[CONF_KEY_MIDI_INPUT_W] = conf.midiInputW;
- j[CONF_KEY_MIDI_INPUT_H] = conf.midiInputH;
- j[CONF_KEY_REC_TRIGGER_MODE] = static_cast<int>(conf.recTriggerMode);
- j[CONF_KEY_REC_TRIGGER_LEVEL] = conf.recTriggerLevel;
- j[CONF_KEY_INPUT_REC_MODE] = static_cast<int>(conf.inputRecMode);
+ j[CONF_KEY_LOG_MODE] = data.logMode;
+ j[CONF_KEY_SHOW_TOOLTIPS] = data.showTooltips;
+ j[CONF_KEY_SOUND_SYSTEM] = data.soundSystem;
+ j[CONF_KEY_SOUND_DEVICE_OUT] = data.soundDeviceOut;
+ j[CONF_KEY_SOUND_DEVICE_IN] = data.soundDeviceIn;
+ j[CONF_KEY_CHANNELS_OUT_COUNT] = data.channelsOutCount;
+ j[CONF_KEY_CHANNELS_OUT_START] = data.channelsOutStart;
+ j[CONF_KEY_CHANNELS_IN_COUNT] = data.channelsInCount;
+ j[CONF_KEY_CHANNELS_IN_START] = data.channelsInStart;
+ j[CONF_KEY_SAMPLERATE] = data.samplerate;
+ j[CONF_KEY_BUFFER_SIZE] = data.buffersize;
+ j[CONF_KEY_LIMIT_OUTPUT] = data.limitOutput;
+ j[CONF_KEY_RESAMPLE_QUALITY] = data.rsmpQuality;
+ j[CONF_KEY_MIDI_SYSTEM] = data.midiSystem;
+ j[CONF_KEY_MIDI_PORT_OUT] = data.midiPortOut;
+ j[CONF_KEY_MIDI_PORT_IN] = data.midiPortIn;
+ j[CONF_KEY_MIDIMAP_PATH] = data.midiMapPath;
+ j[CONF_KEY_LAST_MIDIMAP] = data.lastFileMap;
+ j[CONF_KEY_MIDI_SYNC] = data.midiSync;
+ j[CONF_KEY_MIDI_TC_FPS] = data.midiTCfps;
+ j[CONF_KEY_MIDI_IN] = data.midiInEnabled;
+ j[CONF_KEY_MIDI_IN_FILTER] = data.midiInFilter;
+ j[CONF_KEY_MIDI_IN_REWIND] = data.midiInRewind;
+ j[CONF_KEY_MIDI_IN_START_STOP] = data.midiInStartStop;
+ j[CONF_KEY_MIDI_IN_ACTION_REC] = data.midiInActionRec;
+ j[CONF_KEY_MIDI_IN_INPUT_REC] = data.midiInInputRec;
+ j[CONF_KEY_MIDI_IN_METRONOME] = data.midiInMetronome;
+ j[CONF_KEY_MIDI_IN_VOLUME_IN] = data.midiInVolumeIn;
+ j[CONF_KEY_MIDI_IN_VOLUME_OUT] = data.midiInVolumeOut;
+ j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = data.midiInBeatDouble;
+ j[CONF_KEY_MIDI_IN_BEAT_HALF] = data.midiInBeatHalf;
+ j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT] = data.chansStopOnSeqHalt;
+ j[CONF_KEY_TREAT_RECS_AS_LOOPS] = data.treatRecsAsLoops;
+ j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON] = data.inputMonitorDefaultOn;
+ j[CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON] = data.overdubProtectionDefaultOn;
+ j[CONF_KEY_PLUGINS_PATH] = data.pluginPath;
+ j[CONF_KEY_PATCHES_PATH] = data.patchPath;
+ j[CONF_KEY_SAMPLES_PATH] = data.samplePath;
+ j[CONF_KEY_MAIN_WINDOW_X] = data.mainWindowX;
+ j[CONF_KEY_MAIN_WINDOW_Y] = data.mainWindowY;
+ j[CONF_KEY_MAIN_WINDOW_W] = data.mainWindowW;
+ j[CONF_KEY_MAIN_WINDOW_H] = data.mainWindowH;
+ j[CONF_KEY_BROWSER_X] = data.browserX;
+ j[CONF_KEY_BROWSER_Y] = data.browserY;
+ j[CONF_KEY_BROWSER_W] = data.browserW;
+ j[CONF_KEY_BROWSER_H] = data.browserH;
+ j[CONF_KEY_BROWSER_POSITION] = data.browserPosition;
+ j[CONF_KEY_BROWSER_LAST_PATH] = data.browserLastPath;
+ j[CONF_KEY_BROWSER_LAST_VALUE] = data.browserLastValue;
+ j[CONF_KEY_ACTION_EDITOR_X] = data.actionEditorX;
+ j[CONF_KEY_ACTION_EDITOR_Y] = data.actionEditorY;
+ j[CONF_KEY_ACTION_EDITOR_W] = data.actionEditorW;
+ j[CONF_KEY_ACTION_EDITOR_H] = data.actionEditorH;
+ j[CONF_KEY_ACTION_EDITOR_ZOOM] = data.actionEditorZoom;
+ j[CONF_KEY_ACTION_EDITOR_SPLIT_H] = data.actionEditorSplitH;
+ j[CONF_KEY_ACTION_EDITOR_GRID_VAL] = data.actionEditorGridVal;
+ j[CONF_KEY_ACTION_EDITOR_GRID_ON] = data.actionEditorGridOn;
+ j[CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y] = data.actionEditorPianoRollY;
+ j[CONF_KEY_SAMPLE_EDITOR_X] = data.sampleEditorX;
+ j[CONF_KEY_SAMPLE_EDITOR_Y] = data.sampleEditorY;
+ j[CONF_KEY_SAMPLE_EDITOR_W] = data.sampleEditorW;
+ j[CONF_KEY_SAMPLE_EDITOR_H] = data.sampleEditorH;
+ j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL] = data.sampleEditorGridVal;
+ j[CONF_KEY_SAMPLE_EDITOR_GRID_ON] = data.sampleEditorGridOn;
+ j[CONF_KEY_PLUGIN_LIST_X] = data.pluginListX;
+ j[CONF_KEY_PLUGIN_LIST_Y] = data.pluginListY;
+ j[CONF_KEY_MIDI_INPUT_X] = data.midiInputX;
+ j[CONF_KEY_MIDI_INPUT_Y] = data.midiInputY;
+ j[CONF_KEY_MIDI_INPUT_W] = data.midiInputW;
+ j[CONF_KEY_MIDI_INPUT_H] = data.midiInputH;
+ j[CONF_KEY_REC_TRIGGER_MODE] = static_cast<int>(data.recTriggerMode);
+ j[CONF_KEY_REC_TRIGGER_LEVEL] = data.recTriggerLevel;
+ j[CONF_KEY_INPUT_REC_MODE] = static_cast<int>(data.inputRecMode);
#ifdef WITH_VST
- j[CONF_KEY_PLUGIN_CHOOSER_X] = conf.pluginChooserX;
- j[CONF_KEY_PLUGIN_CHOOSER_Y] = conf.pluginChooserY;
- j[CONF_KEY_PLUGIN_CHOOSER_W] = conf.pluginChooserW;
- j[CONF_KEY_PLUGIN_CHOOSER_H] = conf.pluginChooserH;
- j[CONF_KEY_PLUGIN_SORT_METHOD] = conf.pluginSortMethod;
+ j[CONF_KEY_PLUGIN_CHOOSER_X] = data.pluginChooserX;
+ j[CONF_KEY_PLUGIN_CHOOSER_Y] = data.pluginChooserY;
+ j[CONF_KEY_PLUGIN_CHOOSER_W] = data.pluginChooserW;
+ j[CONF_KEY_PLUGIN_CHOOSER_H] = data.pluginChooserH;
+ j[CONF_KEY_PLUGIN_SORT_METHOD] = data.pluginSortMethod;
#endif
- std::ofstream ofs(confFilePath_);
+ std::ofstream ofs(m_confFilePath);
if (!ofs.good())
{
u::log::print("[conf::write] unable to write configuration file!\n");
ofs << j;
return true;
}
-} // namespace giada::m::conf
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+bool Conf::createConfigFolder() const
+{
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+
+ if (u::fs::dirExists(m_confDirPath))
+ return true;
+
+ u::log::print("[conf::createConfigFolder] .giada folder not present. Updating...\n");
+
+ if (u::fs::mkdir(m_confDirPath))
+ {
+ u::log::print("[conf::createConfigFolder] status: ok\n");
+ return true;
+ }
+ else
+ {
+ u::log::print("[conf::createConfigFolder] status: error!\n");
+ return false;
+ }
+
+#else // Windows: nothing to do
+
+ return true;
+
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Conf::sanitize()
+{
+ data.soundDeviceOut = std::max(0, data.soundDeviceOut);
+ data.channelsOutCount = G_MAX_IO_CHANS;
+ data.channelsOutStart = std::max(0, data.channelsOutStart);
+ data.channelsInCount = std::max(1, data.channelsInCount);
+ data.channelsInStart = std::max(0, data.channelsInStart);
+
+ data.midiPortOut = std::max(-1, data.midiPortOut);
+ data.midiPortIn = std::max(-1, data.midiPortIn);
+}
+} // namespace giada::m
\ No newline at end of file
#include "utils/gui.h"
#include <string>
-namespace giada::m::conf
+namespace giada::m
{
-struct Conf
+class Conf final
{
- int logMode = LOG_MODE_MUTE;
- bool showTooltips = true;
- int soundSystem = G_DEFAULT_SOUNDSYS;
- int soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT;
- int soundDeviceIn = G_DEFAULT_SOUNDDEV_IN;
- int channelsOutCount = G_MAX_IO_CHANS;
- int channelsOutStart = 0;
- int channelsInCount = 1;
- int channelsInStart = 0;
- int samplerate = G_DEFAULT_SAMPLERATE;
- int buffersize = G_DEFAULT_BUFSIZE;
- bool limitOutput = false;
- int rsmpQuality = 0;
-
- int midiSystem = 0;
- int midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
- int midiPortIn = G_DEFAULT_MIDI_PORT_IN;
- std::string midiMapPath = "";
- std::string lastFileMap = "";
- int midiSync = MIDI_SYNC_NONE;
- float midiTCfps = 25.0f;
-
- bool chansStopOnSeqHalt = false;
- bool treatRecsAsLoops = false;
- bool inputMonitorDefaultOn = false;
- bool overdubProtectionDefaultOn = false;
-
- std::string pluginPath;
- std::string patchPath;
- std::string samplePath;
-
- int mainWindowX = u::gui::centerWindowX(G_MIN_GUI_WIDTH);
- int mainWindowY = u::gui::centerWindowY(G_MIN_GUI_HEIGHT);
- int mainWindowW = G_MIN_GUI_WIDTH;
- int mainWindowH = G_MIN_GUI_HEIGHT;
-
- int browserX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
- int browserY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
- int browserW = G_DEFAULT_SUBWINDOW_W;
- int browserH = G_DEFAULT_SUBWINDOW_H;
- int browserPosition;
- int browserLastValue;
- std::string browserLastPath;
-
- int actionEditorY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
- int actionEditorX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
- int actionEditorW = G_DEFAULT_SUBWINDOW_W;
- int actionEditorH = G_DEFAULT_SUBWINDOW_H;
- int actionEditorZoom = 100;
- int actionEditorGridVal = 0;
- int actionEditorGridOn = false;
-
- int sampleEditorX;
- int sampleEditorY;
- int sampleEditorW = G_DEFAULT_SUBWINDOW_W;
- int sampleEditorH = G_DEFAULT_SUBWINDOW_H;
- int sampleEditorGridVal = 0;
- int sampleEditorGridOn = false;
-
- int midiInputX;
- int midiInputY;
- int midiInputW = G_DEFAULT_SUBWINDOW_W;
- int midiInputH = G_DEFAULT_SUBWINDOW_H;
-
- int pianoRollY = -1;
- int pianoRollH = 422;
-
- int sampleActionEditorH = 40;
- int velocityEditorH = 40;
- int envelopeEditorH = 40;
-
- int pluginListX;
- int pluginListY;
-
- RecTriggerMode recTriggerMode = RecTriggerMode::NORMAL;
- float recTriggerLevel = G_DEFAULT_REC_TRIGGER_LEVEL;
- InputRecMode inputRecMode = InputRecMode::FREE;
-
- bool midiInEnabled = false;
- int midiInFilter = -1;
- uint32_t midiInRewind = 0x0;
- uint32_t midiInStartStop = 0x0;
- uint32_t midiInActionRec = 0x0;
- uint32_t midiInInputRec = 0x0;
- uint32_t midiInMetronome = 0x0;
- uint32_t midiInVolumeIn = 0x0;
- uint32_t midiInVolumeOut = 0x0;
- uint32_t midiInBeatDouble = 0x0;
- uint32_t midiInBeatHalf = 0x0;
+public:
+ struct Data
+ {
+ int logMode = LOG_MODE_MUTE;
+ bool showTooltips = true;
+ int soundSystem = G_DEFAULT_SOUNDSYS;
+ int soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT;
+ int soundDeviceIn = G_DEFAULT_SOUNDDEV_IN;
+ int channelsOutCount = G_MAX_IO_CHANS;
+ int channelsOutStart = 0;
+ int channelsInCount = 1;
+ int channelsInStart = 0;
+ int samplerate = G_DEFAULT_SAMPLERATE;
+ int buffersize = G_DEFAULT_BUFSIZE;
+ bool limitOutput = false;
+ int rsmpQuality = 0;
+
+ int midiSystem = 0;
+ int midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
+ int midiPortIn = G_DEFAULT_MIDI_PORT_IN;
+ std::string midiMapPath = "";
+ std::string lastFileMap = "";
+ int midiSync = G_MIDI_SYNC_NONE;
+ float midiTCfps = 25.0f;
+
+ bool chansStopOnSeqHalt = false;
+ bool treatRecsAsLoops = false;
+ bool inputMonitorDefaultOn = false;
+ bool overdubProtectionDefaultOn = false;
+
+ std::string pluginPath;
+ std::string patchPath;
+ std::string samplePath;
+
+ int mainWindowX = u::gui::centerWindowX(G_MIN_GUI_WIDTH);
+ int mainWindowY = u::gui::centerWindowY(G_MIN_GUI_HEIGHT);
+ int mainWindowW = G_MIN_GUI_WIDTH;
+ int mainWindowH = G_MIN_GUI_HEIGHT;
+
+ int browserX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
+ int browserY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
+ int browserW = G_DEFAULT_SUBWINDOW_W;
+ int browserH = G_DEFAULT_SUBWINDOW_H;
+ int browserPosition;
+ int browserLastValue;
+ std::string browserLastPath;
+
+ int actionEditorY = u::gui::centerWindowY(G_DEFAULT_SUBWINDOW_H);
+ int actionEditorX = u::gui::centerWindowX(G_DEFAULT_SUBWINDOW_W);
+ int actionEditorW = G_DEFAULT_SUBWINDOW_W;
+ int actionEditorH = G_DEFAULT_SUBWINDOW_H;
+ int actionEditorZoom = G_DEFAULT_ZOOM_RATIO;
+ int actionEditorSplitH = -1;
+ int actionEditorGridVal = 0;
+ int actionEditorGridOn = false;
+ int actionEditorPianoRollY = -1;
+
+ int sampleEditorX;
+ int sampleEditorY;
+ int sampleEditorW = G_DEFAULT_SUBWINDOW_W;
+ int sampleEditorH = G_DEFAULT_SUBWINDOW_H;
+ int sampleEditorGridVal = 0;
+ int sampleEditorGridOn = false;
+
+ int midiInputX;
+ int midiInputY;
+ int midiInputW = G_DEFAULT_SUBWINDOW_W;
+ int midiInputH = G_DEFAULT_SUBWINDOW_H;
+
+ int pluginListX;
+ int pluginListY;
+
+ RecTriggerMode recTriggerMode = RecTriggerMode::NORMAL;
+ float recTriggerLevel = G_DEFAULT_REC_TRIGGER_LEVEL;
+ InputRecMode inputRecMode = InputRecMode::FREE;
+
+ bool midiInEnabled = false;
+ int midiInFilter = -1;
+ uint32_t midiInRewind = 0x0;
+ uint32_t midiInStartStop = 0x0;
+ uint32_t midiInActionRec = 0x0;
+ uint32_t midiInInputRec = 0x0;
+ uint32_t midiInMetronome = 0x0;
+ uint32_t midiInVolumeIn = 0x0;
+ uint32_t midiInVolumeOut = 0x0;
+ uint32_t midiInBeatDouble = 0x0;
+ uint32_t midiInBeatHalf = 0x0;
#ifdef WITH_VST
- int pluginChooserX;
- int pluginChooserY;
- int pluginChooserW = G_DEFAULT_SUBWINDOW_W;
- int pluginChooserH = G_DEFAULT_SUBWINDOW_H;
- int pluginSortMethod = 0;
+ int pluginChooserX;
+ int pluginChooserY;
+ int pluginChooserW = G_DEFAULT_SUBWINDOW_W;
+ int pluginChooserH = G_DEFAULT_SUBWINDOW_H;
+ int pluginSortMethod = 0;
#endif
-};
+ };
+
+ Conf();
+
+ bool read();
+ bool write() const;
-/* -------------------------------------------------------------------------- */
+ Data data;
-extern Conf conf;
+private:
+ /* createConfigFolder
+ Creates local folder where to put the configuration file. Path differs from
+ OS to OS. */
-/* -------------------------------------------------------------------------- */
+ bool createConfigFolder() const;
-void init();
-bool read();
-bool write();
-} // namespace giada::m::conf
+ void sanitize();
+
+ std::string m_confFilePath;
+ std::string m_confDirPath;
+};
+} // namespace giada::m
#endif
/* -- version --------------------------------------------------------------- */
constexpr auto G_APP_NAME = "Giada";
-constexpr auto G_VERSION_STR = "0.18.1";
+constexpr auto G_VERSION_STR = "0.19.1";
constexpr int G_VERSION_MAJOR = 0;
-constexpr int G_VERSION_MINOR = 18;
+constexpr int G_VERSION_MINOR = 19;
constexpr int G_VERSION_PATCH = 1;
constexpr auto CONF_FILENAME = "giada.conf";
#define G_COLOR_RED fl_rgb_color(28, 32, 80)
#define G_COLOR_BLUE fl_rgb_color(113, 31, 31)
-#define G_COLOR_RED_ALERT fl_rgb_color(239, 75, 53)
#define G_COLOR_LIGHT_2 fl_rgb_color(200, 200, 200)
#define G_COLOR_LIGHT_1 fl_rgb_color(170, 170, 170)
#define G_COLOR_GREY_4 fl_rgb_color(78, 78, 78)
constexpr int G_SYS_API_WASAPI = 7;
/* -- kernel midi ----------------------------------------------------------- */
-constexpr int G_MIDI_API_JACK = 0x01; // 0000 0001
-constexpr int G_MIDI_API_ALSA = 0x02; // 0000 0010
+constexpr int G_MIDI_API_JACK = 1;
+constexpr int G_MIDI_API_ALSA = 2;
+constexpr int G_MIDI_API_MM = 3;
+constexpr int G_MIDI_API_CORE = 4;
/* -- default system -------------------------------------------------------- */
#if defined(G_OS_LINUX)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_NONE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_NONE;
#elif defined(G_OS_FREEBSD)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_PULSE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_PULSE;
#elif defined(G_OS_WINDOWS)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_DS
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_DS;
#elif defined(G_OS_MAC)
-#define G_DEFAULT_SOUNDSYS G_SYS_API_CORE
+constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_CORE;
#endif
constexpr int G_DEFAULT_SOUNDDEV_OUT = -1; // disabled by default
constexpr int G_RES_OK = 1;
/* -- log modes ------------------------------------------------------------- */
-constexpr int LOG_MODE_STDOUT = 0x01;
-constexpr int LOG_MODE_FILE = 0x02;
-constexpr int LOG_MODE_MUTE = 0x04;
+constexpr int LOG_MODE_MUTE = 0;
+constexpr int LOG_MODE_STDOUT = 1;
+constexpr int LOG_MODE_FILE = 2;
/* -- unique IDs of mainWin's subwindows ------------------------------------ */
/* -- wid > 0 are reserved by gg_keyboard ----------------------------------- */
/* midi sync constants */
-constexpr int MIDI_SYNC_NONE = 0x00;
-constexpr int MIDI_SYNC_CLOCK_M = 0x01; // master
-constexpr int MIDI_SYNC_CLOCK_S = 0x02; // slave
-constexpr int MIDI_SYNC_MTC_M = 0x04; // master
-constexpr int MIDI_SYNC_MTC_S = 0x08; // slave
+constexpr int G_MIDI_SYNC_NONE = 0;
+constexpr int G_MIDI_SYNC_CLOCK_M = 1; // master
+constexpr int G_MIDI_SYNC_CLOCK_S = 2; // slave
+constexpr int G_MIDI_SYNC_MTC_M = 3; // master
+constexpr int G_MIDI_SYNC_MTC_S = 4; // slave
/* JSON patch keys */
constexpr auto CONF_KEY_ACTION_EDITOR_W = "action_editor_w";
constexpr auto CONF_KEY_ACTION_EDITOR_H = "action_editor_h";
constexpr auto CONF_KEY_ACTION_EDITOR_ZOOM = "action_editor_zoom";
+constexpr auto CONF_KEY_ACTION_EDITOR_SPLIT_H = "action_editor_split_h";
constexpr auto CONF_KEY_ACTION_EDITOR_GRID_VAL = "action_editor_grid_val";
constexpr auto CONF_KEY_ACTION_EDITOR_GRID_ON = "action_editor_grid_on";
constexpr auto CONF_KEY_SAMPLE_EDITOR_X = "sample_editor_x";
constexpr auto CONF_KEY_SAMPLE_EDITOR_H = "sample_editor_h";
constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_VAL = "sample_editor_grid_val";
constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_ON = "sample_editor_grid_on";
-constexpr auto CONF_KEY_PIANO_ROLL_Y = "piano_roll_y";
-constexpr auto CONF_KEY_PIANO_ROLL_H = "piano_roll_h";
-constexpr auto CONF_KEY_SAMPLE_ACTION_EDITOR_H = "sample_action_editor_h";
-constexpr auto CONF_KEY_VELOCITY_EDITOR_H = "velocity_editor_h";
-constexpr auto CONF_KEY_ENVELOPE_EDITOR_H = "envelope_editor_h";
+constexpr auto CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y = "piano_roll_y";
constexpr auto CONF_KEY_PLUGIN_LIST_X = "plugin_list_x";
constexpr auto CONF_KEY_PLUGIN_LIST_Y = "plugin_list_y";
constexpr auto CONF_KEY_PLUGIN_CHOOSER_X = "plugin_chooser_x";
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/engine.h"
+#include "core/model/model.h"
+#include "core/model/storage.h"
+#include "utils/fs.h"
+#include "utils/log.h"
+
+namespace giada::m
+{
+Engine::Engine()
+: midiMapper(kernelMidi)
+, channelManager(conf.data, model)
+, midiDispatcher(model)
+, actionRecorder(model)
+, synchronizer(conf.data, kernelMidi)
+, sequencer(model, synchronizer, jackTransport)
+, mixer(model)
+, mixerHandler(model, mixer)
+, recorder(model, sequencer, mixerHandler)
+#ifdef WITH_VST
+, pluginHost(model)
+#endif
+{
+ kernelAudio.onAudioCallback = [this](KernelAudio::CallbackInfo info) {
+ return audioCallback(info);
+ };
+
+ kernelMidi.onMidiReceived = [this](uint32_t msg) { midiDispatcher.dispatch(msg); };
+
+#ifdef WITH_AUDIO_JACK
+ if (kernelAudio.getAPI() == G_SYS_API_JACK)
+ jackTransport.setHandle(kernelAudio.getJackHandle());
+
+ synchronizer.onJackRewind = [this]() { sequencer.rawRewind(); };
+ synchronizer.onJackChangeBpm = [this](float bpm) { sequencer.rawSetBpm(bpm, kernelAudio.getSampleRate()); };
+ synchronizer.onJackStart = [this]() { sequencer.rawStart(); };
+ synchronizer.onJackStop = [this]() { sequencer.rawStop(); };
+#endif
+
+ eventDispatcher.onMidiLearn = [this](const MidiEvent& e) { midiDispatcher.learn(e); };
+ eventDispatcher.onMidiProcess = [this](const MidiEvent& e) { midiDispatcher.process(e); };
+ eventDispatcher.onProcessChannels = [this](const EventDispatcher::EventBuffer& eb) {
+ for (Channel& ch : model.get().channels)
+ ch.react(eb);
+ model.swap(model::SwapType::SOFT); // TODO - is this necessary???
+ };
+ eventDispatcher.onProcessSequencer = [this](const EventDispatcher::EventBuffer& eb) {
+ sequencer.react(eb);
+ };
+ eventDispatcher.onMixerSignalCallback = [this]() {
+ recorder.startInputRecOnCallback();
+ };
+ eventDispatcher.onMixerEndOfRecCallback = [this]() {
+ if (recorder.isRecordingInput())
+ recorder.stopInputRec(conf.data.inputRecMode, kernelAudio.getSampleRate());
+ };
+
+ midiDispatcher.onDispatch = [this](EventDispatcher::EventType event, Action action) {
+ /* Notify Event Dispatcher when a MIDI signal is received. */
+ eventDispatcher.pumpMidiEvent({event, 0, 0, action});
+ };
+
+ midiDispatcher.onEventReceived = [this]() {
+ recorder.startActionRecOnCallback();
+ };
+
+ mixer.onSignalTresholdReached = [this]() {
+ /* Invokes the signal callback. This is done by pumping a MIXER_SIGNAL_CALLBACK
+ event to the Event Dispatcher, rather than invoking the callback directly.
+ This is done on purpose: the callback might (and surely will) contain
+ blocking stuff from model:: that the realtime thread cannot perform directly. */
+ eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_SIGNAL_CALLBACK});
+ };
+
+ mixer.onEndOfRecording = [this]() {
+ /* Same rationale as above, for the end-of-recording callback. */
+ eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_END_OF_REC_CALLBACK});
+ };
+
+ mixerHandler.onChannelsAltered = [this]() {
+ if (!recorder.canEnableFreeInputRec())
+ conf.data.inputRecMode = InputRecMode::RIGID;
+ };
+ mixerHandler.onChannelRecorded = [this](Frame recordedFrames) {
+ std::string filename = "TAKE-" + std::to_string(patch.data.lastTakeId++) + ".wav";
+ return waveManager.createEmpty(recordedFrames, G_MAX_IO_CHANS, kernelAudio.getSampleRate(), filename);
+ };
+
+ sequencer.onAboutStart = [this](SeqStatus status) {
+ /* TODO move this logic to Recorder */
+ if (status == SeqStatus::WAITING)
+ recorder.stopActionRec(actionRecorder);
+ conf.data.recTriggerMode = RecTriggerMode::NORMAL;
+ };
+
+ sequencer.onAboutStop = [this]() {
+ /* If recordings (both input and action) are active deactivate them, but
+ store the takes. RecManager takes care of it. */
+ /* TODO move this logic to Recorder */
+ if (recorder.isRecordingAction())
+ recorder.stopActionRec(actionRecorder);
+ else if (recorder.isRecordingInput())
+ recorder.stopInputRec(conf.data.inputRecMode, kernelAudio.getSampleRate());
+ };
+
+ sequencer.onBpmChange = [this](float oldVal, float newVal, int quantizerStep) {
+ actionRecorder.updateBpm(oldVal / newVal, quantizerStep);
+ };
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::updateMixerModel()
+{
+ model.get().mixer.limitOutput = conf.data.limitOutput;
+ model.get().mixer.allowsOverdub = conf.data.inputRecMode == InputRecMode::RIGID;
+ model.get().mixer.maxFramesToRec = conf.data.inputRecMode == InputRecMode::FREE ? sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()) : sequencer.getFramesInLoop();
+ model.get().mixer.recTriggerLevel = conf.data.recTriggerLevel;
+ model.swap(model::SwapType::NONE);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::init()
+{
+ if (!conf.read())
+ u::log::print("[Engine::init] Can't read configuration file! Using default values\n");
+
+ model::load(conf.data);
+
+ if (!u::log::init(conf.data.logMode))
+ u::log::print("[Engine::init] log init failed! Using default stdout\n");
+
+ init::printBuildInfo();
+
+ midiMapper.init();
+ if (midiMapper.read(conf.data.midiMapPath) != MIDIMAP_READ_OK)
+ u::log::print("[Engine::init] MIDI map read failed!\n");
+
+ /* Initialize KernelAudio. If fails, interrupt the Engine initialization:
+ Giada can't work without a functional KernelAudio. */
+
+ kernelAudio.openDevice(conf.data);
+ if (!kernelAudio.isReady())
+ return;
+
+ mixerHandler.reset(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()),
+ kernelAudio.getBufferSize(), channelManager);
+ sequencer.reset(kernelAudio.getSampleRate());
+#ifdef WITH_VST
+ pluginHost.reset(kernelAudio.getBufferSize());
+ pluginManager.reset(static_cast<PluginManager::SortMethod>(conf.data.pluginSortMethod));
+#endif
+ mixer.enable();
+ kernelAudio.startStream();
+
+ kernelMidi.openOutDevice(conf.data.midiSystem, conf.data.midiPortOut);
+ kernelMidi.openInDevice(conf.data.midiSystem, conf.data.midiPortIn);
+ kernelMidi.logPorts();
+
+ midiMapper.sendInitMessages(midiMapper.currentMap);
+
+ eventDispatcher.start();
+
+ updateMixerModel();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::reset()
+{
+ model.reset();
+ mixerHandler.reset(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()),
+ kernelAudio.getBufferSize(), channelManager);
+ channelManager.reset();
+ waveManager.reset();
+ synchronizer.reset();
+ sequencer.reset(kernelAudio.getSampleRate());
+ actionRecorder.reset();
+#ifdef WITH_VST
+ pluginHost.reset(kernelAudio.getBufferSize());
+ pluginManager.reset(static_cast<PluginManager::SortMethod>(conf.data.pluginSortMethod));
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Engine::shutdown()
+{
+ if (kernelAudio.isReady())
+ {
+ kernelAudio.closeDevice();
+ u::log::print("[Engine::shutdown] KernelAudio closed\n");
+ mixer.disable();
+ u::log::print("[Engine::shutdown] Mixer closed\n");
+ }
+
+ model::store(conf.data);
+ if (!conf.write())
+ u::log::print("[Engine::shutdown] error while saving configuration file!\n");
+ else
+ u::log::print("[Engine::shutdown] configuration saved\n");
+
+ u::log::close();
+
+#ifdef WITH_VST
+ /* Currently the Engine is global/static, and so are all of its sub-components,
+ Model included. Some plug-ins (JUCE-based ones) crash hard on destructor when
+ deleted as a result of returning from main, so it's better to free them all first.
+ TODO - investigate this! */
+
+ pluginHost.freeAllPlugins();
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+int Engine::audioCallback(KernelAudio::CallbackInfo kernelInfo)
+{
+ mcl::AudioBuffer out(static_cast<float*>(kernelInfo.outBuf), kernelInfo.bufferSize, kernelInfo.channelsOutCount);
+ mcl::AudioBuffer in;
+ if (kernelInfo.channelsInCount > 0)
+ in = mcl::AudioBuffer(static_cast<float*>(kernelInfo.inBuf), kernelInfo.bufferSize, kernelInfo.channelsInCount);
+
+ /* Clean up output buffer before any rendering. Do this even if mixer is
+ disabled to avoid audio leftovers during a temporary suspension (e.g. when
+ loading a new patch). */
+
+ out.clear();
+
+ if (!kernelInfo.ready)
+ return 0;
+
+ /* Prepare the LayoutLock. From this point on (until out of scope) the
+ Layout is locked for realtime rendering by the audio thread. Rendering
+ functions must access the realtime layout coming from layoutLock.get(). */
+
+ const model::LayoutLock layoutLock = model.get_RT();
+ const model::Layout& layout_RT = layoutLock.get();
+
+ /* Mixer disabled, nothing to do here. */
+
+ if (!layout_RT.mixer.a_isActive())
+ return 0;
+
+#ifdef WITH_AUDIO_JACK
+ if (kernelInfo.withJack)
+ synchronizer.recvJackSync(jackTransport.getState());
+#endif
+
+ /* If the sequencer is running, advance it first (i.e. parse it for events).
+ Also advance channels (i.e. let them react to sequencer events), only if the
+ layout is not locked: another thread might altering channel's data in the
+ meantime (e.g. Plugins or Waves). */
+
+ if (layout_RT.sequencer.isRunning())
+ {
+ const Sequencer::EventBuffer& events = sequencer.advance(in.countFrames(), actionRecorder);
+ sequencer.render(out);
+ if (!layout_RT.locked)
+ mixer.advanceChannels(events, layout_RT);
+ }
+
+ /* Then render Mixer: render channels, process I/O. */
+
+ mixer.render(out, in, layout_RT);
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Engine::store(const std::string& projectName, const std::string& projectPath,
+ const std::string& patchPath)
+{
+ if (!u::fs::mkdir(projectPath))
+ {
+ u::log::print("[Engine::store] Unable to make project directory!\n");
+ return false;
+ }
+
+ u::log::print("[Engine::store] Project dir created: %s\n", projectPath);
+
+ /* Update all existing file paths in Waves, so that they point to the project
+ folder they belong to. */
+
+ for (std::unique_ptr<Wave>& w : model.getAllShared<model::WavePtrs>())
+ {
+ w->setPath(makeUniqueWavePath(projectPath, *w, model.getAllShared<model::WavePtrs>()));
+ waveManager.save(*w, w->getPath()); // TODO - error checking
+ }
+
+ /* Write Model into Patch, then into file. */
+
+ patch.data.name = projectName;
+ model::store(patch.data);
+
+ if (!patch.write(patchPath))
+ return false;
+
+ /* Store the parent folder the project belongs to, in order to reuse it the
+ next time. */
+
+ conf.data.patchPath = u::fs::getUpDir(u::fs::getUpDir(patchPath));
+
+ u::log::print("[Engine::store] Project patch saved as %s\n", patchPath);
+
+ return true;
+}
+
+/* -------------------------------------------------------------------------- */
+
+int Engine::load(const std::string& projectPath, const std::string& patchPath)
+{
+ u::log::print("[Engine::load] Load project from %s\n", projectPath);
+
+ patch.reset();
+ if (int res = patch.read(patchPath, projectPath); res != G_PATCH_OK)
+ return res;
+
+ /* Then suspend Mixer, reset and fill the model. */
+
+ mixer.disable();
+ reset();
+ m::model::load(patch.data);
+
+ /* Prepare the engine. Recorder has to recompute the actions positions if
+ the current samplerate != patch samplerate. Clock needs to update frames
+ in sequencer. */
+
+ mixerHandler.updateSoloCount();
+ actionRecorder.updateSamplerate(kernelAudio.getSampleRate(), patch.data.samplerate);
+ sequencer.recomputeFrames(kernelAudio.getSampleRate());
+ mixer.allocRecBuffer(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate()));
+
+ /* Store the parent folder the project belongs to, in order to reuse it the
+ next time. */
+
+ conf.data.patchPath = u::fs::getUpDir(projectPath);
+
+ /* Mixer is ready to go back online. */
+
+ mixer.enable();
+
+ return G_PATCH_OK;
+}
+
+} // namespace giada::m
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_ENGINE_H
+#define G_ENGINE_H
+
+#include "core/actions/actionRecorder.h"
+#include "core/actions/actions.h"
+#include "core/channels/channelManager.h"
+#include "core/conf.h"
+#include "core/eventDispatcher.h"
+#include "core/init.h"
+#include "core/jackTransport.h"
+#include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/midiDispatcher.h"
+#include "core/midiMapper.h"
+#include "core/mixer.h"
+#include "core/mixerHandler.h"
+#include "core/model/model.h"
+#include "core/patch.h"
+#include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
+#include "core/recorder.h"
+#include "core/sequencer.h"
+#include "core/synchronizer.h"
+#include "core/waveManager.h"
+
+namespace giada::m
+{
+class Engine final
+{
+public:
+ /* Engine()
+ Prepares all sub-components by constructing them and setting up the required
+ callback for inter-component communication. It doesn't start the engine yet. */
+
+ Engine();
+
+ /* store
+ Saves the current state to a Patch, then saves it to file. Returns true
+ on success. */
+
+ bool store(const std::string& projectName, const std::string& projectPath,
+ const std::string& patchPath);
+
+ /* load
+ Reads a Patch from file and then de-serialize its content into the model.
+ Returns G_PATCH_OK on success or any G_PATCH_* on failure. */
+
+ int load(const std::string& projectPath, const std::string& patchPath);
+
+ /* updateMixerModel
+ Updates some values in model::Mixer data struct needed by m::Mixer for the
+ audio rendering. Call this whenever the audio configuration changes. */
+
+ void updateMixerModel();
+
+ /* init
+ Initializes all sub-components. If KernelAudio fails to start, the process
+ interrupts and Giada is put in an invalid state. */
+
+ void init();
+
+ /* reset
+ Resets all sub-components to the initial state. Useful when Giada needs to
+ be brought back to the starup state. */
+
+ void reset();
+
+ /* shutdown
+ Closes the current audio device. */
+
+ void shutdown();
+
+ model::Model model;
+ Conf conf;
+ Patch patch;
+ KernelAudio kernelAudio;
+ KernelMidi kernelMidi;
+ JackTransport jackTransport;
+ WaveManager waveManager;
+ EventDispatcher eventDispatcher;
+ MidiMapper<KernelMidi> midiMapper;
+ ChannelManager channelManager;
+ MidiDispatcher midiDispatcher;
+ ActionRecorder actionRecorder;
+ Synchronizer synchronizer;
+ Sequencer sequencer;
+ Mixer mixer;
+ MixerHandler mixerHandler;
+ Recorder recorder;
+#ifdef WITH_VST
+ PluginHost pluginHost;
+ PluginManager pluginManager;
+#endif
+
+private:
+ int audioCallback(KernelAudio::CallbackInfo);
+};
+} // namespace giada::m
+
+#endif
*
* -------------------------------------------------------------------------- */
-#include "eventDispatcher.h"
-#include "core/clock.h"
+#include "core/eventDispatcher.h"
#include "core/const.h"
-#include "core/model/model.h"
-#include "core/sequencer.h"
-#include "core/worker.h"
-#include "utils/log.h"
-#include <functional>
+#include <cassert>
-namespace giada::m::eventDispatcher
+namespace giada::m
{
-namespace
+EventDispatcher::EventDispatcher()
+: onMidiLearn(nullptr)
+, onMidiProcess(nullptr)
+, onProcessChannels(nullptr)
+, onProcessSequencer(nullptr)
+, onMixerSignalCallback(nullptr)
+, onMixerEndOfRecCallback(nullptr)
{
-Worker worker_;
-
-/* eventBuffer_
-Buffer of events sent to channels for event parsing. This is filled with Events
-coming from the two event queues.*/
-
-EventBuffer eventBuffer_;
+}
/* -------------------------------------------------------------------------- */
-void processFuntions_()
+void EventDispatcher::start()
{
- for (const Event& e : eventBuffer_)
- {
- if (e.type == EventType::FUNCTION)
- std::get<std::function<void()>>(e.data)();
- G_DEBUG("Event type=" << (int)e.type << ", delta=" << e.delta << ", frame=" << clock::getCurrentFrame());
- }
+ m_worker.start([this]() { process(); }, /*sleep=*/G_EVENT_DISPATCHER_RATE_MS);
}
/* -------------------------------------------------------------------------- */
-void processChannels_()
-{
- for (channel::Data& ch : model::get().channels)
- channel::react(ch, eventBuffer_, mixer::isChannelAudible(ch));
- model::swap(model::SwapType::SOFT);
-}
+void EventDispatcher::pumpUIevent(Event e) { UIevents.push(e); }
+void EventDispatcher::pumpMidiEvent(Event e) { MidiEvents.push(e); }
/* -------------------------------------------------------------------------- */
-void processSequencer_()
+void EventDispatcher::processFuntions()
{
- sequencer::react(eventBuffer_);
+ assert(onMidiLearn != nullptr);
+ assert(onMidiProcess != nullptr);
+ assert(onMixerSignalCallback != nullptr);
+ assert(onMixerEndOfRecCallback != nullptr);
+
+ for (const Event& e : m_eventBuffer)
+ {
+ switch (e.type)
+ {
+ case EventType::MIDI_DISPATCHER_LEARN:
+ onMidiLearn(std::get<Action>(e.data).event);
+ break;
+
+ case EventType::MIDI_DISPATCHER_PROCESS:
+ onMidiProcess(std::get<Action>(e.data).event);
+ break;
+
+ case EventType::MIXER_SIGNAL_CALLBACK:
+ onMixerSignalCallback();
+ break;
+
+ case EventType::MIXER_END_OF_REC_CALLBACK:
+ onMixerEndOfRecCallback();
+ break;
+
+ default:
+ break;
+ }
+ }
}
/* -------------------------------------------------------------------------- */
-void process_()
+void EventDispatcher::process()
{
- eventBuffer_.clear();
+ assert(onProcessChannels != nullptr);
+ assert(onProcessSequencer != nullptr);
+
+ m_eventBuffer.clear();
Event e;
while (UIevents.pop(e))
- eventBuffer_.push_back(e);
+ m_eventBuffer.push_back(e);
while (MidiEvents.pop(e))
- eventBuffer_.push_back(e);
+ m_eventBuffer.push_back(e);
- if (eventBuffer_.size() == 0)
+ if (m_eventBuffer.size() == 0)
return;
- processFuntions_();
- processChannels_();
- processSequencer_();
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
-Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
- worker_.start(process_, /*sleep=*/G_EVENT_DISPATCHER_RATE_MS);
+ processFuntions();
+ onProcessChannels(m_eventBuffer);
+ onProcessSequencer(m_eventBuffer);
}
-
-/* -------------------------------------------------------------------------- */
-
-void pumpUIevent(Event e) { UIevents.push(e); }
-void pumpMidiEvent(Event e) { MidiEvents.push(e); }
-} // namespace giada::m::eventDispatcher
\ No newline at end of file
+} // namespace giada::m
\ No newline at end of file
#ifndef G_EVENT_DISPATCHER_H
#define G_EVENT_DISPATCHER_H
-#include "core/action.h"
#include "core/const.h"
#include "core/queue.h"
#include "core/ringBuffer.h"
#include "core/types.h"
+#include "core/worker.h"
+#include "src/core/actions/action.h"
#include <atomic>
#include <functional>
#include <thread>
#include <variant>
-/* giada::m::eventDispatcher
+/* giada::m::EventDispatcher
Takes events from the two queues (MIDI and UI) filled by c::events and turns
them into actual changes in the data model. The EventDispatcher runs in a
separate worker thread. */
-namespace giada::m::eventDispatcher
+namespace giada::m
{
-enum class EventType
+class EventDispatcher
{
- KEY_PRESS,
- KEY_RELEASE,
- KEY_KILL,
- SEQUENCER_START,
- SEQUENCER_STOP,
- SEQUENCER_REWIND,
- MIDI,
- CHANNEL_TOGGLE_READ_ACTIONS,
- CHANNEL_KILL_READ_ACTIONS,
- CHANNEL_TOGGLE_ARM,
- CHANNEL_MUTE,
- CHANNEL_SOLO,
- CHANNEL_VOLUME,
- CHANNEL_PITCH,
- CHANNEL_PAN,
- FUNCTION
-};
+public:
+ enum class EventType
+ {
+ KEY_PRESS,
+ KEY_RELEASE,
+ KEY_KILL,
+ SEQUENCER_START,
+ SEQUENCER_STOP,
+ SEQUENCER_REWIND,
+ MIDI,
+ MIDI_DISPATCHER_LEARN,
+ MIDI_DISPATCHER_PROCESS,
+ MIXER_SIGNAL_CALLBACK,
+ MIXER_END_OF_REC_CALLBACK,
+ CHANNEL_TOGGLE_READ_ACTIONS,
+ CHANNEL_KILL_READ_ACTIONS,
+ CHANNEL_TOGGLE_ARM,
+ CHANNEL_MUTE,
+ CHANNEL_SOLO,
+ CHANNEL_VOLUME,
+ CHANNEL_PITCH,
+ CHANNEL_PAN
+ };
-using EventData = std::variant<int, float, Action, std::function<void()>>;
+ struct Event
+ {
+ using EventData = std::variant<int, float, Action>;
-struct Event
-{
- EventType type;
- Frame delta = 0;
- ID channelId = 0;
- EventData data;
-};
+ EventType type;
+ Frame delta = 0;
+ ID channelId = 0;
+ EventData data = {};
+ };
+
+ /* EventBuffer
+ Alias for a RingBuffer containing events to be sent to engine. The double
+ size is due to the presence of two distinct Queues for collecting events
+ coming from other threads. See below. */
+
+ using EventBuffer = RingBuffer<Event, G_MAX_DISPATCHER_EVENTS * 2>;
+
+ EventDispatcher();
+
+ /* start
+ Starts the internal worker on a separate thread. Call this on startup. */
-/* EventBuffer
-Alias for a RingBuffer containing events to be sent to engine. The double size
-is due to the presence of two distinct Queues for collecting events coming from
-other threads. See below. */
+ void start();
-using EventBuffer = RingBuffer<Event, G_MAX_DISPATCHER_EVENTS * 2>;
+ void pumpUIevent(Event e);
+ void pumpMidiEvent(Event e);
-/* Event queues
-Collect events coming from the UI or MIDI devices. Our poor's man Queue is a
-single-producer/single-consumer one, so we need two queues for two writers.
-TODO - let's add a multi-producer queue sooner or later! */
+ /* Event queues
+ Collect events coming from the UI or MIDI devices. Our poor man's Queue is a
+ single-producer/single-consumer one, so we need two queues for two writers.
+ TODO - let's add a multi-producer queue sooner or later! */
+ /*TODO - make them private*/
-extern Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
-extern Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
+ Queue<Event, G_MAX_DISPATCHER_EVENTS> UIevents;
+ Queue<Event, G_MAX_DISPATCHER_EVENTS> MidiEvents;
-void init();
+ /* on[...]
+ Callbacks fired when something happens in the Event Dispatcher. */
-void pumpUIevent(Event e);
-void pumpMidiEvent(Event e);
-} // namespace giada::m::eventDispatcher
+ std::function<void(const MidiEvent& e)> onMidiLearn;
+ std::function<void(const MidiEvent& e)> onMidiProcess;
+ std::function<void(const EventBuffer&)> onProcessChannels;
+ std::function<void(const EventBuffer&)> onProcessSequencer;
+ std::function<void()> onMixerSignalCallback;
+ std::function<void()> onMixerEndOfRecCallback;
+
+private:
+ void processFuntions();
+ void process();
+
+ /* m_worker
+ A separate thread responsible for the event processing. */
+
+ Worker m_worker;
+
+ /* m_eventBuffer
+ Buffer of events sent to channels for event parsing. This is filled with
+ Events coming from the two event queues.*/
+
+ EventBuffer m_eventBuffer;
+};
+} // namespace giada::m
#endif
"..................",
".................."};
+const char* oneshotBasicPause_xpm[] = {
+ "18 18 5 1",
+ " c #252525",
+ ". c #4E4E4E",
+ "+ c #767676",
+ "@ c #9F9F9F",
+ "# c #C8C8C8",
+ " ",
+ " ",
+ " ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +## ",
+ " +++++++++@## ",
+ " ############ ",
+ " ############ ",
+ " ",
+ " ",
+ " "};
+
const char* oneshotRetrig_xpm[] = {
"18 18 8 1",
" c #181917",
extern const char* loopOnce_xpm[];
extern const char* loopOnceBar_xpm[];
extern const char* oneshotBasic_xpm[];
+extern const char* oneshotBasicPause_xpm[];
extern const char* oneshotRetrig_xpm[];
extern const char* oneshotPress_xpm[];
extern const char* oneshotEndless_xpm[];
/* -------------------------------------------------------------------------- */
-ID IdManager::get()
+ID IdManager::get() const
{
return m_id;
}
+
+/* -------------------------------------------------------------------------- */
+
+ID IdManager::getNext() const
+{
+ return m_id + 1;
+}
} // namespace giada::m
/* get
Returns the current id, a.k.a. the last generated one. */
- ID get();
+ ID get() const;
- private:
+ /* getNext
+ Returns the upcoming ID, that is the ID that will be generated on the next
+ call. */
+
+ ID getNext() const;
+
+private:
ID m_id;
};
} // namespace giada::m
*
* -------------------------------------------------------------------------- */
-#include <atomic>
-#include <ctime>
-#include <thread>
#ifdef __APPLE__
#include <pwd.h>
#endif
-#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_VST)
-#include <X11/Xlib.h> // For XInitThreads
-#endif
-#include "core/channels/channelManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "core/eventDispatcher.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/model/storage.h"
-#include "core/patch.h"
-#include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "core/wave.h"
-#include "core/waveManager.h"
-#include "deps/json/single_include/nlohmann/json.hpp"
-#include "glue/main.h"
-#include "gui/dialogs/mainWindow.h"
+#include "core/engine.h"
#include "gui/dialogs/warnings.h"
+#include "gui/ui.h"
#include "gui/updater.h"
-#include "init.h"
-#include "utils/fs.h"
-#include "utils/gui.h"
#include "utils/log.h"
-#include "utils/time.h"
#include "utils/ver.h"
+#ifdef WITH_TESTS
+#define CATCH_CONFIG_RUNNER
+#include "tests/actionRecorder.cpp"
+#include "tests/midiLighter.cpp"
+#include "tests/utils.cpp"
+#include "tests/wave.cpp"
+#include "tests/waveFx.cpp"
+#include "tests/waveManager.cpp"
+#include <catch2/catch.hpp>
+#include <string>
+#include <vector>
+#endif
#include <FL/Fl.H>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::m::Engine g_engine;
+extern giada::v::Ui g_ui;
namespace giada::m::init
{
-namespace
-{
-void initConf_()
-{
- if (!conf::read())
- u::log::print("[init] Can't read configuration file! Using default values\n");
-
- patch::init();
- midimap::init();
- midimap::setDefault();
-
- model::load(conf::conf);
-
- if (!u::log::init(conf::conf.logMode))
- u::log::print("[init] log init failed! Using default stdout\n");
-
- if (midimap::read(conf::conf.midiMapPath) != MIDIMAP_READ_OK)
- u::log::print("[init] MIDI map read failed!\n");
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initSystem_()
-{
- model::init();
- eventDispatcher::init();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initAudio_()
-{
- kernelAudio::openDevice();
- clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
- mh::init();
- sequencer::init();
- recorder::init();
- recorderHandler::init();
-
-#ifdef WITH_VST
-
- pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize());
- pluginHost::init(kernelAudio::getRealBufSize());
-
-#endif
-
- if (!kernelAudio::isReady())
- return;
-
- mixer::enable();
- kernelAudio::startStream();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initMIDI_()
-{
- kernelMidi::setApi(conf::conf.midiSystem);
- kernelMidi::openOutDevice(conf::conf.midiPortOut);
- kernelMidi::openInDevice(conf::conf.midiPortIn);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void initGUI_(int argc, char** argv)
-{
- /* This is of paramount importance on Linux with VST enabled, otherwise many
- plug-ins go nuts and crash hard. It seems that some plug-ins or our Juce-based
- PluginHost use Xlib concurrently. */
-
-#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_VST)
- XInitThreads();
-#endif
-
- G_MainWin = new v::gdMainWindow(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT, "", argc, argv);
- G_MainWin->resize(conf::conf.mainWindowX, conf::conf.mainWindowY, conf::conf.mainWindowW,
- conf::conf.mainWindowH);
-
- u::gui::updateMainWinLabel(patch::patch.name == "" ? G_DEFAULT_PATCH_NAME : patch::patch.name);
-
- if (!kernelAudio::isReady())
- v::gdAlert("Your soundcard isn't configured correctly.\n"
- "Check the configuration and restart Giada.");
-
- v::updater::init();
- u::gui::updateStaticWidgets();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void shutdownAudio_()
+int tests(int argc, char** argv)
{
- if (kernelAudio::isReady())
- {
- kernelAudio::closeDevice();
- u::log::print("[init] KernelAudio closed\n");
- mh::close();
- u::log::print("[init] Mixer closed\n");
- }
-
- /* TODO - why cleaning plug-ins and mixer memory? Just shutdown the audio
- device and let the OS take care of the rest. */
-
-#ifdef WITH_VST
-
- pluginHost::close();
- u::log::print("[init] PluginHost cleaned up\n");
-
+#ifdef WITH_TESTS
+ std::vector<char*> args(argv, argv + argc);
+ if (args.size() > 1 && strcmp(args[1], "--run-tests") == 0)
+ return Catch::Session().run(args.size() - 1, &args[1]);
+ else
+ return -1;
+#else
+ (void)argc;
+ (void)argv;
+ return -1;
#endif
}
/* -------------------------------------------------------------------------- */
-void shutdownGUI_()
-{
- u::gui::closeAllSubwindows();
-
- u::log::print("[init] All subwindows and UI thread closed\n");
-}
-
-/* -------------------------------------------------------------------------- */
-
-void printBuildInfo_()
+void printBuildInfo()
{
u::log::print("[init] Giada %s\n", G_VERSION_STR);
u::log::print("[init] Build date: " BUILD_DATE "\n");
#ifdef WITH_VST
u::log::print("[init] JUCE - %d.%d.%d\n", JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER);
#endif
- kernelAudio::logCompiledAPIs();
+ KernelAudio::logCompiledAPIs();
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
void startup(int argc, char** argv)
{
- printBuildInfo_();
+#ifdef WITH_VST
+ juce::initialiseJuce_GUI();
+#endif
+ g_engine.init();
+ g_ui.init(argc, argv, g_engine);
- initConf_();
- initSystem_();
- initAudio_();
- initMIDI_();
- initGUI_(argc, argv);
+ if (!g_engine.kernelAudio.isReady())
+ v::gdAlert("Your soundcard isn't configured correctly.\n"
+ "Check the configuration and restart Giada.");
}
/* -------------------------------------------------------------------------- */
-void closeMainWindow()
+int run()
{
- if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?"))
- return;
-
- v::updater::close();
- G_MainWin->hide();
- delete G_MainWin;
+ Fl::lock(); // Enable multithreading in FLTK
+ return Fl::run();
}
/* -------------------------------------------------------------------------- */
-void reset()
+void closeMainWindow()
{
- u::gui::closeAllSubwindows();
- G_MainWin->clearKeyboard();
-
- mh::close();
-#ifdef WITH_VST
- pluginHost::close();
-#endif
-
- model::init();
- channelManager::init();
- waveManager::init();
- clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
- mh::init();
- sequencer::init();
- recorder::init();
-#ifdef WITH_VST
- pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize());
-#endif
-
- u::gui::updateMainWinLabel(G_DEFAULT_PATCH_NAME);
- u::gui::updateStaticWidgets();
+ if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?"))
+ return;
+ shutdown();
}
/* -------------------------------------------------------------------------- */
void shutdown()
{
- shutdownGUI_();
-
- model::store(conf::conf);
-
- if (!conf::write())
- u::log::print("[init] error while saving configuration file!\n");
- else
- u::log::print("[init] configuration saved\n");
-
- shutdownAudio_();
-
+ g_ui.shutdown();
+ g_engine.shutdown();
+#ifdef WITH_VST
+ juce::shutdownJuce_GUI();
+#endif
u::log::print("[init] Giada %s closed\n\n", G_VERSION_STR);
- u::log::close();
}
} // namespace giada::m::init
namespace giada::m::init
{
+/* tests
+Performs tests, if requested. Returns -1 if no tests are available or the
+`--run-tests` has not been passed in. */
+
+int tests(int argc, char** argv);
+
+void printBuildInfo();
void startup(int argc, char** argv);
-void reset();
+int run();
void closeMainWindow();
void shutdown();
} // namespace giada::m::init
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "jackTransport.h"
+#ifdef WITH_AUDIO_JACK
+#include <jack/intclient.h>
+#include <jack/transport.h>
+#endif
+
+namespace giada::m
+{
+bool JackTransport::State::operator!=(const State& o) const
+{
+ return !(running == o.running && bpm == o.bpm && frame == o.frame);
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+JackTransport::JackTransport()
+#ifdef WITH_AUDIO_JACK
+: m_jackHandle(nullptr)
+#endif
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::start() const
+{
+#ifdef WITH_AUDIO_JACK
+ if (m_jackHandle == nullptr)
+ return false;
+ jack_transport_start(m_jackHandle);
+ return true;
+#else
+ return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::stop() const
+{
+#ifdef WITH_AUDIO_JACK
+ if (m_jackHandle == nullptr)
+ return false;
+ jack_transport_stop(m_jackHandle);
+ return true;
+#else
+ return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::setPosition(uint32_t frame) const
+{
+#ifdef WITH_AUDIO_JACK
+ if (m_jackHandle == nullptr)
+ return false;
+ jack_position_t position;
+ jack_transport_query(m_jackHandle, &position);
+ position.frame = frame;
+ jack_transport_reposition(m_jackHandle, &position);
+ return true;
+#else
+ (void)frame;
+ return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool JackTransport::setBpm(double bpm) const
+{
+#ifdef WITH_AUDIO_JACK
+ if (m_jackHandle == nullptr)
+ return false;
+ jack_position_t position;
+ jack_transport_query(m_jackHandle, &position);
+ position.valid = jack_position_bits_t::JackPositionBBT;
+ position.bar = 0; // no such info from Giada
+ position.beat = 0; // no such info from Giada
+ position.tick = 0; // no such info from Giada
+ position.beats_per_minute = bpm;
+ jack_transport_reposition(m_jackHandle, &position);
+ return true;
+#else
+ (void)bpm;
+ return false;
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+JackTransport::State JackTransport::getState() const
+{
+#ifdef WITH_AUDIO_JACK
+ if (m_jackHandle == nullptr)
+ return {};
+
+ jack_position_t position;
+ jack_transport_state_t ts = jack_transport_query(m_jackHandle, &position);
+
+ return {
+ ts != JackTransportStopped,
+ position.beats_per_minute,
+ position.frame};
+#else
+ return {};
+#endif
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_AUDIO_JACK
+void JackTransport::setHandle(jack_client_t* h)
+{
+ m_jackHandle = h;
+}
+#endif
+} // namespace giada::m
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_JACK_TRANSPORT_H
+#define G_JACK_TRANSPORT_H
+
+#ifdef WITH_AUDIO_JACK
+#include <jack/jack.h>
+#endif
+#include <cstdint>
+
+namespace giada::m
+{
+class JackTransport final
+{
+public:
+ struct State
+ {
+ bool running;
+ double bpm;
+ uint32_t frame;
+
+ bool operator!=(const State& o) const;
+ };
+
+ JackTransport();
+
+ bool start() const;
+ bool stop() const;
+ bool setPosition(uint32_t frame) const;
+ bool setBpm(double bpm) const;
+ State getState() const;
+
+#ifdef WITH_AUDIO_JACK
+ void setHandle(jack_client_t*);
+#endif
+
+private:
+ /* m_jackHandle
+ Optional handle to JACK. If nullptr the JackTransport class is not
+ initialized and all public transport methods above will return false. This
+ is useful when you are on a platform that supports JACK (e.g. Linux) but
+ the JACK API is currently not selected. */
+
+#ifdef WITH_AUDIO_JACK
+ jack_client_t* m_jackHandle;
+#endif
+};
+} // namespace giada::m
+
+#endif
*
* -------------------------------------------------------------------------- */
-#include "kernelAudio.h"
-#include "conf.h"
-#include "const.h"
-#include "core/clock.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
-#include "deps/rtaudio/RtAudio.h"
-#include "glue/main.h"
-#include "mixer.h"
+#include "core/kernelAudio.h"
+#include "core/conf.h"
+#include "core/const.h"
#include "utils/log.h"
#include "utils/vector.h"
+#include <cassert>
-namespace giada::m::kernelAudio
+namespace giada::m
{
-namespace
+KernelAudio::KernelAudio()
+: onAudioCallback(nullptr)
+, m_ready(false)
+, m_inputEnabled(false)
+, m_realBufferSize(0)
+, m_realSampleRate(0)
+, m_channelsOutCount(0)
+, m_channelsInCount(0)
+, m_api(0)
{
-std::vector<Device> devices_;
-RtAudio* rtSystem_ = nullptr;
-bool inputEnabled_ = false;
-unsigned realBufsize_ = 0; // Real buffer size from the soundcard
-int api_ = 0;
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-jack_client_t* jackGetHandle_()
-{
- return static_cast<jack_client_t*>(rtSystem_->HACK__getJackClient());
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-Device fetchDevice_(size_t deviceIndex)
-{
- try
- {
- RtAudio::DeviceInfo info = rtSystem_->getDeviceInfo(deviceIndex);
-
- if (!info.probed)
- {
- u::log::print("[KA] Can't probe device %d\n", deviceIndex);
- return {deviceIndex};
- }
-
- return {
- deviceIndex,
- true,
- info.name,
- static_cast<int>(info.outputChannels),
- static_cast<int>(info.inputChannels),
- static_cast<int>(info.duplexChannels),
- info.isDefaultOutput,
- info.isDefaultInput,
- u::vector::cast<int>(info.sampleRates)};
- }
- catch (RtAudioError& e)
- {
- u::log::print("[KA] Error fetching device %d: %s\n", deviceIndex, e.getMessage());
- return {0};
- }
}
/* -------------------------------------------------------------------------- */
-std::vector<Device> fetchDevices_()
+int KernelAudio::openDevice(const Conf::Data& conf)
{
- std::vector<Device> out;
- for (unsigned i = 0; i < rtSystem_->getDeviceCount(); i++)
- out.push_back(fetchDevice_(i));
- return out;
-}
+ assert(onAudioCallback != nullptr);
-/* -------------------------------------------------------------------------- */
-
-void printDevices_(const std::vector<Device>& devices)
-{
- u::log::print("[KA] %d device(s) found\n", devices.size());
- for (const Device& d : devices)
- {
- u::log::print(" %d) %s\n", d.index, d.name);
- u::log::print(" ins=%d outs=%d duplex=%d\n", d.maxInputChannels, d.maxOutputChannels, d.maxDuplexChannels);
- u::log::print(" isDefaultOut=%d isDefaultIn=%d\n", d.isDefaultOut, d.isDefaultIn);
- u::log::print(" sampleRates:\n\t");
- for (int s : d.sampleRates)
- u::log::print("%d ", s);
- u::log::print("\n");
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canRender_()
-{
- return model::get().kernel.audioReady && model::get().mixer.state->active.load() == true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int callback_(void* outBuf, void* inBuf, unsigned bufferSize, double /*streamTime*/,
- RtAudioStreamStatus /*status*/, void* /*userData*/)
-{
- AudioBuffer out(static_cast<float*>(outBuf), bufferSize, G_MAX_IO_CHANS);
- AudioBuffer in;
- if (isInputEnabled())
- in = AudioBuffer(static_cast<float*>(inBuf), bufferSize, conf::conf.channelsInCount);
-
- /* Clean up output buffer before any rendering. Do this even if mixer is
- disabled to avoid audio leftovers during a temporary suspension (e.g. when
- loading a new patch). */
-
- out.clear();
-
- if (!canRender_())
- return 0;
-
-#ifdef WITH_AUDIO_JACK
- if (getAPI() == G_SYS_API_JACK)
- clock::recvJackSync();
-#endif
-
- mixer::RenderInfo info;
- info.isAudioReady = model::get().kernel.audioReady;
- info.hasInput = isInputEnabled();
- info.isClockActive = clock::isActive();
- info.isClockRunning = clock::isRunning();
- info.canLineInRec = recManager::isRecordingInput() && isInputEnabled();
- info.limitOutput = conf::conf.limitOutput;
- info.inToOut = mh::getInToOut();
- info.maxFramesToRec = conf::conf.inputRecMode == InputRecMode::FREE ? clock::getMaxFramesInLoop() : clock::getFramesInLoop();
- info.outVol = mh::getOutVol();
- info.inVol = mh::getInVol();
- info.recTriggerLevel = conf::conf.recTriggerLevel;
-
- return mixer::render(out, in, info);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_AUDIO_JACK
-
-bool JackState::operator!=(const JackState& o) const
-{
- return !(running == o.running && bpm == o.bpm && frame == o.frame);
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-bool isReady()
-{
- return model::get().kernel.audioReady;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int openDevice()
-{
- api_ = conf::conf.soundSystem;
- u::log::print("[KA] using system 0x%x\n", api_);
+ m_api = conf.soundSystem;
+ u::log::print("[KA] using system 0x%x\n", m_api);
#if defined(__linux__) || defined(__FreeBSD__)
- if (api_ == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
- rtSystem_ = new RtAudio(RtAudio::UNIX_JACK);
- else if (api_ == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA))
- rtSystem_ = new RtAudio(RtAudio::LINUX_ALSA);
- else if (api_ == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
- rtSystem_ = new RtAudio(RtAudio::LINUX_PULSE);
+ if (m_api == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::UNIX_JACK);
+ else if (m_api == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_ALSA);
+ else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_PULSE);
#elif defined(__FreeBSD__)
- if (api_ == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
- rtSystem_ = new RtAudio(RtAudio::UNIX_JACK);
- else if (api_ == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
- rtSystem_ = new RtAudio(RtAudio::LINUX_PULSE);
+ if (m_api == G_SYS_API_JACK && hasAPI(RtAudio::UNIX_JACK))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::UNIX_JACK);
+ else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::LINUX_PULSE);
#elif defined(_WIN32)
- if (api_ == G_SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS))
- rtSystem_ = new RtAudio(RtAudio::WINDOWS_DS);
- else if (api_ == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO))
- rtSystem_ = new RtAudio(RtAudio::WINDOWS_ASIO);
- else if (api_ == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI))
- rtSystem_ = new RtAudio(RtAudio::WINDOWS_WASAPI);
+ if (m_api == G_SYS_API_DS && hasAPI(RtAudio::WINDOWS_DS))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_DS);
+ else if (m_api == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_ASIO);
+ else if (m_api == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::WINDOWS_WASAPI);
#elif defined(__APPLE__)
- if (api_ == G_SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE))
- rtSystem_ = new RtAudio(RtAudio::MACOSX_CORE);
+ if (m_api == G_SYS_API_CORE && hasAPI(RtAudio::MACOSX_CORE))
+ m_rtAudio = std::make_unique<RtAudio>(RtAudio::MACOSX_CORE);
#endif
return 0;
}
+ m_rtAudio->setErrorCallback([](RtAudioErrorType type, const std::string& msg) {
+ u::log::print("[KA] RtAudio error %d: %s\n", type, msg.c_str());
+ });
+
u::log::print("[KA] Opening device out=%d, in=%d, samplerate=%d\n",
- conf::conf.soundDeviceOut, conf::conf.soundDeviceIn, conf::conf.samplerate);
+ conf.soundDeviceOut, conf.soundDeviceIn, conf.samplerate);
- devices_ = fetchDevices_();
- printDevices_(devices_);
+ m_devices = fetchDevices();
+ printDevices(m_devices);
/* Abort here if devices found are zero. */
- if (devices_.size() == 0)
+ if (m_devices.size() == 0)
{
closeDevice();
return 0;
RtAudio::StreamParameters outParams;
RtAudio::StreamParameters inParams;
- outParams.deviceId = conf::conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? rtSystem_->getDefaultOutputDevice() : conf::conf.soundDeviceOut;
- outParams.nChannels = conf::conf.channelsOutCount;
- outParams.firstChannel = conf::conf.channelsOutStart;
+ outParams.deviceId = conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? m_rtAudio->getDefaultOutputDevice() : conf.soundDeviceOut;
+ outParams.nChannels = conf.channelsOutCount;
+ outParams.firstChannel = conf.channelsOutStart;
/* Input device can be disabled. Unlike the output, here we are using all
channels and let the user choose which one to record from in the configuration
panel. */
- if (conf::conf.soundDeviceIn != -1)
+ if (conf.soundDeviceIn != -1)
{
- inParams.deviceId = conf::conf.soundDeviceIn;
- inParams.nChannels = conf::conf.channelsInCount;
- inParams.firstChannel = conf::conf.channelsInStart;
- inputEnabled_ = true;
+ inParams.deviceId = conf.soundDeviceIn;
+ inParams.nChannels = conf.channelsInCount;
+ inParams.firstChannel = conf.channelsInStart;
+ m_inputEnabled = true;
}
else
- inputEnabled_ = false;
+ m_inputEnabled = false;
RtAudio::StreamOptions options;
options.streamName = G_APP_NAME;
options.numberOfBuffers = 4; // TODO - wtf?
- realBufsize_ = conf::conf.buffersize;
+ m_realBufferSize = conf.buffersize;
+ m_realSampleRate = conf.samplerate;
+ m_channelsOutCount = conf.channelsOutCount;
+ m_channelsInCount = conf.channelsInCount;
#ifdef WITH_AUDIO_JACK
- if (api_ == G_SYS_API_JACK)
+ /* If JACK, use its own sample rate, not the one coming from the conf
+ object. */
+
+ if (m_api == G_SYS_API_JACK)
{
- conf::conf.samplerate = devices_[conf::conf.soundDeviceOut].sampleRates[0];
- u::log::print("[KA] JACK in use, samplerate=%d\n", conf::conf.samplerate);
+ assert(m_devices.size() > 0);
+ assert(m_devices[0].sampleRates.size() > 0);
+
+ m_realSampleRate = m_devices[0].sampleRates[0];
+ u::log::print("[KA] JACK in use, samplerate=%d\n", m_realSampleRate);
}
#endif
- try
+ m_callbackInfo = {
+ /* kernelAudio = */ this,
+ /* ready = */ true,
+ /* withJack = */ getAPI() == G_SYS_API_JACK,
+ /* outBuf = */ nullptr, // filled later on in audio callback
+ /* inBuf = */ nullptr, // filled later on in audio callback
+ /* bufferSize = */ 0, // filled later on in audio callback
+ /* channelsOutCount = */ m_channelsOutCount,
+ /* channelsInCount = */ m_channelsInCount};
+
+ RtAudioErrorType res = m_rtAudio->openStream(
+ &outParams, // output params
+ conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected
+ RTAUDIO_FLOAT32, // audio format
+ m_realSampleRate, // sample rate
+ &m_realBufferSize, // buffer size in byte
+ &audioCallback, // audio callback
+ &m_callbackInfo, // user data passed to callback
+ &options);
+
+ if (res == RtAudioErrorType::RTAUDIO_NO_ERROR)
{
- rtSystem_->openStream(
- &outParams, // output params
- conf::conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected
- RTAUDIO_FLOAT32, // audio format
- conf::conf.samplerate, // sample rate
- &realBufsize_, // buffer size in byte
- &callback_, // audio callback
- nullptr, // user data (unused)
- &options);
-
- model::get().kernel.audioReady = true;
- model::swap(model::SwapType::NONE);
+ m_ready = true;
return 1;
}
- catch (RtAudioError& e)
+ else
{
- u::log::print("[KA] rtSystem_ init error: %s\n", e.getMessage());
closeDevice();
return 0;
}
/* -------------------------------------------------------------------------- */
-int startStream()
+int KernelAudio::startStream()
{
- try
+ if (m_rtAudio->startStream() == RtAudioErrorType::RTAUDIO_NO_ERROR)
{
- rtSystem_->startStream();
- u::log::print("[KA] latency = %lu\n", rtSystem_->getStreamLatency());
+ u::log::print("[KA] Start stream - latency = %lu\n", m_rtAudio->getStreamLatency());
return 1;
}
- catch (RtAudioError& e)
- {
- u::log::print("[KA] Start stream error: %s\n", e.getMessage());
- return 0;
- }
+ return 0;
}
/* -------------------------------------------------------------------------- */
-int stopStream()
+int KernelAudio::stopStream()
{
- try
+ if (m_rtAudio->stopStream() == RtAudioErrorType::RTAUDIO_NO_ERROR)
{
- rtSystem_->stopStream();
+ u::log::print("[KA] Stop stream\n");
return 1;
}
- catch (RtAudioError& /*e*/)
- {
- u::log::print("[KA] Stop stream error\n");
- return 0;
- }
+ return 0;
}
/* -------------------------------------------------------------------------- */
-int closeDevice()
+void KernelAudio::closeDevice()
{
- if (rtSystem_->isStreamOpen())
- {
- rtSystem_->stopStream();
- rtSystem_->closeStream();
- delete rtSystem_;
- rtSystem_ = nullptr;
- }
- return 1;
+ if (!m_rtAudio->isStreamOpen())
+ return;
+ m_rtAudio->stopStream();
+ m_rtAudio->closeStream();
+ m_rtAudio.reset(nullptr);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool KernelAudio::isReady() const
+{
+ return m_ready;
}
/* -------------------------------------------------------------------------- */
-unsigned getRealBufSize() { return realBufsize_; }
-bool isInputEnabled() { return inputEnabled_; }
+int KernelAudio::getBufferSize() const { return static_cast<int>(m_realBufferSize); }
+int KernelAudio::getSampleRate() const { return m_realSampleRate; }
+int KernelAudio::getChannelsOutCount() const { return m_channelsOutCount; }
+int KernelAudio::getChannelsInCount() const { return m_channelsInCount; }
+bool KernelAudio::isInputEnabled() const { return m_inputEnabled; }
/* -------------------------------------------------------------------------- */
-Device getDevice(const char* name)
+m::KernelAudio::Device KernelAudio::getDevice(const char* name) const
{
- for (Device device : devices_)
+ for (Device device : m_devices)
if (name == device.name)
return device;
return {0, false};
/* -------------------------------------------------------------------------- */
-const std::vector<Device>& getDevices()
+const std::vector<m::KernelAudio::Device>& KernelAudio::getDevices() const
{
- return devices_;
+ return m_devices;
}
/* -------------------------------------------------------------------------- */
-bool hasAPI(int API)
+#ifdef WITH_AUDIO_JACK
+jack_client_t* KernelAudio::getJackHandle() const
+{
+ return static_cast<jack_client_t*>(m_rtAudio->HACK__getJackClient());
+}
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+bool KernelAudio::hasAPI(int API) const
{
std::vector<RtAudio::Api> APIs;
RtAudio::getCompiledApi(APIs);
return false;
}
-int getAPI() { return api_; }
+int KernelAudio::getAPI() const { return m_api; }
/* -------------------------------------------------------------------------- */
-void logCompiledAPIs()
+void KernelAudio::logCompiledAPIs()
{
std::vector<RtAudio::Api> APIs;
RtAudio::getCompiledApi(APIs);
u::log::print("[KA] Compiled RtAudio APIs: %d\n", APIs.size());
- for (const RtAudio::Api& api_ : APIs)
+ for (const RtAudio::Api& m_api : APIs)
{
- switch (api_)
+ switch (m_api)
{
case RtAudio::Api::LINUX_ALSA:
u::log::print(" ALSA\n");
/* -------------------------------------------------------------------------- */
-#ifdef WITH_AUDIO_JACK
-
-JackState jackTransportQuery()
+m::KernelAudio::Device KernelAudio::fetchDevice(size_t deviceIndex) const
{
- if (api_ != G_SYS_API_JACK)
- return {};
+ RtAudio::DeviceInfo info = m_rtAudio->getDeviceInfo(deviceIndex);
- jack_position_t position;
- jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position);
+ if (!info.probed)
+ {
+ u::log::print("[KA] Can't probe device %d\n", deviceIndex);
+ return {deviceIndex};
+ }
return {
- ts != JackTransportStopped,
- position.beats_per_minute,
- position.frame};
-}
-
-/* -------------------------------------------------------------------------- */
-
-void jackStart()
-{
- if (api_ == G_SYS_API_JACK)
- jack_transport_start(jackGetHandle_());
+ deviceIndex,
+ true,
+ info.name,
+ static_cast<int>(info.outputChannels),
+ static_cast<int>(info.inputChannels),
+ static_cast<int>(info.duplexChannels),
+ info.isDefaultOutput,
+ info.isDefaultInput,
+ u::vector::cast<int>(info.sampleRates)};
}
/* -------------------------------------------------------------------------- */
-void jackSetPosition(uint32_t frame)
+std::vector<m::KernelAudio::Device> KernelAudio::fetchDevices() const
{
- if (api_ != G_SYS_API_JACK)
- return;
- jack_position_t position;
- jack_transport_query(jackGetHandle_(), &position);
- position.frame = frame;
- jack_transport_reposition(jackGetHandle_(), &position);
+ std::vector<Device> out;
+ for (unsigned i = 0; i < m_rtAudio->getDeviceCount(); i++)
+ out.push_back(fetchDevice(i));
+ return out;
}
/* -------------------------------------------------------------------------- */
-void jackSetBpm(double bpm)
+void KernelAudio::printDevices(const std::vector<m::KernelAudio::Device>& devices) const
{
- if (api_ != G_SYS_API_JACK)
- return;
- jack_position_t position;
- jack_transport_query(jackGetHandle_(), &position);
- position.valid = jack_position_bits_t::JackPositionBBT;
- position.bar = 0; // no such info from Giada
- position.beat = 0; // no such info from Giada
- position.tick = 0; // no such info from Giada
- position.beats_per_minute = bpm;
- jack_transport_reposition(jackGetHandle_(), &position);
+ u::log::print("[KA] %d device(s) found\n", devices.size());
+ for (const m::KernelAudio::Device& d : devices)
+ {
+ u::log::print(" %d) %s\n", d.index, d.name);
+ u::log::print(" ins=%d outs=%d duplex=%d\n", d.maxInputChannels, d.maxOutputChannels, d.maxDuplexChannels);
+ u::log::print(" isDefaultOut=%d isDefaultIn=%d\n", d.isDefaultOut, d.isDefaultIn);
+ u::log::print(" sampleRates:\n\t");
+ for (int s : d.sampleRates)
+ u::log::print("%d ", s);
+ u::log::print("\n");
+ }
}
/* -------------------------------------------------------------------------- */
-void jackStop()
+int KernelAudio::audioCallback(void* outBuf, void* inBuf, unsigned bufferSize,
+ double /*streamTime*/, RtAudioStreamStatus /*status*/, void* data)
{
- if (api_ == G_SYS_API_JACK)
- jack_transport_stop(jackGetHandle_());
+ CallbackInfo info = *static_cast<CallbackInfo*>(data);
+ info.outBuf = outBuf;
+ info.inBuf = inBuf;
+ info.bufferSize = bufferSize;
+ return info.kernelAudio->onAudioCallback(info);
}
-
-#endif // WITH_AUDIO_JACK
-} // namespace giada::m::kernelAudio
+} // namespace giada::m
#ifndef G_KERNELAUDIO_H
#define G_KERNELAUDIO_H
-#include <optional>
+#include "core/conf.h"
+#include "deps/rtaudio/RtAudio.h"
+#include <functional>
+#include <memory>
#include <string>
#include <vector>
#ifdef WITH_AUDIO_JACK
-#include <jack/intclient.h>
-#include <jack/jack.h>
-#include <jack/transport.h>
+#include "core/jackTransport.h"
#endif
-namespace giada::m::kernelAudio
+namespace giada::m
{
-#ifdef WITH_AUDIO_JACK
-
-struct JackState
+class KernelAudio final
{
- bool running;
- double bpm;
- uint32_t frame;
+public:
+ struct Device
+ {
+ size_t index = 0;
+ bool probed = false;
+ std::string name = "";
+ int maxOutputChannels = 0;
+ int maxInputChannels = 0;
+ int maxDuplexChannels = 0;
+ bool isDefaultOut = false;
+ bool isDefaultIn = false;
+ std::vector<int> sampleRates = {};
+ };
- bool operator!=(const JackState& o) const;
-};
+ struct CallbackInfo
+ {
+ KernelAudio* kernelAudio;
+ bool ready;
+ bool withJack;
+ void* outBuf;
+ void* inBuf;
+ int bufferSize;
+ int channelsOutCount;
+ int channelsInCount;
+ };
-#endif
-
-struct Device
-{
- size_t index = 0;
- bool probed = false;
- std::string name = "";
- int maxOutputChannels = 0;
- int maxInputChannels = 0;
- int maxDuplexChannels = 0;
- bool isDefaultOut = false;
- bool isDefaultIn = false;
- std::vector<int> sampleRates = {};
-};
+ KernelAudio();
-int openDevice();
-int closeDevice();
-int startStream();
-int stopStream();
+ static void logCompiledAPIs();
-bool isReady();
-bool isInputEnabled();
-unsigned getRealBufSize();
-bool hasAPI(int API);
-int getAPI();
-void logCompiledAPIs();
-Device getDevice(const char* name);
-const std::vector<Device>& getDevices();
+ int openDevice(const Conf::Data& conf);
+ void closeDevice();
+ int startStream();
+ int stopStream();
+ bool isReady() const;
+ bool isInputEnabled() const;
+ int getBufferSize() const;
+ int getSampleRate() const;
+ int getChannelsOutCount() const;
+ int getChannelsInCount() const;
+ bool hasAPI(int API) const;
+ int getAPI() const;
+ Device getDevice(const char* name) const;
+ const std::vector<Device>& getDevices() const;
#ifdef WITH_AUDIO_JACK
+ jack_client_t* getJackHandle() const;
+#endif
+
+ /* onAudioCallback
+ Main callback invoked on each audio block. */
+
+ std::function<int(CallbackInfo)> onAudioCallback;
-void jackStart();
-void jackStop();
-void jackSetPosition(uint32_t frame);
-void jackSetBpm(double bpm);
-JackState jackTransportQuery();
+private:
+ static int audioCallback(void*, void*, unsigned, double, RtAudioStreamStatus, void*);
+ Device fetchDevice(size_t deviceIndex) const;
+ std::vector<Device> fetchDevices() const;
+ void printDevices(const std::vector<Device>& devices) const;
+
+#ifdef WITH_AUDIO_JACK
+ JackTransport m_jackTransport;
#endif
-} // namespace giada::m::kernelAudio
+ std::vector<Device> m_devices;
+ std::unique_ptr<RtAudio> m_rtAudio;
+ CallbackInfo m_callbackInfo;
+ bool m_ready;
+ bool m_inputEnabled;
+ unsigned m_realBufferSize; // Real buffer size from the soundcard
+ int m_realSampleRate; // Sample rate might differ if JACK in use
+ int m_channelsOutCount;
+ int m_channelsInCount;
+ int m_api;
+};
+} // namespace giada::m
#endif
*
* -------------------------------------------------------------------------- */
-#include "kernelMidi.h"
-#include "const.h"
-#include "midiDispatcher.h"
-#include "midiMapConf.h"
+#include "core/kernelMidi.h"
+#include "core/const.h"
#include "utils/log.h"
-#include <RtMidi.h>
+#include <cassert>
-namespace giada
-{
-namespace m
-{
-namespace kernelMidi
+namespace giada::m
{
namespace
{
-bool status_ = false;
-int api_ = 0;
-RtMidiOut* midiOut_ = nullptr;
-RtMidiIn* midiIn_ = nullptr;
-unsigned numOutPorts_ = 0;
-unsigned numInPorts_ = 0;
-
-static void callback_(double /*t*/, std::vector<unsigned char>* msg, void* /*data*/)
+constexpr auto OUTPUT_NAME = "Giada MIDI output";
+constexpr auto INPUT_NAME = "Giada MIDI input";
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<unsigned char> split_(uint32_t iValue)
{
- if (msg->size() < 3)
- {
- //u::log::print("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size());
- //for (unsigned i=0; i<msg->size(); i++)
- // u::log::print("%X", (int) msg->at(i));
- //u::log::print("\n");
- return;
- }
- midiDispatcher::dispatch(msg->at(0), msg->at(1), msg->at(2));
+ return {
+ static_cast<unsigned char>((iValue >> 24) & 0xFF),
+ static_cast<unsigned char>((iValue >> 16) & 0xFF),
+ static_cast<unsigned char>((iValue >> 8) & 0xFF)};
}
/* -------------------------------------------------------------------------- */
-void sendMidiLightningInitMsgs_()
+uint32_t join_(int b1, int b2, int b3)
{
- for (const midimap::Message& m : midimap::midimap.initCommands)
- {
- if (m.value != 0x0 && m.channel != -1)
- {
- u::log::print("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", m.channel, m.value);
- MidiEvent e(m.value);
- e.setChannel(m.channel);
- send(e.getRaw());
- }
- }
+ return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
}
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void setApi(int api)
+KernelMidi::KernelMidi()
+: onMidiReceived(nullptr)
{
- api_ = api;
- u::log::print("[KM] using system 0x%x\n", api_);
}
/* -------------------------------------------------------------------------- */
-int openOutDevice(int port)
+bool KernelMidi::openOutDevice(int api, int port)
{
- try
- {
- midiOut_ = new RtMidiOut((RtMidi::Api)api_, "Giada MIDI Output");
- status_ = true;
- }
- catch (RtMidiError& error)
- {
- u::log::print("[KM] MIDI out device error: %s\n", error.getMessage());
- status_ = false;
- return 0;
- }
-
- /* print output ports */
-
- numOutPorts_ = midiOut_->getPortCount();
- u::log::print("[KM] %d output MIDI ports found\n", numOutPorts_);
- for (unsigned i = 0; i < numOutPorts_; i++)
- u::log::print(" %d) %s\n", i, getOutPortName(i));
+ if (port == -1)
+ return false;
- /* try to open a port, if enabled */
-
- if (port != -1 && numOutPorts_ > 0)
- {
- try
- {
- midiOut_->openPort(port, getOutPortName(port));
- u::log::print("[KM] MIDI out port %d open\n", port);
-
- /* TODO - it shold send midiLightning message only if there is a map loaded
- and available in midimap:: */
-
- sendMidiLightningInitMsgs_();
- return 1;
- }
- catch (RtMidiError& error)
- {
- u::log::print("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage());
- status_ = false;
- return 0;
- }
- }
- else
- return 2;
+ m_midiOut = makeDevice<RtMidiOut>(api, port, OUTPUT_NAME);
+ return m_midiOut != nullptr;
}
/* -------------------------------------------------------------------------- */
-int openInDevice(int port)
+bool KernelMidi::openInDevice(int api, int port)
{
- try
- {
- midiIn_ = new RtMidiIn((RtMidi::Api)api_, "Giada MIDI input");
- status_ = true;
- }
- catch (RtMidiError& error)
- {
- u::log::print("[KM] MIDI in device error: %s\n", error.getMessage());
- status_ = false;
- return 0;
- }
+ if (port == -1)
+ return false;
- /* print input ports */
+ m_midiIn = makeDevice<RtMidiIn>(api, port, INPUT_NAME);
+ if (m_midiIn == nullptr)
+ return false;
- numInPorts_ = midiIn_->getPortCount();
- u::log::print("[KM] %d input MIDI ports found\n", numInPorts_);
- for (unsigned i = 0; i < numInPorts_; i++)
- u::log::print(" %d) %s\n", i, getInPortName(i));
+ m_midiIn->setCallback(&s_callback, this);
+ m_midiIn->ignoreTypes(true, false, true); // Ignore all system/time msgs, for now
- /* try to open a port, if enabled */
+ return true;
+}
- if (port != -1 && numInPorts_ > 0)
- {
- try
- {
- midiIn_->openPort(port, getInPortName(port));
- midiIn_->ignoreTypes(true, false, true); // ignore all system/time msgs, for now
- u::log::print("[KM] MIDI in port %d open\n", port);
- midiIn_->setCallback(&callback_);
- return 1;
- }
- catch (RtMidiError& error)
- {
- u::log::print("[KM] unable to open MIDI in port %d: %s\n", port, error.getMessage());
- status_ = false;
- return 0;
- }
- }
- else
- return 2;
+/* -------------------------------------------------------------------------- */
+
+void KernelMidi::logPorts()
+{
+ if (m_midiOut != nullptr)
+ logPorts(*m_midiOut, OUTPUT_NAME);
+ if (m_midiIn != nullptr)
+ logPorts(*m_midiIn, INPUT_NAME);
}
/* -------------------------------------------------------------------------- */
-bool hasAPI(int API)
+bool KernelMidi::hasAPI(int API) const
{
std::vector<RtMidi::Api> APIs;
RtMidi::getCompiledApi(APIs);
/* -------------------------------------------------------------------------- */
-std::string getOutPortName(unsigned p)
-{
- try
- {
- return midiOut_->getPortName(p);
- }
- catch (RtMidiError& /*error*/)
- {
- return "";
- }
-}
-
-std::string getInPortName(unsigned p)
-{
- try
- {
- return midiIn_->getPortName(p);
- }
- catch (RtMidiError& /*error*/)
- {
- return "";
- }
-}
+std::string KernelMidi::getOutPortName(unsigned p) const { return getPortName(*m_midiOut, p); }
+std::string KernelMidi::getInPortName(unsigned p) const { return getPortName(*m_midiIn, p); }
/* -------------------------------------------------------------------------- */
-void send(uint32_t data)
+void KernelMidi::send(uint32_t data)
{
- if (!status_)
+ if (m_midiOut == nullptr)
return;
- std::vector<unsigned char> msg(1, getB1(data));
- msg.push_back(getB2(data));
- msg.push_back(getB3(data));
+ std::vector<unsigned char> msg = split_(data);
- midiOut_->sendMessage(&msg);
+ m_midiOut->sendMessage(&msg);
u::log::print("[KM::send] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
}
/* -------------------------------------------------------------------------- */
-void send(int b1, int b2, int b3)
+void KernelMidi::send(int b1, int b2, int b3)
{
- if (!status_)
+ if (m_midiOut == nullptr)
return;
std::vector<unsigned char> msg(1, b1);
if (b3 != -1)
msg.push_back(b3);
- midiOut_->sendMessage(&msg);
+ m_midiOut->sendMessage(&msg);
u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3);
}
/* -------------------------------------------------------------------------- */
-void sendMidiLightning(uint32_t learnt, const midimap::Message& m)
+unsigned KernelMidi::countOutPorts() const { return m_midiOut != nullptr ? m_midiOut->getPortCount() : 0; }
+unsigned KernelMidi::countInPorts() const { return m_midiIn != nullptr ? m_midiIn->getPortCount() : 0; }
+
+/* -------------------------------------------------------------------------- */
+
+void KernelMidi::s_callback(double /*t*/, std::vector<unsigned char>* msg, void* data)
{
- // Skip lightning message if not defined in midi map
+ static_cast<KernelMidi*>(data)->callback(msg);
+}
+
+/* -------------------------------------------------------------------------- */
- if (!midimap::isDefined(m))
+void KernelMidi::callback(std::vector<unsigned char>* msg)
+{
+ assert(onMidiReceived != nullptr);
+
+ if (msg->size() < 3)
{
- u::log::print("[KM::sendMidiLightning] message skipped (not defined in midimap)");
+ G_DEBUG("Received unknown MIDI signal - bytes=" << msg->size());
return;
}
- u::log::print("[KM::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n",
- learnt, m.channel, m.value, m.offset);
-
- /* Isolate 'channel' from learnt message and offset it as requested by 'nn' in
- the midimap configuration file. */
-
- uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
+ onMidiReceived(join_(msg->at(0), msg->at(1), msg->at(2)));
+}
- /* Merge the previously prepared channel into final message, and finally send
- it. */
+/* -------------------------------------------------------------------------- */
- out |= m.value | (m.channel << 24);
- send(out);
+template <typename Device>
+std::unique_ptr<Device> KernelMidi::makeDevice(int api, int port, std::string name) const
+{
+ try
+ {
+ auto device = std::make_unique<Device>(static_cast<RtMidi::Api>(api), name);
+ device->openPort(port, device->getPortName(port));
+ return device;
+ }
+ catch (RtMidiError& error)
+ {
+ u::log::print("[KM] Device '%s' error on open: %s\n", name.c_str(), error.getMessage());
+ return nullptr;
+ }
}
+template std::unique_ptr<RtMidiOut> KernelMidi::makeDevice(int, int, std::string) const;
+template std::unique_ptr<RtMidiIn> KernelMidi::makeDevice(int, int, std::string) const;
+
/* -------------------------------------------------------------------------- */
-unsigned countInPorts() { return numInPorts_; }
-unsigned countOutPorts() { return numOutPorts_; }
-bool getStatus() { return status_; }
+std::string KernelMidi::getPortName(RtMidi& device, int port) const
+{
+ try
+ {
+ return device.getPortName(port);
+ }
+ catch (RtMidiError& /*error*/)
+ {
+ return "";
+ }
+}
/* -------------------------------------------------------------------------- */
-int getB1(uint32_t iValue) { return (iValue >> 24) & 0xFF; }
-int getB2(uint32_t iValue) { return (iValue >> 16) & 0xFF; }
-int getB3(uint32_t iValue) { return (iValue >> 8) & 0xFF; }
-
-uint32_t getIValue(int b1, int b2, int b3)
+void KernelMidi::logPorts(RtMidi& device, std::string name) const
{
- return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
+ u::log::print("[KM] Device '%s': %d MIDI ports found\n", name.c_str(), device.getPortCount());
+ for (unsigned i = 0; i < device.getPortCount(); i++)
+ u::log::print(" %d) %s\n", i, device.getPortName(i));
}
-} // namespace kernelMidi
-} // namespace m
-} // namespace giada
+} // namespace giada::m
#ifndef G_KERNELMIDI_H
#define G_KERNELMIDI_H
-#include "midiMapConf.h"
+#include "midiMapper.h"
+#include <RtMidi.h>
#include <cstdint>
+#include <functional>
+#include <memory>
#include <string>
-namespace giada
+namespace giada::m
{
-namespace m
+class KernelMidi final
{
-namespace kernelMidi
-{
-int getB1(uint32_t iValue);
-int getB2(uint32_t iValue);
-int getB3(uint32_t iValue);
-
-uint32_t getIValue(int b1, int b2, int b3);
+public:
+ KernelMidi();
-/* send
-Sends a MIDI message 's' as uint32_t or as separate bytes. */
+ unsigned countOutPorts() const;
+ unsigned countInPorts() const;
-void send(uint32_t s);
-void send(int b1, int b2 = -1, int b3 = -1);
+ /* getOut/InPortName
+ Returns the name of the port 'p'. */
-/* sendMidiLightning
-Sends a MIDI lightning message defined by 'msg'. */
+ std::string getOutPortName(unsigned p) const;
+ std::string getInPortName(unsigned p) const;
-void sendMidiLightning(uint32_t learnt, const midimap::Message& msg);
+ bool hasAPI(int API) const;
-/* setApi
-Sets the Api in use for both in & out messages. */
+ /* send
+ Sends a MIDI message 's' as uint32_t or as separate bytes. */
-void setApi(int api);
+ void send(uint32_t s);
+ void send(int b1, int b2 = -1, int b3 = -1);
-/* getStatus
-Returns current engine status. */
+ /* setApi
+ Sets the Api in use for both in & out messages. */
-bool getStatus();
+ void setApi(int api);
-/* open/close/in/outDevice */
+ bool openOutDevice(int api, int port);
+ bool openInDevice(int api, int port);
-int openOutDevice(int port);
-int openInDevice(int port);
-int closeInDevice();
-int closeOutDevice();
+ void logPorts();
-/* getIn/OutPortName
-Returns the name of the port 'p'. */
+ std::function<void(uint32_t)> onMidiReceived;
-std::string getInPortName(unsigned p);
-std::string getOutPortName(unsigned p);
+private:
+ template <typename Device>
+ std::unique_ptr<Device> makeDevice(int api, int port, std::string name) const;
-unsigned countInPorts();
-unsigned countOutPorts();
+ static void s_callback(double, std::vector<unsigned char>*, void*);
+ void callback(std::vector<unsigned char>*);
-bool hasAPI(int API);
+ std::string getPortName(RtMidi&, int port) const;
+ void logPorts(RtMidi&, std::string name) const;
-} // namespace kernelMidi
-} // namespace m
-} // namespace giada
+ std::unique_ptr<RtMidiOut> m_midiOut;
+ std::unique_ptr<RtMidiIn> m_midiIn;
+};
+} // namespace giada::m
#endif
* -------------------------------------------------------------------------- */
#include "metronome.h"
-#include "core/audioBuffer.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
namespace giada::m
{
/* -------------------------------------------------------------------------- */
-void Metronome::render(AudioBuffer& outBuf)
+void Metronome::render(mcl::AudioBuffer& outBuf)
{
const float* data = m_click == Click::BEAT ? beat : bar;
for (Frame f = m_offset; f < outBuf.countFrames() && m_rendering; f++)
#include "core/types.h"
-namespace giada::m
+namespace mcl
{
class AudioBuffer;
+}
+namespace giada::m
+{
class Metronome
{
public:
BAR
};
- void render(AudioBuffer& outBuf);
+ void render(mcl::AudioBuffer& outBuf);
void trigger(Click c, Frame o);
bool running = false;
- private:
+private:
static constexpr Frame CLICK_SIZE = 38;
static constexpr float beat[CLICK_SIZE] = {
#include "core/model/model.h"
#include "core/plugins/plugin.h"
#include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/recorder.h"
#include "core/types.h"
#include "glue/events.h"
#include "glue/plugin.h"
#include <cassert>
#include <vector>
-namespace giada::m::midiDispatcher
+namespace giada::m
{
-namespace
+MidiDispatcher::MidiDispatcher(model::Model& m)
+: onDispatch(nullptr)
+, m_learnCb(nullptr)
+, m_model(m)
{
-/* cb_midiLearn, cb_data_
-Callback prepared by the gdMidiGrabber window and called by midiDispatcher. It
-contains things to do once the midi message has been stored. */
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::startChannelLearn(int param, ID channelId, std::function<void()> f)
+{
+ m_learnCb = [=](m::MidiEvent e) { learnChannel(e, param, channelId, f); };
+}
+
+void MidiDispatcher::startMasterLearn(int param, std::function<void()> f)
+{
+ m_learnCb = [=](m::MidiEvent e) { learnMaster(e, param, f); };
+}
-std::function<void()> signalCb_ = nullptr;
-std::function<void(MidiEvent)> learnCb_ = nullptr;
+#ifdef WITH_VST
+
+void MidiDispatcher::startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
+{
+ m_learnCb = [=](m::MidiEvent e) { learnPlugin(e, paramIndex, pluginId, f); };
+}
+
+#endif
+
+void MidiDispatcher::stopLearn()
+{
+ m_learnCb = nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::clearMasterLearn(int param, std::function<void()> f)
+{
+ learnMaster(MidiEvent(), param, f); // Empty event (0x0)
+}
+
+void MidiDispatcher::clearChannelLearn(int param, ID channelId, std::function<void()> f)
+{
+ learnChannel(MidiEvent(), param, channelId, f); // Empty event (0x0)
+}
+
+#ifdef WITH_VST
+
+void MidiDispatcher::clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
+{
+ learnPlugin(MidiEvent(), paramIndex, pluginId, f); // Empty event (0x0)
+}
+
+#endif
/* -------------------------------------------------------------------------- */
-bool isMasterMidiInAllowed_(int c)
+void MidiDispatcher::dispatch(uint32_t msg)
{
- int filter = model::get().midiIn.filter;
- bool enabled = model::get().midiIn.enabled;
+ assert(onDispatch != nullptr);
+
+ /* Here we want to catch two things: a) note on/note off from a MIDI keyboard
+ and b) knob/wheel/slider movements from a MIDI controller.
+ We must also fix the velocity zero issue for those devices that sends NOTE
+ OFF events as NOTE ON + velocity zero. Let's make it a real NOTE OFF event. */
+
+ MidiEvent midiEvent(msg);
+ midiEvent.fixVelocityZero();
+
+ u::log::print("[midiDispatcher] MIDI received - 0x%X (chan %d)\n", midiEvent.getRaw(),
+ midiEvent.getChannel());
+
+ /* Start dispatcher. Don't parse channels if MIDI learn is ON, just learn
+ the incoming MIDI signal. The action is not invoked directly, but scheduled
+ to be perfomed by the Event Dispatcher. */
+
+ Action action = {0, 0, 0, midiEvent};
+ auto event = m_learnCb != nullptr ? EventDispatcher::EventType::MIDI_DISPATCHER_LEARN : EventDispatcher::EventType::MIDI_DISPATCHER_PROCESS;
+
+ onDispatch(event, action);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::learn(const MidiEvent& e)
+{
+ assert(m_learnCb != nullptr);
+ m_learnCb(e);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::process(const MidiEvent& e)
+{
+ assert(onEventReceived != nullptr);
+
+ processMaster(e);
+ processChannels(e);
+ onEventReceived();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool MidiDispatcher::isMasterMidiInAllowed(int c)
+{
+ int filter = m_model.get().midiIn.filter;
+ bool enabled = m_model.get().midiIn.enabled;
return enabled && (filter == -1 || filter == c);
}
/* -------------------------------------------------------------------------- */
-bool isChannelMidiInAllowed_(ID channelId, int c)
+bool MidiDispatcher::isChannelMidiInAllowed(ID channelId, int c)
{
- return model::get().getChannel(channelId).midiLearner.isAllowed(c);
+ return m_model.get().getChannel(channelId).midiLearner.isAllowed(c);
}
/* -------------------------------------------------------------------------- */
#ifdef WITH_VST
-void processPlugins_(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent)
+void MidiDispatcher::processPlugins(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent)
{
uint32_t pure = midiEvent.getRawNoVelocity();
float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, 1.0f);
/* -------------------------------------------------------------------------- */
-void processChannels_(const MidiEvent& midiEvent)
+void MidiDispatcher::processChannels(const MidiEvent& midiEvent)
{
uint32_t pure = midiEvent.getRawNoVelocity();
- for (const channel::Data& c : model::get().channels)
+ for (const Channel& c : m_model.get().channels)
{
/* Do nothing on this channel if MIDI in is disabled or filtered out for
#ifdef WITH_VST
/* Process learned plugins parameters. */
- processPlugins_(c.plugins, midiEvent);
+ processPlugins(c.plugins, midiEvent);
#endif
/* Redirect raw MIDI message (pure + velocity) to plug-ins in armed
/* -------------------------------------------------------------------------- */
-void processMaster_(const MidiEvent& midiEvent)
+void MidiDispatcher::processMaster(const MidiEvent& midiEvent)
{
const uint32_t pure = midiEvent.getRawNoVelocity();
- const model::MidiIn& midiIn = model::get().midiIn;
+ const model::MidiIn& midiIn = m_model.get().midiIn;
if (pure == midiIn.rewind)
{
/* -------------------------------------------------------------------------- */
-void learnChannel_(MidiEvent e, int param, ID channelId, std::function<void()> doneCb)
+void MidiDispatcher::learnChannel(MidiEvent e, int param, ID channelId, std::function<void()> doneCb)
{
- if (!isChannelMidiInAllowed_(channelId, e.getChannel()))
+ if (!isChannelMidiInAllowed(channelId, e.getChannel()))
return;
uint32_t raw = e.getRawNoVelocity();
- channel::Data& ch = model::get().getChannel(channelId);
+ Channel& ch = m_model.get().getChannel(channelId);
switch (param)
{
break;
}
- model::swap(model::SwapType::SOFT);
+ m_model.swap(model::SwapType::SOFT);
stopLearn();
doneCb();
}
-void learnMaster_(MidiEvent e, int param, std::function<void()> doneCb)
+/* -------------------------------------------------------------------------- */
+
+void MidiDispatcher::learnMaster(MidiEvent e, int param, std::function<void()> doneCb)
{
- if (!isMasterMidiInAllowed_(e.getChannel()))
+ if (!isMasterMidiInAllowed(e.getChannel()))
return;
uint32_t raw = e.getRawNoVelocity();
switch (param)
{
case G_MIDI_IN_REWIND:
- model::get().midiIn.rewind = raw;
+ m_model.get().midiIn.rewind = raw;
break;
case G_MIDI_IN_START_STOP:
- model::get().midiIn.startStop = raw;
+ m_model.get().midiIn.startStop = raw;
break;
case G_MIDI_IN_ACTION_REC:
- model::get().midiIn.actionRec = raw;
+ m_model.get().midiIn.actionRec = raw;
break;
case G_MIDI_IN_INPUT_REC:
- model::get().midiIn.inputRec = raw;
+ m_model.get().midiIn.inputRec = raw;
break;
case G_MIDI_IN_METRONOME:
- model::get().midiIn.metronome = raw;
+ m_model.get().midiIn.metronome = raw;
break;
case G_MIDI_IN_VOLUME_IN:
- model::get().midiIn.volumeIn = raw;
+ m_model.get().midiIn.volumeIn = raw;
break;
case G_MIDI_IN_VOLUME_OUT:
- model::get().midiIn.volumeOut = raw;
+ m_model.get().midiIn.volumeOut = raw;
break;
case G_MIDI_IN_BEAT_DOUBLE:
- model::get().midiIn.beatDouble = raw;
+ m_model.get().midiIn.beatDouble = raw;
break;
case G_MIDI_IN_BEAT_HALF:
- model::get().midiIn.beatHalf = raw;
+ m_model.get().midiIn.beatHalf = raw;
break;
}
- model::swap(model::SwapType::SOFT);
+ m_model.swap(model::SwapType::SOFT);
stopLearn();
doneCb();
}
+/* -------------------------------------------------------------------------- */
+
#ifdef WITH_VST
-void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb)
+void MidiDispatcher::learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb)
{
- model::DataLock lock(model::SwapType::NONE);
-
- Plugin* plugin = model::find<Plugin>(pluginId);
+ model::DataLock lock = m_model.lockData(model::SwapType::NONE);
+ Plugin* plugin = m_model.findShared<Plugin>(pluginId);
assert(plugin != nullptr);
assert(paramIndex < plugin->midiInParams.size());
}
#endif
-
-/* -------------------------------------------------------------------------- */
-
-void triggerSignalCb_()
-{
- if (signalCb_ == nullptr)
- return;
- signalCb_();
- signalCb_ = nullptr;
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void startChannelLearn(int param, ID channelId, std::function<void()> f)
-{
- learnCb_ = [=](m::MidiEvent e) { learnChannel_(e, param, channelId, f); };
-}
-
-void startMasterLearn(int param, std::function<void()> f)
-{
- learnCb_ = [=](m::MidiEvent e) { learnMaster_(e, param, f); };
-}
-
-#ifdef WITH_VST
-
-void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
-{
- learnCb_ = [=](m::MidiEvent e) { learnPlugin_(e, paramIndex, pluginId, f); };
-}
-
-#endif
-
-void stopLearn()
-{
- learnCb_ = nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearMasterLearn(int param, std::function<void()> f)
-{
- learnMaster_(MidiEvent(), param, f); // Empty event (0x0)
-}
-
-void clearChannelLearn(int param, ID channelId, std::function<void()> f)
-{
- learnChannel_(MidiEvent(), param, channelId, f); // Empty event (0x0)
-}
-
-#ifdef WITH_VST
-
-void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f)
-{
- learnPlugin_(MidiEvent(), paramIndex, pluginId, f); // Empty event (0x0)
-}
-
-#endif
-
-/* -------------------------------------------------------------------------- */
-
-void dispatch(int byte1, int byte2, int byte3)
-{
- /* Here we want to catch two things: a) note on/note off from a keyboard and
- b) knob/wheel/slider movements from a controller.
- We must also fix the velocity zero issue for those devices that sends NOTE
- OFF events as NOTE ON + velocity zero. Let's make it a real NOTE OFF event. */
-
- MidiEvent midiEvent(byte1, byte2, byte3);
- midiEvent.fixVelocityZero();
-
- u::log::print("[midiDispatcher] MIDI received - 0x%X (chan %d)\n", midiEvent.getRaw(),
- midiEvent.getChannel());
-
- /* Start dispatcher. If midi learn is on don't parse channels, just learn
- incoming MIDI signal. Learn callback wants 'pure' MIDI event, i.e. with
- velocity value stripped off. If midi learn is off process master events first,
- then each channel in the stack. This way incoming signals don't get processed
- by glue_* when MIDI learning is on. */
-
- std::function<void()> f = [midiEvent]() {
- if (learnCb_ != nullptr)
- {
- learnCb_(midiEvent);
- }
- else
- {
- processMaster_(midiEvent);
- processChannels_(midiEvent);
- triggerSignalCb_();
- }
- };
-
- eventDispatcher::pumpMidiEvent({eventDispatcher::EventType::FUNCTION, 0, 0, f});
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f)
-{
- signalCb_ = f;
-}
-} // namespace giada::m::midiDispatcher
+} // namespace giada::m
#ifndef G_MIDI_DISPATCHER_H
#define G_MIDI_DISPATCHER_H
+#include "core/actions/action.h"
#include "core/midiEvent.h"
#include "core/model/model.h"
#include "core/types.h"
#include <cstdint>
#include <functional>
-namespace giada::m::midiDispatcher
+namespace giada::m
{
-void startChannelLearn(int param, ID channelId, std::function<void()> f);
-void startMasterLearn(int param, std::function<void()> f);
-void stopLearn();
-void clearMasterLearn(int param, std::function<void()> f);
-void clearChannelLearn(int param, ID channelId, std::function<void()> f);
+class MidiDispatcher
+{
+public:
+ MidiDispatcher(model::Model&);
+
+ void startChannelLearn(int param, ID channelId, std::function<void()> f);
+ void startMasterLearn(int param, std::function<void()> f);
+ void stopLearn();
+ void clearMasterLearn(int param, std::function<void()> f);
+ void clearChannelLearn(int param, ID channelId, std::function<void()> f);
#ifdef WITH_VST
-void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
-void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
+ void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
+ void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function<void()> f);
#endif
-void dispatch(int byte1, int byte2, int byte3);
+ /* dispatch
+ Main callback invoked by kernelMidi whenever a new MIDI data comes in. */
+
+ void dispatch(uint32_t msg);
+
+ /* learn
+ Learns event 'e'. Called by the Event Dispatcher. */
+
+ void learn(const MidiEvent& e);
+
+ /* process
+ Sends event 'e' to channels (masters and keyboard). Called by the Event
+ Dispatcher. */
+
+ void process(const MidiEvent& e);
+
+ /* onDispatch
+ Callback fired when the dispatch() method is invoked by KernelMidi. */
+
+ std::function<void(EventDispatcher::EventType, Action)> onDispatch;
+
+ /* onEventReceived
+ Callback fired when a MIDI event has been received (passed in by the Event
+ Dispatcher). */
+
+ std::function<void()> onEventReceived;
+
+private:
+ bool isMasterMidiInAllowed(int c);
+ bool isChannelMidiInAllowed(ID channelId, int c);
+
+ void processChannels(const MidiEvent& midiEvent);
+ void processMaster(const MidiEvent& midiEvent);
+
+ void learnChannel(MidiEvent e, int param, ID channelId, std::function<void()> doneCb);
+ void learnMaster(MidiEvent e, int param, std::function<void()> doneCb);
+
+#ifdef WITH_VST
+ void processPlugins(const std::vector<Plugin*>& plugins, const MidiEvent& midiEvent);
+ void learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function<void()> doneCb);
+#endif
+
+ /* cb_midiLearn
+ Callback prepared by the gdMidiGrabber window and called by midiDispatcher.
+ It contains things to do once the midi message has been stored. */
+
+ std::function<void(MidiEvent)> m_learnCb;
-void setSignalCallback(std::function<void()> f);
-} // namespace giada::m::midiDispatcher
+ model::Model& m_model;
+};
+} // namespace giada::m
#endif
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-MidiEvent::MidiEvent(uint32_t raw)
+MidiEvent::MidiEvent(uint32_t raw, int delta)
: m_status((raw & 0xF0000000) >> 24)
, m_channel((raw & 0x0F000000) >> 24)
, m_note((raw & 0x00FF0000) >> 16)
, m_velocity((raw & 0x0000FF00) >> 8)
-, m_delta(0) // not used
+, m_delta(delta)
{
}
/* -------------------------------------------------------------------------- */
/* static_cast to avoid ambiguity with MidiEvent(float). */
-MidiEvent::MidiEvent(int byte1, int byte2, int byte3)
-: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)))
+MidiEvent::MidiEvent(int byte1, int byte2, int byte3, int delta)
+: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)), delta)
{
}
/* -------------------------------------------------------------------------- */
-MidiEvent::MidiEvent(float v)
-: MidiEvent(ENVELOPE, 0, 0)
+MidiEvent::MidiEvent(float v, int delta)
+: MidiEvent(ENVELOPE, 0, 0, delta)
{
m_velocity = static_cast<int>(v * FLOAT_FACTOR);
}
MidiEvent() = default;
- MidiEvent(uint32_t raw);
- MidiEvent(int byte1, int byte2, int byte3);
+ MidiEvent(uint32_t raw, int delta = 0);
+ MidiEvent(int byte1, int byte2, int byte3, int delta = 0);
/* MidiEvent (4)
A constructor that takes a float parameter. Useful to build ENVELOPE events
for automations, volume and pitch. */
- MidiEvent(float v);
+ MidiEvent(float v, int delta = 0);
int getStatus() const;
int getChannel() const;
void fixVelocityZero();
- private:
+private:
int m_status;
int m_channel;
int m_note;
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "midiMapConf.h"
-#include "const.h"
-#include "deps/json/single_include/nlohmann/json.hpp"
-#include "utils/fs.h"
-#include "utils/log.h"
-#include "utils/string.h"
-#include <cstring>
-#include <filesystem>
-#include <fstream>
-#include <string>
-#include <vector>
-
-namespace nl = nlohmann;
-
-namespace giada
-{
-namespace m
-{
-namespace midimap
-{
-namespace
-{
-bool readInitCommands_(const nl::json& j)
-{
- if (j.find(MIDIMAP_KEY_INIT_COMMANDS) == j.end())
- return false;
-
- for (const auto& jc : j[MIDIMAP_KEY_INIT_COMMANDS])
- {
- Message m;
- m.channel = jc[MIDIMAP_KEY_CHANNEL];
- m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
- m.value = strtoul(m.valueStr.c_str(), nullptr, 16);
-
- midimap.initCommands.push_back(m);
- }
-
- return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool readCommand_(const nl::json& j, Message& m, const std::string& key)
-{
- if (j.find(key) == j.end())
- return false;
-
- const nl::json& jc = j[key];
-
- m.channel = jc[MIDIMAP_KEY_CHANNEL];
- m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
-
- return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void parse_(Message& message)
-{
- /* Remove '0x' part from the original string. */
-
- std::string input = message.valueStr;
-
- std::size_t f = input.find("0x"); // check if "0x" is there
- if (f != std::string::npos)
- input = message.valueStr.replace(f, 2, "");
-
- /* Then transform string value into the actual uint32_t value, by parsing
- each char (i.e. nibble) in the original string. Substitute 'n' with
- zeros. */
-
- std::string output;
- for (unsigned i = 0, p = 24; i < input.length(); i++, p -= 4)
- {
- if (input[i] == 'n')
- {
- output += '0';
- if (message.offset == -1) // do it once
- message.offset = p;
- }
- else
- output += input[i];
- }
-
- /* From string to uint32_t */
-
- message.value = strtoul(output.c_str(), nullptr, 16);
-
- u::log::print("[parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n",
- message.channel, message.valueStr, message.value, message.offset);
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-MidiMap midimap;
-std::string midimapsPath;
-std::vector<std::string> maps;
-
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
- midimapsPath = u::fs::getHomePath() + G_SLASH + "midimaps" + G_SLASH;
-
- /* scan dir of midi maps and load the filenames into <>maps. */
-
- u::log::print("[midiMapConf::init] scanning midimaps directory '%s'...\n",
- midimapsPath);
-
- if (!std::filesystem::exists(midimapsPath))
- {
- u::log::print("[midiMapConf::init] unable to scan midimaps directory!\n");
- return;
- }
-
- for (const auto& d : std::filesystem::directory_iterator(midimapsPath))
- {
- // TODO - check if is a valid midimap file (verify headers)
- if (!d.is_regular_file())
- continue;
- u::log::print("[midiMapConf::init] found midimap '%s'\n", d.path().filename().string());
- maps.push_back(d.path().filename().string());
- }
-
- u::log::print("[midiMapConf::init] total midimaps found: %d\n", maps.size());
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setDefault()
-{
- midimap = MidiMap();
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isDefined(const Message& m)
-{
- return m.offset != -1;
-}
-
-/* -------------------------------------------------------------------------- */
-
-int read(const std::string& file)
-{
- if (file.empty())
- {
- u::log::print("[midiMapConf::read] midimap not specified, nothing to do\n");
- return MIDIMAP_NOT_SPECIFIED;
- }
-
- u::log::print("[midiMapConf::read] reading midimap file '%s'\n", file);
-
- std::ifstream ifs(midimapsPath + file);
- if (!ifs.good())
- return MIDIMAP_UNREADABLE;
-
- nl::json j = nl::json::parse(ifs);
-
- midimap.brand = j[MIDIMAP_KEY_BRAND];
- midimap.device = j[MIDIMAP_KEY_DEVICE];
-
- if (!readInitCommands_(j))
- return MIDIMAP_UNREADABLE;
- if (readCommand_(j, midimap.muteOn, MIDIMAP_KEY_MUTE_ON))
- parse_(midimap.muteOn);
- if (readCommand_(j, midimap.muteOff, MIDIMAP_KEY_MUTE_OFF))
- parse_(midimap.muteOff);
- if (readCommand_(j, midimap.soloOn, MIDIMAP_KEY_SOLO_ON))
- parse_(midimap.soloOn);
- if (readCommand_(j, midimap.soloOff, MIDIMAP_KEY_SOLO_OFF))
- parse_(midimap.soloOff);
- if (readCommand_(j, midimap.waiting, MIDIMAP_KEY_WAITING))
- parse_(midimap.waiting);
- if (readCommand_(j, midimap.playing, MIDIMAP_KEY_PLAYING))
- parse_(midimap.playing);
- if (readCommand_(j, midimap.stopping, MIDIMAP_KEY_STOPPING))
- parse_(midimap.stopping);
- if (readCommand_(j, midimap.stopped, MIDIMAP_KEY_STOPPED))
- parse_(midimap.stopped);
- if (readCommand_(j, midimap.playingInaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE))
- parse_(midimap.playingInaudible);
-
- return MIDIMAP_READ_OK;
-}
-} // namespace midimap
-} // namespace m
-} // namespace giada
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_MIDIMAPCONF_H
-#define G_MIDIMAPCONF_H
-
-#include <string>
-#include <vector>
-
-namespace giada
-{
-namespace m
-{
-namespace midimap
-{
-struct Message
-{
- int channel = 0;
- std::string valueStr = "";
- int offset = -1;
- uint32_t value = 0;
-};
-
-struct MidiMap
-{
- std::string brand;
- std::string device;
- std::vector<Message> initCommands;
- Message muteOn;
- Message muteOff;
- Message soloOn;
- Message soloOff;
- Message waiting;
- Message playing;
- Message stopping;
- Message stopped;
- Message playingInaudible;
-};
-
-/* -------------------------------------------------------------------------- */
-
-/* midimap
-The actual MidiMap struct with data. */
-
-extern MidiMap midimap;
-
-/* midimapsPath
-Path of midimap files, different between OSes. */
-
-extern std::string midimapsPath;
-
-/* maps
-Maps are the available .giadamap files. Each element of the std::vector
-represents a .giadamap filename. */
-
-extern std::vector<std::string> maps;
-
-/* -------------------------------------------------------------------------- */
-
-/* init
-Parses the midi maps folders and find the available maps. */
-
-void init();
-
-/* setDefault
-Sets default values in case no maps are available/chosen. */
-
-void setDefault();
-
-/* isDefined
-Checks whether a specific message has been defined within midi map file. */
-
-bool isDefined(const Message& msg);
-
-/* read
-Reads a midi map from file 'file'. */
-
-int read(const std::string& file);
-} // namespace midimap
-} // namespace m
-} // namespace giada
-
-#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/midiMapper.h"
+#include "core/const.h"
+#include "core/kernelMidi.h"
+#include "core/midiEvent.h"
+#include "utils/fs.h"
+#include "utils/log.h"
+#include "utils/string.h"
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <vector>
+#ifdef WITH_TESTS
+#include "tests/mocks/kernelMidiMock.h"
+#endif
+
+namespace nl = nlohmann;
+
+namespace giada::m
+{
+bool MidiMap::isValid() const
+{
+ return !(brand.empty() || device.empty());
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+MidiMapper<KernelMidiI>::MidiMapper(KernelMidiI& k)
+: m_kernelMidi(k)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+const std::vector<std::string>& MidiMapper<KernelMidiI>::getMapFilesFound() const
+{
+ return m_mapFiles;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::init()
+{
+ m_mapsPath = u::fs::getHomePath() + G_SLASH + "midimaps" + G_SLASH;
+
+ /* scan dir of midi maps and load the filenames into m_mapFiles vector. */
+
+ u::log::print("[MidiMapper::init] scanning midimaps directory '%s'...\n",
+ m_mapsPath);
+
+ if (!std::filesystem::exists(m_mapsPath))
+ {
+ u::log::print("[MidiMapper::init] unable to scan midimaps directory!\n");
+ return;
+ }
+
+ for (const auto& d : std::filesystem::directory_iterator(m_mapsPath))
+ {
+ // TODO - check if is a valid midiMap file (verify headers)
+ if (!d.is_regular_file())
+ continue;
+ u::log::print("[MidiMapper::init] found midiMap '%s'\n", d.path().filename().string());
+ m_mapFiles.push_back(d.path().filename().string());
+ }
+
+ u::log::print("[MidiMapper::init] total midimaps found: %d\n", m_mapFiles.size());
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+int MidiMapper<KernelMidiI>::read(const std::string& file)
+{
+ if (file.empty())
+ {
+ u::log::print("[MidiMapper::read] midiMap not specified, nothing to do\n");
+ return MIDIMAP_NOT_SPECIFIED;
+ }
+
+ u::log::print("[MidiMapper::read] reading midiMap file '%s'\n", file);
+
+ std::ifstream ifs(m_mapsPath + file);
+ if (!ifs.good())
+ return MIDIMAP_UNREADABLE;
+
+ nl::json j = nl::json::parse(ifs);
+
+ currentMap.brand = j[MIDIMAP_KEY_BRAND];
+ currentMap.device = j[MIDIMAP_KEY_DEVICE];
+
+ if (!readInitCommands(currentMap, j))
+ return MIDIMAP_UNREADABLE;
+ if (readCommand(j, currentMap.muteOn, MIDIMAP_KEY_MUTE_ON))
+ parse(currentMap.muteOn);
+ if (readCommand(j, currentMap.muteOff, MIDIMAP_KEY_MUTE_OFF))
+ parse(currentMap.muteOff);
+ if (readCommand(j, currentMap.soloOn, MIDIMAP_KEY_SOLO_ON))
+ parse(currentMap.soloOn);
+ if (readCommand(j, currentMap.soloOff, MIDIMAP_KEY_SOLO_OFF))
+ parse(currentMap.soloOff);
+ if (readCommand(j, currentMap.waiting, MIDIMAP_KEY_WAITING))
+ parse(currentMap.waiting);
+ if (readCommand(j, currentMap.playing, MIDIMAP_KEY_PLAYING))
+ parse(currentMap.playing);
+ if (readCommand(j, currentMap.stopping, MIDIMAP_KEY_STOPPING))
+ parse(currentMap.stopping);
+ if (readCommand(j, currentMap.stopped, MIDIMAP_KEY_STOPPED))
+ parse(currentMap.stopped);
+ if (readCommand(j, currentMap.playingInaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE))
+ parse(currentMap.playingInaudible);
+
+ return MIDIMAP_READ_OK;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::isMessageDefined(const MidiMap::Message& m) const
+{
+ return m.offset != -1;
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::sendInitMessages(const MidiMap& midiMap)
+{
+ if (!midiMap.isValid())
+ return;
+
+ for (const MidiMap::Message& m : midiMap.initCommands)
+ {
+ if (m.value == 0x0 || m.channel == -1)
+ continue;
+ MidiEvent e(m.value);
+ e.setChannel(m.channel);
+ m_kernelMidi.send(e.getRaw());
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::sendMidiLightning(uint32_t learnt, const MidiMap::Message& m)
+{
+ // Skip lightning message if not defined in midi map
+
+ if (!isMessageDefined(m))
+ {
+ u::log::print("[MidiMapper::sendMidiLightning] message skipped (not defined in midiMap)");
+ return;
+ }
+
+ u::log::print("[MidiMapper::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n",
+ learnt, m.channel, m.value, m.offset);
+
+ /* Isolate 'channel' from learnt message and offset it as requested by 'nn' in
+ the midiMap configuration file. */
+
+ uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
+
+ /* Merge the previously prepared channel into final message, and finally send
+ it. */
+
+ out |= m.value | (m.channel << 24);
+ m_kernelMidi.send(out);
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+void MidiMapper<KernelMidiI>::parse(MidiMap::Message& message) const
+{
+ /* Remove '0x' part from the original string. */
+
+ std::string input = message.valueStr;
+
+ std::size_t f = input.find("0x"); // check if "0x" is there
+ if (f != std::string::npos)
+ input = message.valueStr.replace(f, 2, "");
+
+ /* Then transform string value into the actual uint32_t value, by parsing
+ each char (i.e. nibble) in the original string. Substitute 'n' with
+ zeros. */
+
+ std::string output;
+ for (unsigned i = 0, p = 24; i < input.length(); i++, p -= 4)
+ {
+ if (input[i] == 'n')
+ {
+ output += '0';
+ if (message.offset == -1) // do it once
+ message.offset = p;
+ }
+ else
+ output += input[i];
+ }
+
+ /* From string to uint32_t */
+
+ message.value = strtoul(output.c_str(), nullptr, 16);
+
+ u::log::print("[MidiMapper::parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n",
+ message.channel, message.valueStr, message.value, message.offset);
+}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::readCommand(const nl::json& j, MidiMap::Message& m, const std::string& key) const
+{
+ if (j.find(key) == j.end())
+ return false;
+
+ const nl::json& jc = j[key];
+
+ m.channel = jc[MIDIMAP_KEY_CHANNEL];
+ m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
+
+ return true;
+}
+/* -------------------------------------------------------------------------- */
+
+template <typename KernelMidiI>
+bool MidiMapper<KernelMidiI>::readInitCommands(MidiMap& midiMap, const nl::json& j)
+{
+ if (j.find(MIDIMAP_KEY_INIT_COMMANDS) == j.end())
+ return false;
+
+ for (const auto& jc : j[MIDIMAP_KEY_INIT_COMMANDS])
+ {
+ MidiMap::Message m;
+ m.channel = jc[MIDIMAP_KEY_CHANNEL];
+ m.valueStr = jc[MIDIMAP_KEY_MESSAGE];
+ m.value = strtoul(m.valueStr.c_str(), nullptr, 16);
+
+ midiMap.initCommands.push_back(m);
+ }
+
+ return true;
+}
+
+template class MidiMapper<KernelMidi>;
+#ifdef WITH_TESTS
+template class MidiMapper<KernelMidiMock>;
+#endif
+} // namespace giada::m
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MIDIMAPPER_H
+#define G_MIDIMAPPER_H
+
+#include "deps/json/single_include/nlohmann/json.hpp"
+#include <string>
+#include <vector>
+
+namespace giada::m
+{
+class KernelMidi;
+#ifdef WITH_TESTS
+class KernelMidiMock;
+#endif
+} // namespace giada::m
+
+namespace giada::m
+{
+struct MidiMap
+{
+ struct Message
+ {
+ int channel = 0;
+ std::string valueStr = "";
+ int offset = -1;
+ uint32_t value = 0;
+ };
+
+ /* isValid
+ A valid MidiMap must have the brand and the device defined. */
+
+ bool isValid() const;
+
+ std::string brand;
+ std::string device;
+ std::vector<Message> initCommands;
+ Message muteOn;
+ Message muteOff;
+ Message soloOn;
+ Message soloOff;
+ Message waiting;
+ Message playing;
+ Message stopping;
+ Message stopped;
+ Message playingInaudible;
+};
+
+template <typename KernelMidiI>
+class MidiMapper final
+{
+public:
+ MidiMapper(KernelMidiI&);
+
+ /* getMapFilesFound
+ Returns a reference to the list of midimaps found. */
+
+ const std::vector<std::string>& getMapFilesFound() const;
+
+ /* init
+ Parses the midimap folders and find the available midimaps. */
+
+ void init();
+
+ /* read
+ Reads a midimap from file 'file' and sets it as the current one. */
+
+ int read(const std::string& file);
+
+ /* sendInitMessages
+ Sends initialization messages from the midimap to the connected MIDI devices. */
+
+ void sendInitMessages(const MidiMap& midiMap);
+
+ /* sendMidiLightning
+ Sends a MIDI lightning message defined by 'msg'. */
+
+ void sendMidiLightning(uint32_t learnt, const MidiMap::Message& msg);
+
+ /* currentMap
+ The current MidiMap selected and loaded. It might be invalid if no midimaps
+ have been found. */
+
+ MidiMap currentMap;
+
+private:
+ KernelMidiI& m_kernelMidi;
+
+ /* isMessageDefined
+ Checks whether a specific message has been defined within a midimap file. */
+
+ bool isMessageDefined(const MidiMap::Message& m) const;
+
+ /* parse
+ Takes a string message with 'nn' in it and turns it into a real MIDI value.
+ TODO - don't edit message in place! */
+
+ void parse(MidiMap::Message& message) const;
+
+ /* TODO - don't edit midiMap in place! */
+ bool readInitCommands(MidiMap& midiMap, const nlohmann::json& j);
+
+ /* TODO - don't edit message in place! */
+ bool readCommand(const nlohmann::json& j, MidiMap::Message& m, const std::string& key) const;
+
+ /* m_mapsPath
+ Path to folder containing midimap files, different between OSes. */
+
+ std::string m_mapsPath;
+
+ /* m_mapFiles
+ The available .giadamap files. Each element of the vector represents
+ a .giadamap file found in the midimap folder. */
+
+ std::vector<std::string> m_mapFiles;
+};
+
+extern template class MidiMapper<KernelMidi>;
+#ifdef WITH_TESTS
+extern template class MidiMapper<KernelMidiMock>;
+#endif
+} // namespace giada::m
+
+#endif
* -------------------------------------------------------------------------- */
#include "core/mixer.h"
-#include "core/audioBuffer.h"
#include "core/const.h"
#include "core/model/model.h"
-#include "core/sequencer.h"
#include "utils/log.h"
#include "utils/math.h"
-namespace giada::m::mixer
+namespace giada::m
{
namespace
{
-/* recBuffer_
-Working buffer for audio recording. */
+/* CH_LEFT, CH_RIGHT
+Channels identifiers. */
-AudioBuffer recBuffer_;
-
-/* inBuffer_
-Working buffer for input channel. Used for the in->out bridge. */
-
-AudioBuffer inBuffer_;
+constexpr int CH_LEFT = 0;
+constexpr int CH_RIGHT = 1;
+} // namespace
-/* inputTracker_
-Frame position while recording. */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
-Frame inputTracker_ = 0;
+Mixer::Mixer(model::Model& m)
+: onSignalTresholdReached(nullptr)
+, onEndOfRecording(nullptr)
+, m_model(m)
+, m_signalCbFired(false)
+, m_endOfRecCbFired(false)
+{
+}
-/* signalCb_
-Callback triggered when the input signal level reaches a threshold. */
+/* -------------------------------------------------------------------------- */
-std::function<void()> signalCb_ = nullptr;
+void Mixer::reset(Frame maxFramesInLoop, Frame framesInBuffer)
+{
+ /* Allocate working buffers. rec buffer has variable size: it depends on how
+ many frames there are in the current loop. */
-/* endOfRecCb_
-Callback triggered when the end of the internal recording buffer has been
-reached.*/
+ m_model.get().mixer.getRecBuffer().alloc(maxFramesInLoop, G_MAX_IO_CHANS);
+ m_model.get().mixer.getInBuffer().alloc(framesInBuffer, G_MAX_IO_CHANS);
-std::function<void()> endOfRecCb_ = nullptr;
+ u::log::print("[mixer::reset] buffers ready - maxFramesInLoop=%d, framesInBuffer=%d\n",
+ maxFramesInLoop, framesInBuffer);
+}
-/* signalCbFired_
-Boolean guard to determine whether the signal callback has been fired or not.
-Checking if signalCb_ != null (i.e. a callback is still present, so not fired
-yet) is not enough, as the actual firing takes place on a different thread in
-a slightly different moment (see fireSignalCb_() below). */
+/* -------------------------------------------------------------------------- */
-bool signalCbFired_ = false;
+bool Mixer::isActive() const { return m_model.get().mixer.a_isActive(); }
/* -------------------------------------------------------------------------- */
-/* fireSignalCb_
-Invokes the signal callback. This is done by pumping a FUNCTION event to the
-event dispatcher, rather than invoking the callback directly. This is done on
-purpose: the callback might (and surely will) contain blocking stuff from
-model:: that the realtime thread cannot perform directly. */
-
-void fireSignalCb_()
+void Mixer::enable()
{
- eventDispatcher::pumpUIevent({eventDispatcher::EventType::FUNCTION, 0, 0, []() {
- signalCb_();
- signalCb_ = nullptr;
- }});
+ m_model.get().mixer.a_setActive(true);
+ u::log::print("[mixer::enable] enabled\n");
}
-/* -------------------------------------------------------------------------- */
-
-/* fireEndOfRecCb_
-Same rationale of fireSignalCb_, for the endOfRecCb_ callback. */
-
-void fireEndOfRecCb_()
+void Mixer::disable()
{
- eventDispatcher::pumpUIevent({eventDispatcher::EventType::FUNCTION, 0, 0, []() {
- endOfRecCb_();
- endOfRecCb_ = nullptr;
- }});
+ m_model.get().mixer.a_setActive(false);
+ while (m_model.isLocked())
+ ;
+ u::log::print("[mixer::disable] disabled\n");
}
/* -------------------------------------------------------------------------- */
-/* lineInRec
-Records from line in. 'maxFrames' determines how many frames to record before
-the internal tracker loops over. The value changes whether you are recording
-in RIGID or FREE mode. */
-
-void lineInRec_(const AudioBuffer& inBuf, Frame maxFrames, float inVol)
+void Mixer::allocRecBuffer(Frame frames)
{
- assert(maxFrames <= recBuffer_.countFrames());
+ m_model.get().mixer.getRecBuffer().alloc(frames, G_MAX_IO_CHANS);
+}
- if (inputTracker_ >= maxFrames && endOfRecCb_ != nullptr)
- {
- fireEndOfRecCb_();
- return;
- }
+void Mixer::clearRecBuffer()
+{
+ m_model.get().mixer.getRecBuffer().clear();
+}
- const Frame framesToCopy = -1; // copy everything
- const Frame srcOffset = 0;
- const Frame destOffset = inputTracker_ % maxFrames; // loop over at maxFrames
+const mcl::AudioBuffer& Mixer::getRecBuffer()
+{
+ return m_model.get().mixer.getRecBuffer();
+}
- recBuffer_.sum(inBuf, framesToCopy, srcOffset, destOffset, inVol);
+/* -------------------------------------------------------------------------- */
- inputTracker_ += inBuf.countFrames();
+void Mixer::advanceChannels(const Sequencer::EventBuffer& events, const model::Layout& rtLayout)
+{
+ for (const Channel& c : rtLayout.channels)
+ if (!c.isInternal())
+ c.advance(events);
}
/* -------------------------------------------------------------------------- */
-/* processLineIn
-Computes line in peaks and prepares the internal working buffer for input
-recording. */
-
-void processLineIn_(const model::Mixer& mixer, const AudioBuffer& inBuf,
- float inVol, float recTriggerLevel)
+void Mixer::render(mcl::AudioBuffer& out, const mcl::AudioBuffer& in, const model::Layout& layout_RT) const
{
- float peak = inBuf.getPeak();
+ const model::Mixer& mixer = layout_RT.mixer;
+ const model::Sequencer& sequencer = layout_RT.sequencer;
+ const model::Recorder& recorder = layout_RT.recorder;
- if (signalCb_ != nullptr && u::math::linearToDB(peak) > recTriggerLevel && !signalCbFired_)
- {
- G_DEBUG("Signal > threshold!");
- fireSignalCb_();
- signalCbFired_ = true;
- }
+ const Channel& masterOutCh = layout_RT.getChannel(Mixer::MASTER_OUT_CHANNEL_ID);
+ const Channel& masterInCh = layout_RT.getChannel(Mixer::MASTER_IN_CHANNEL_ID);
+ const Channel& previewCh = layout_RT.getChannel(Mixer::PREVIEW_CHANNEL_ID);
- mixer.state->peakIn.store(peak);
+ const bool hasInput = in.isAllocd();
+ const bool inToOut = mixer.inToOut;
+ const bool isSeqActive = sequencer.isActive();
+ const bool shouldLineInRec = isSeqActive && recorder.a_isRecordingInput() && hasInput;
+ const float recTriggerLevel = mixer.recTriggerLevel;
+ const Frame maxFramesToRec = mixer.maxFramesToRec;
+ const bool allowsOverdub = mixer.allowsOverdub;
+ const bool limitOutput = mixer.limitOutput;
- /* Prepare the working buffer for input stream, which will be processed
- later on by the Master Input Channel with plug-ins. */
+ mixer.getInBuffer().clear();
- assert(inBuf.countChannels() <= inBuffer_.countChannels());
+ /* Reset peak computation. */
- inBuffer_.set(inBuf, inVol);
-}
+ mixer.a_setPeakOut({0.0f, 0.0f});
+ mixer.a_setPeakIn({0.0f, 0.0f});
-/* -------------------------------------------------------------------------- */
+ if (hasInput)
+ {
+ processLineIn(mixer, in, masterInCh.volume, recTriggerLevel, isSeqActive);
+ renderMasterIn(masterInCh, mixer.getInBuffer());
+ }
-void processChannels_(const model::Layout& layout, AudioBuffer& out, AudioBuffer& in)
-{
- for (const channel::Data& c : layout.channels)
- if (!c.isInternal())
- channel::render(c, &out, &in, isChannelAudible(c));
-}
+ if (shouldLineInRec)
+ {
+ const Frame newTrackerPos = lineInRec(in, mixer.getRecBuffer(),
+ mixer.a_getInputTracker(), maxFramesToRec, masterInCh.volume,
+ allowsOverdub);
+ mixer.a_setInputTracker(newTrackerPos);
+ }
-/* -------------------------------------------------------------------------- */
+ /* Channel processing. Don't do it if layout is locked: another thread is
+ changing data (e.g. Plugins or Waves). */
-void processSequencer_(const model::Layout& layout, AudioBuffer& out, const AudioBuffer& in)
-{
- /* Advance sequencer first, then render it (rendering is just about
- generating metronome audio). This way the metronome is aligned with
- everything else. */
+ if (!layout_RT.locked)
+ renderChannels(layout_RT.channels, out, mixer.getInBuffer());
- const sequencer::EventBuffer& events = sequencer::advance(in.countFrames());
- sequencer::render(out);
+ /* Render remaining internal channels. */
- /* No channel processing if layout is locked: another thread is changing
- data (e.g. Plugins or Waves). */
+ renderMasterOut(masterOutCh, out);
+ renderPreview(previewCh, out);
- if (layout.locked)
- return;
+ /* Post processing. */
- for (const channel::Data& c : layout.channels)
- if (!c.isInternal())
- channel::advance(c, events);
+ finalizeOutput(mixer, out, inToOut, limitOutput, masterOutCh.volume);
}
/* -------------------------------------------------------------------------- */
-void renderMasterIn_(const model::Layout& layout, AudioBuffer& in)
+void Mixer::startInputRec(Frame from)
{
- channel::render(layout.getChannel(mixer::MASTER_IN_CHANNEL_ID), nullptr, &in, true);
+ m_model.get().mixer.a_setInputTracker(from);
}
-void renderMasterOut_(const model::Layout& layout, AudioBuffer& out)
+Frame Mixer::stopInputRec()
{
- channel::render(layout.getChannel(mixer::MASTER_OUT_CHANNEL_ID), &out, nullptr, true);
-}
-
-void renderPreview_(const model::Layout& layout, AudioBuffer& out)
-{
- channel::render(layout.getChannel(mixer::PREVIEW_CHANNEL_ID), &out, nullptr, true);
+ const Frame ret = m_model.get().mixer.a_getInputTracker();
+ m_model.get().mixer.a_setInputTracker(0);
+ m_signalCbFired = false;
+ m_endOfRecCbFired = false;
+ return ret;
}
/* -------------------------------------------------------------------------- */
-/* limit_
-Applies a very dumb hard limiter. */
-
-void limit_(AudioBuffer& outBuf)
+bool Mixer::isChannelAudible(const Channel& c) const
{
- for (int i = 0; i < outBuf.countFrames(); i++)
- for (int j = 0; j < outBuf.countChannels(); j++)
- outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
+ if (c.isInternal())
+ return true;
+ if (c.isMuted())
+ return false;
+ const bool hasSolos = m_model.get().mixer.hasSolos;
+ return !hasSolos || (hasSolos && c.isSoloed());
}
/* -------------------------------------------------------------------------- */
-/* finalizeOutput
-Last touches after the output has been rendered: apply inToOut if any, apply
-output volume, compute peak. */
+Peak Mixer::getPeakOut() const { return m_model.get().mixer.a_getPeakOut(); }
+Peak Mixer::getPeakIn() const { return m_model.get().mixer.a_getPeakIn(); }
-void finalizeOutput_(const model::Mixer& mixer, AudioBuffer& outBuf,
- const RenderInfo& info)
-{
- if (info.inToOut)
- outBuf.sum(inBuffer_, info.outVol);
- else
- outBuf.applyGain(info.outVol);
-
- if (info.limitOutput)
- limit_(outBuf);
-
- mixer.state->peakOut.store(outBuf.getPeak());
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init(Frame maxFramesInLoop, Frame framesInBuffer)
+Mixer::RecordInfo Mixer::getRecordInfo() const
{
- /* Allocate working buffers. recBuffer_ has variable size: it depends on how
- many frames there are in the current loop. */
-
- recBuffer_.alloc(maxFramesInLoop, G_MAX_IO_CHANS);
- inBuffer_.alloc(framesInBuffer, G_MAX_IO_CHANS);
-
- u::log::print("[mixer::init] buffers ready - maxFramesInLoop=%d, framesInBuffer=%d\n",
- maxFramesInLoop, framesInBuffer);
+ return {
+ m_model.get().mixer.a_getInputTracker(),
+ m_model.get().mixer.getRecBuffer().countFrames()};
}
/* -------------------------------------------------------------------------- */
-void enable()
+bool Mixer::thresholdReached(Peak p, float threshold) const
{
- model::get().mixer.state->active.store(true);
- u::log::print("[mixer::enable] enabled\n");
-}
-
-void disable()
-{
- model::get().mixer.state->active.store(false);
- while (model::isLocked())
- ;
- u::log::print("[mixer::disable] disabled\n");
+ return u::math::linearToDB(p.left) > threshold ||
+ u::math::linearToDB(p.right) > threshold;
}
/* -------------------------------------------------------------------------- */
-void allocRecBuffer(Frame frames)
-{
- recBuffer_.alloc(frames, G_MAX_IO_CHANS);
-}
-
-void clearRecBuffer()
+Peak Mixer::makePeak(const mcl::AudioBuffer& b) const
{
- recBuffer_.clear();
-}
-
-const AudioBuffer& getRecBuffer()
-{
- return recBuffer_;
+ if (!b.isAllocd())
+ return {0.0f, 0.0f};
+ return {b.getPeak(CH_LEFT), b.getPeak(b.countChannels() == 1 ? CH_LEFT : CH_RIGHT)};
}
/* -------------------------------------------------------------------------- */
-int render(AudioBuffer& out, const AudioBuffer& in, const RenderInfo& info)
+Frame Mixer::lineInRec(const mcl::AudioBuffer& inBuf, mcl::AudioBuffer& recBuf, Frame inputTracker,
+ Frame maxFrames, float inVol, bool allowsOverdub) const
{
- const model::Lock rtLock = model::get_RT();
- const model::Mixer& mixer = rtLock.get().mixer;
+ assert(maxFrames > 0 && maxFrames <= recBuf.countFrames());
+ assert(onEndOfRecording != nullptr);
- inBuffer_.clear();
+ if (inputTracker >= maxFrames && !allowsOverdub && !m_endOfRecCbFired)
+ {
+ onEndOfRecording();
+ m_endOfRecCbFired = true;
+ return 0;
+ }
- /* Reset peak computation. */
+ const Frame framesToCopy = -1; // copy everything
+ const Frame srcOffset = 0;
+ const Frame destOffset = inputTracker % maxFrames; // loop over at maxFrames
- mixer.state->peakOut.store(0.0);
- mixer.state->peakIn.store(0.0);
+ recBuf.sum(inBuf, framesToCopy, srcOffset, destOffset, inVol);
- /* Process line IN if input has been enabled in KernelAudio. */
+ return inputTracker + inBuf.countFrames();
+}
- if (info.hasInput)
- {
- processLineIn_(mixer, in, info.inVol, info.recTriggerLevel);
- renderMasterIn_(rtLock.get(), inBuffer_);
- }
+/* -------------------------------------------------------------------------- */
- /* Record input audio and advance the sequencer only if clock is active:
- can't record stuff with the sequencer off. */
+void Mixer::processLineIn(const model::Mixer& mixer, const mcl::AudioBuffer& inBuf,
+ float inVol, float recTriggerLevel, bool isSeqActive) const
+{
+ const Peak peak = makePeak(inBuf);
- if (info.isClockActive)
+ if (thresholdReached(peak, recTriggerLevel) && !m_signalCbFired && isSeqActive)
{
- if (info.canLineInRec)
- lineInRec_(in, info.maxFramesToRec, info.inVol);
- if (info.isClockRunning)
- processSequencer_(rtLock.get(), out, inBuffer_);
+ m_signalCbFired = true;
+ onSignalTresholdReached();
+ G_DEBUG("Signal > threshold!");
}
- /* Channel processing. Don't do it if layout is locked: another thread is
- changing data (e.g. Plugins or Waves). */
-
- if (!rtLock.get().locked)
- processChannels_(rtLock.get(), out, inBuffer_);
+ mixer.a_setPeakIn(peak);
- /* Render remaining internal channels. */
+ /* Prepare the working buffer for input stream, which will be processed
+ later on by the Master Input Channel with plug-ins. */
- renderMasterOut_(rtLock.get(), out);
- renderPreview_(rtLock.get(), out);
+ assert(inBuf.countChannels() <= mixer.getInBuffer().countChannels());
- /* Post processing. */
+ mixer.getInBuffer().set(inBuf, inVol);
+}
- finalizeOutput_(mixer, out, info);
+/* -------------------------------------------------------------------------- */
- return 0;
+void Mixer::renderChannels(const std::vector<Channel>& channels, mcl::AudioBuffer& out, mcl::AudioBuffer& in) const
+{
+ for (const Channel& c : channels)
+ if (!c.isInternal())
+ c.render(&out, &in, isChannelAudible(c));
}
/* -------------------------------------------------------------------------- */
-void startInputRec(Frame from)
+void Mixer::renderMasterIn(const Channel& ch, mcl::AudioBuffer& in) const
{
- inputTracker_ = from;
- signalCbFired_ = false;
+ ch.render(nullptr, &in, true);
}
-Frame stopInputRec()
+void Mixer::renderMasterOut(const Channel& ch, mcl::AudioBuffer& out) const
{
- Frame ret = inputTracker_;
- inputTracker_ = 0;
- signalCbFired_ = false;
- return ret;
+ ch.render(&out, nullptr, true);
}
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f) { signalCb_ = f; }
-void setEndOfRecCallback(std::function<void()> f) { endOfRecCb_ = f; }
+void Mixer::renderPreview(const Channel& ch, mcl::AudioBuffer& out) const
+{
+ ch.render(&out, nullptr, true);
+}
/* -------------------------------------------------------------------------- */
-bool isChannelAudible(const channel::Data& c)
+void Mixer::limit(mcl::AudioBuffer& outBuf) const
{
- if (c.isInternal())
- return true;
- if (c.mute)
- return false;
- bool hasSolos = model::get().mixer.hasSolos;
- return !hasSolos || (hasSolos && c.solo);
+ for (int i = 0; i < outBuf.countFrames(); i++)
+ for (int j = 0; j < outBuf.countChannels(); j++)
+ outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
}
/* -------------------------------------------------------------------------- */
-float getPeakOut() { return m::model::get().mixer.state->peakOut.load(); }
-float getPeakIn() { return m::model::get().mixer.state->peakIn.load(); }
+void Mixer::finalizeOutput(const model::Mixer& mixer, mcl::AudioBuffer& buf,
+ bool inToOut, bool shouldLimit, float vol) const
+{
+ if (inToOut)
+ buf.sum(mixer.getInBuffer(), vol);
+ else
+ buf.applyGain(vol);
-/* -------------------------------------------------------------------------- */
+ if (shouldLimit)
+ limit(buf);
-RecordInfo getRecordInfo()
-{
- return {inputTracker_, recBuffer_.countFrames()};
+ mixer.a_setPeakOut({buf.getPeak(CH_LEFT), buf.getPeak(CH_RIGHT)});
}
-} // namespace giada::m::mixer
+} // namespace giada::m
#include "core/midiEvent.h"
#include "core/queue.h"
-#include "core/recorder.h"
#include "core/ringBuffer.h"
+#include "core/sequencer.h"
#include "core/types.h"
-#include "deps/rtaudio/RtAudio.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include "src/core/actions/actions.h"
#include <functional>
-namespace giada::m
+namespace mcl
{
-struct Action;
class AudioBuffer;
-} // namespace giada::m
-namespace giada::m::channel
-{
-struct Data;
}
-namespace giada::m::mixer
-{
-constexpr int MASTER_OUT_CHANNEL_ID = 1;
-constexpr int MASTER_IN_CHANNEL_ID = 2;
-constexpr int PREVIEW_CHANNEL_ID = 3;
-/* RenderInfo
-Struct of parameters passed to Mixer for rendering. */
+namespace giada::m::model
+{
+class Mixer;
+struct Layout;
+} // namespace giada::m::model
-struct RenderInfo
+namespace giada::m
{
- bool isAudioReady;
- bool hasInput;
- bool isClockActive;
- bool isClockRunning;
- bool canLineInRec;
- bool limitOutput;
- bool inToOut;
- Frame maxFramesToRec;
- float outVol;
- float inVol;
- float recTriggerLevel;
-};
+struct Action;
+class Channel;
+class MixerHandler;
+class Mixer
+{
+public:
+ friend MixerHandler;
-/* RecordInfo
-Information regarding the input recording progress. */
+ static constexpr int MASTER_OUT_CHANNEL_ID = 1;
+ static constexpr int MASTER_IN_CHANNEL_ID = 2;
+ static constexpr int PREVIEW_CHANNEL_ID = 3;
-struct RecordInfo
-{
- Frame position;
- Frame maxLength;
-};
+ /* RecordInfo
+ Information regarding the input recording progress. */
+
+ struct RecordInfo
+ {
+ Frame position;
+ Frame maxLength;
+ };
+
+ Mixer(model::Model&);
+
+ /* isActive
+ Mixer might be inactive (not initialized or suspended). */
+
+ bool isActive() const;
+
+ /* isChannelAudible
+ True if the channel 'c' is currently audible: not muted or not included in a
+ solo session. */
+
+ bool isChannelAudible(const Channel& c) const;
+
+ Peak getPeakOut() const;
+ Peak getPeakIn() const;
-void init(Frame framesInLoop, Frame framesInBuffer);
+ /* getRecordInfo
+ Returns information on the ongoing input recording. */
-/* enable, disable
-Toggles master callback processing. Useful to suspend the rendering. */
+ RecordInfo getRecordInfo() const;
-void enable();
-void disable();
+ /* render
+ Core rendering function. */
-/* allocRecBuffer
-Allocates new memory for the virtual input channel. */
+ void render(mcl::AudioBuffer& out, const mcl::AudioBuffer& in, const model::Layout&) const;
-void allocRecBuffer(Frame frames);
+ /* reset
+ Brings everything back to the initial state. */
-/* clearRecBuffer
-Clears internal virtual channel. */
+ void reset(Frame framesInLoop, Frame framesInBuffer);
-void clearRecBuffer();
+ /* enable, disable
+ Toggles master callback processing. Useful to suspend the rendering. */
-/* getRecBuffer
-Returns a read-only reference to the internal virtual channel. Use this to
-merge data into channel after an input recording session. */
+ void enable();
+ void disable();
-const AudioBuffer& getRecBuffer();
+ /* allocRecBuffer
+ Allocates new memory for the virtual input channel. */
-/* render
-Core rendering function. */
+ void allocRecBuffer(Frame frames);
-int render(AudioBuffer& out, const AudioBuffer& in, const RenderInfo& info);
+ /* clearRecBuffer
+ Clears internal virtual channel. */
-/* startInputRec, stopInputRec
-Starts/stops input recording on frame 'from'. The latter returns the number of
-recorded frames. */
+ void clearRecBuffer();
-void startInputRec(Frame from);
-Frame stopInputRec();
+ /* getRecBuffer
+ Returns a read-only reference to the internal virtual channel. Use this to
+ merge data into channel after an input recording session. */
-/* setSignalCallback
-Registers the function to be called when the audio signal reaches a certain
-threshold (record-on-signal mode). */
+ const mcl::AudioBuffer& getRecBuffer();
-void setSignalCallback(std::function<void()> f);
+ /* advanceChannels
+ Processes Channels' static events (e.g. pre-recorded actions or sequencer
+ events) in the current audio block. Called by the main audio thread when the
+ sequencer is running. */
-/* setEndOfRecCallback
-Registers the function to be called when the end of the internal recording
-buffer has been reached. */
+ void advanceChannels(const Sequencer::EventBuffer& events, const model::Layout&);
-void setEndOfRecCallback(std::function<void()> f);
+ /* onSignalTresholdReached
+ Callback fired when audio has reached a certain threshold (record-on-signal
+ mode). */
-/* isChannelAudible
-True if the channel 'c' is currently audible: not muted or not included in a
-solo session. */
+ std::function<void()> onSignalTresholdReached;
-bool isChannelAudible(const channel::Data& c);
+ /* onEndOfRecording
+ Callback fired when the audio recording session has ended. */
-float getPeakOut();
-float getPeakIn();
+ std::function<void()> onEndOfRecording;
-RecordInfo getRecordInfo();
-} // namespace giada::m::mixer
+private:
+ /* thresholdReached
+ Returns true if left or right channel's peak has reached a certain
+ threshold. */
+
+ bool thresholdReached(Peak p, float threshold) const;
+
+ /* makePeak
+ Returns a Peak object given an audio buffer, taking number of channels into
+ account. */
+
+ Peak makePeak(const mcl::AudioBuffer& b) const;
+
+ /* lineInRec
+ Records from line in. 'maxFrames' determines how many frames to record
+ before the internal tracker loops over. The value changes whether you are
+ recording in RIGID or FREE mode. Returns the number of recorded frames. */
+
+ Frame lineInRec(const mcl::AudioBuffer& inBuf, mcl::AudioBuffer& recBuf,
+ Frame inputTracker, Frame maxFrames, float inVol, bool allowsOverdub) const;
+
+ /* processLineIn
+ Computes line in peaks and prepares the internal working buffer for input
+ recording. */
+
+ void processLineIn(const model::Mixer& mixer, const mcl::AudioBuffer& inBuf,
+ float inVol, float recTriggerLevel, bool isSeqActive) const;
+
+ void renderChannels(const std::vector<Channel>& channels, mcl::AudioBuffer& out, mcl::AudioBuffer& in) const;
+ void renderMasterIn(const Channel&, mcl::AudioBuffer& in) const;
+ void renderMasterOut(const Channel&, mcl::AudioBuffer& out) const;
+ void renderPreview(const Channel&, mcl::AudioBuffer& out) const;
+
+ /* limit
+ Applies a very dumb hard limiter. */
+
+ void limit(mcl::AudioBuffer& outBuf) const;
+
+ /* finalizeOutput
+ Last touches after the output has been rendered: apply inToOut if any, apply
+ output volume, compute peak. */
+
+ void finalizeOutput(const model::Mixer&, mcl::AudioBuffer&, bool inToOut,
+ bool limit, float vol) const;
+
+ /* startInputRec, stopInputRec
+ Starts/stops input recording on frame 'from'. The latter returns the number
+ of recorded frames. */
+
+ void startInputRec(Frame from);
+ Frame stopInputRec();
+
+ model::Model& m_model;
+
+ /* m_signalCbFired, m_endOfRecCbFired
+ Boolean guards to determine whether the callbacks have been fired or not,
+ to avoid retriggering. Mutable: strictly for internal use only. */
+
+ mutable bool m_signalCbFired;
+ mutable bool m_endOfRecCbFired;
+};
+} // namespace giada::m
#endif
#include "core/mixerHandler.h"
#include "core/channels/channelManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
#include "core/const.h"
-#include "core/init.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
#include "core/mixer.h"
#include "core/model/model.h"
-#include "core/patch.h"
-#include "core/plugins/plugin.h"
-#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/wave.h"
-#include "core/waveFx.h"
-#include "core/waveManager.h"
#include "glue/channel.h"
#include "glue/main.h"
#include "utils/fs.h"
#include <cassert>
#include <vector>
-namespace giada::m::mh
+namespace giada::m
{
-namespace
+MixerHandler::MixerHandler(model::Model& model, Mixer& mixer)
+: onChannelsAltered(nullptr)
+, onChannelRecorded(nullptr)
+, m_model(model)
+, m_mixer(mixer)
{
-channel::Data& addChannel_(ChannelType type, ID columnId)
-{
- model::get().channels.push_back(channelManager::create(/*id=*/0, type, columnId));
- model::swap(model::SwapType::HARD);
-
- return model::get().channels.back();
-}
-
-/* -------------------------------------------------------------------------- */
-
-waveManager::Result createWave_(const std::string& fname)
-{
- return waveManager::createFromFile(fname, /*id=*/0, conf::conf.samplerate,
- conf::conf.rsmpQuality);
}
/* -------------------------------------------------------------------------- */
-bool anyChannel_(std::function<bool(const channel::Data&)> f)
+void MixerHandler::reset(Frame framesInLoop, Frame framesInBuffer, ChannelManager& channelManager)
{
- return std::any_of(model::get().channels.begin(), model::get().channels.end(), f);
-}
-
-/* -------------------------------------------------------------------------- */
+ m_mixer.reset(framesInLoop, framesInBuffer);
-template <typename F>
-std::vector<channel::Data*> getChannelsIf_(F f)
-{
- std::vector<channel::Data*> out;
- for (channel::Data& ch : model::get().channels)
- if (f(ch))
- out.push_back(&ch);
- return out;
-}
+ m_model.get().channels.clear();
-std::vector<channel::Data*> getRecordableChannels_()
-{
- return getChannelsIf_([](const channel::Data& c) { return c.canInputRec() && !c.hasWave(); });
-}
+ m_model.get().channels.push_back(channelManager.create(
+ Mixer::MASTER_OUT_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0, framesInBuffer));
+ m_model.get().channels.push_back(channelManager.create(
+ Mixer::MASTER_IN_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0, framesInBuffer));
+ m_model.get().channels.push_back(channelManager.create(
+ Mixer::PREVIEW_CHANNEL_ID, ChannelType::PREVIEW, /*columnId=*/0, framesInBuffer));
-std::vector<channel::Data*> getOverdubbableChannels_()
-{
- return getChannelsIf_([](const channel::Data& c) { return c.canInputRec() && c.hasWave(); });
+ m_model.swap(model::SwapType::NONE);
}
/* -------------------------------------------------------------------------- */
-void setupChannelPostRecording_(channel::Data& ch)
+Channel& MixerHandler::addChannel(ChannelType type, ID columnId, int bufferSize,
+ ChannelManager& channelManager)
{
- /* Start sample channels in loop mode right away. */
- if (ch.samplePlayer->isAnyLoopMode())
- samplePlayer::kickIn(ch, clock::getCurrentFrame());
- /* Disable 'arm' button if overdub protection is on. */
- if (ch.audioReceiver->overdubProtection == true)
- ch.armed = false;
+ m_model.get().channels.push_back(channelManager.create(/*id=*/0, type, columnId, bufferSize));
+ m_model.swap(model::SwapType::HARD);
+
+ return m_model.get().channels.back();
}
/* -------------------------------------------------------------------------- */
-/* recordChannel_
-Records the current Mixer audio input data into an empty channel. */
-
-void recordChannel_(channel::Data& ch, Frame recordedFrames)
+void MixerHandler::loadChannel(ID channelId, std::unique_ptr<Wave> w)
{
- /* Create a new Wave with audio coming from Mixer's input buffer. */
+ assert(onChannelsAltered != nullptr);
- std::string filename = "TAKE-" + std::to_string(patch::patch.lastTakeId++) + ".wav";
- std::unique_ptr<Wave> wave = waveManager::createEmpty(recordedFrames, G_MAX_IO_CHANS,
- conf::conf.samplerate, filename);
+ m_model.addShared(std::move(w));
- G_DEBUG("Created new Wave, size=" << wave->getBuffer().countFrames());
-
- /* Copy up to wave.getSize() from the mixer's input buffer into wave's. */
+ Channel& channel = m_model.get().getChannel(channelId);
+ Wave& wave = m_model.backShared<Wave>();
+ Wave* old = channel.samplePlayer->getWave();
- wave->getBuffer().set(mixer::getRecBuffer(), wave->getBuffer().countFrames());
+ channel.samplePlayer->loadWave(channel, &wave);
+ m_model.swap(model::SwapType::HARD);
- /* Update channel with the new Wave. */
+ /* Remove old wave, if any. It is safe to do it now: the audio thread is
+ already processing the new layout. */
- model::add(std::move(wave));
- samplePlayer::loadWave(ch, &model::back<Wave>());
- setupChannelPostRecording_(ch);
+ if (old != nullptr)
+ m_model.removeShared<Wave>(*old);
- model::swap(model::SwapType::HARD);
+ onChannelsAltered();
}
/* -------------------------------------------------------------------------- */
-/* overdubChannel_
-Records the current Mixer audio input data into a channel with an existing
-Wave, overdub mode. */
-
-void overdubChannel_(channel::Data& ch)
+void MixerHandler::addAndLoadChannel(ID columnId, std::unique_ptr<Wave> w, int bufferSize,
+ ChannelManager& channelManager)
{
- Wave* wave = ch.samplePlayer->getWave();
+ assert(onChannelsAltered != nullptr);
- /* Need model::DataLock here, as data might be being read by the audio
- thread at the same time. */
+ m_model.addShared(std::move(w));
- model::DataLock lock;
- wave->getBuffer().sum(mixer::getRecBuffer(), /*gain=*/1.0f);
- wave->setLogical(true);
+ Wave& wave = m_model.backShared<Wave>();
+ Channel& channel = addChannel(ChannelType::SAMPLE, columnId, bufferSize, channelManager);
- setupChannelPostRecording_(ch);
+ channel.samplePlayer->loadWave(channel, &wave);
+ m_model.swap(model::SwapType::HARD);
+
+ onChannelsAltered();
}
-} // namespace
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init()
+#ifdef WITH_VST
+void MixerHandler::cloneChannel(ID channelId, int sampleRate, int bufferSize,
+ ChannelManager& channelManager, WaveManager& waveManager, const Sequencer& sequencer,
+ PluginManager& pluginManager)
+#else
+void MixerHandler::cloneChannel(ID channelId, int bufferSize, ChannelManager& channelManager,
+ WaveManager& waveManager)
+#endif
{
- mixer::init(clock::getMaxFramesInLoop(), kernelAudio::getRealBufSize());
-
- model::get().channels.clear();
-
- model::get().channels.push_back(channelManager::create(
- mixer::MASTER_OUT_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0));
- model::get().channels.push_back(channelManager::create(
- mixer::MASTER_IN_CHANNEL_ID, ChannelType::MASTER, /*columnId=*/0));
- model::get().channels.push_back(channelManager::create(
- mixer::PREVIEW_CHANNEL_ID, ChannelType::PREVIEW, /*columnId=*/0));
+ const Channel& oldChannel = m_model.get().getChannel(channelId);
+ Channel newChannel = channelManager.create(oldChannel, bufferSize);
- model::swap(model::SwapType::NONE);
-}
+ /* Clone waves and plugins first in their own lists. */
-/* -------------------------------------------------------------------------- */
+ if (oldChannel.samplePlayer && oldChannel.samplePlayer->hasWave())
+ {
+ const Wave& oldWave = *oldChannel.samplePlayer->getWave();
+ m_model.addShared(waveManager.createFromWave(oldWave, 0, oldWave.getBuffer().countFrames()));
+ newChannel.samplePlayer->loadWave(newChannel, &m_model.backShared<Wave>());
+ }
-void close()
-{
- mixer::disable();
-}
+#ifdef WITH_VST
+ for (const Plugin* plugin : oldChannel.plugins)
+ {
+ m_model.addShared(pluginManager.makePlugin(*plugin, sampleRate, bufferSize, sequencer));
+ newChannel.plugins.push_back(&m_model.backShared<Plugin>());
+ }
+#endif
-/* -------------------------------------------------------------------------- */
+ /* Then push the new channel in the channels vector. */
-void addChannel(ChannelType type, ID columnId)
-{
- addChannel_(type, columnId);
+ m_model.get().channels.push_back(newChannel);
+ m_model.swap(model::SwapType::HARD);
}
/* -------------------------------------------------------------------------- */
-int loadChannel(ID channelId, const std::string& fname)
+void MixerHandler::freeChannel(ID channelId)
{
- waveManager::Result res = createWave_(fname);
-
- if (res.status != G_RES_OK)
- return res.status;
+ assert(onChannelsAltered != nullptr);
- model::add(std::move(res.wave));
+ Channel& ch = m_model.get().getChannel(channelId);
- Wave& wave = model::back<Wave>();
- Wave* old = model::get().getChannel(channelId).samplePlayer->getWave();
-
- samplePlayer::loadWave(model::get().getChannel(channelId), &wave);
- model::swap(model::SwapType::HARD);
+ assert(ch.samplePlayer);
- /* Remove old wave, if any. It is safe to do it now: the audio thread is
- already processing the new layout. */
+ const Wave* wave = ch.samplePlayer->getWave();
- if (old != nullptr)
- model::remove<Wave>(*old);
+ ch.samplePlayer->loadWave(ch, nullptr);
+ m_model.swap(model::SwapType::HARD);
- recManager::refreshInputRecMode();
+ if (wave != nullptr)
+ m_model.removeShared<Wave>(*wave);
- return res.status;
+ onChannelsAltered();
}
/* -------------------------------------------------------------------------- */
-int addAndLoadChannel(ID columnId, const std::string& fname)
-{
- waveManager::Result res = createWave_(fname);
- if (res.status == G_RES_OK)
- addAndLoadChannel(columnId, std::move(res.wave));
- return res.status;
-}
-
-void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w)
+void MixerHandler::freeAllChannels()
{
- model::add(std::move(w));
+ assert(onChannelsAltered != nullptr);
- Wave& wave = model::back<Wave>();
- channel::Data& channel = addChannel_(ChannelType::SAMPLE, columnId);
+ for (Channel& ch : m_model.get().channels)
+ if (ch.samplePlayer)
+ ch.samplePlayer->loadWave(ch, nullptr);
- samplePlayer::loadWave(channel, &wave);
- model::swap(model::SwapType::HARD);
+ m_model.swap(model::SwapType::HARD);
+ m_model.clearShared<model::WavePtrs>();
- recManager::refreshInputRecMode();
+ onChannelsAltered();
}
/* -------------------------------------------------------------------------- */
-void cloneChannel(ID channelId)
+void MixerHandler::deleteChannel(ID channelId)
{
- channel::Data& oldChannel = model::get().getChannel(channelId);
- channel::Data newChannel = channelManager::create(oldChannel);
-
- /* Clone plugins, actions and wave first in their own lists. */
+ assert(onChannelsAltered != nullptr);
+ const Channel& ch = m_model.get().getChannel(channelId);
+ const Wave* wave = ch.samplePlayer ? ch.samplePlayer->getWave() : nullptr;
#ifdef WITH_VST
- newChannel.plugins = pluginHost::clonePlugins(oldChannel.plugins);
+ const std::vector<Plugin*> plugins = ch.plugins;
#endif
- recorderHandler::cloneActions(channelId, newChannel.id);
- if (newChannel.samplePlayer && newChannel.samplePlayer->hasWave())
- {
- Wave* wave = newChannel.samplePlayer->getWave();
- model::add(waveManager::createFromWave(*wave, 0, wave->getBuffer().countFrames()));
- }
+ u::vector::removeIf(m_model.get().channels, [channelId](const Channel& c) {
+ return c.id == channelId;
+ });
+ m_model.swap(model::SwapType::HARD);
- /* Then push the new channel in the channels vector. */
+ if (wave != nullptr)
+ m_model.removeShared<Wave>(*wave);
- model::get().channels.push_back(newChannel);
- model::swap(model::SwapType::HARD);
+ onChannelsAltered();
}
/* -------------------------------------------------------------------------- */
-void freeChannel(ID channelId)
+void MixerHandler::renameChannel(ID channelId, const std::string& name)
{
- channel::Data& ch = model::get().getChannel(channelId);
-
- assert(ch.samplePlayer);
-
- const Wave* wave = ch.samplePlayer->getWave();
+ m_model.get().getChannel(channelId).name = name;
+ m_model.swap(model::SwapType::HARD);
+}
- samplePlayer::loadWave(ch, nullptr);
- model::swap(model::SwapType::HARD);
+/* -------------------------------------------------------------------------- */
- if (wave != nullptr)
- model::remove<Wave>(*wave);
+void MixerHandler::updateSoloCount()
+{
+ bool hasSolos = forAnyChannel([](const Channel& ch) {
+ return ch.isSoloed();
+ });
- recManager::refreshInputRecMode();
+ m_model.get().mixer.hasSolos = hasSolos;
+ m_model.swap(model::SwapType::NONE);
}
/* -------------------------------------------------------------------------- */
-void freeAllChannels()
+void MixerHandler::setInToOut(bool v)
{
- for (channel::Data& ch : model::get().channels)
- if (ch.samplePlayer)
- samplePlayer::loadWave(ch, nullptr);
-
- model::swap(model::SwapType::HARD);
- model::clear<model::WavePtrs>();
-
- recManager::refreshInputRecMode();
+ m_model.get().mixer.inToOut = v;
+ m_model.swap(model::SwapType::NONE);
}
/* -------------------------------------------------------------------------- */
-void deleteChannel(ID channelId)
+float MixerHandler::getInVol() const
{
- const channel::Data& ch = model::get().getChannel(channelId);
- const Wave* wave = ch.samplePlayer ? ch.samplePlayer->getWave() : nullptr;
-#ifdef WITH_VST
- const std::vector<Plugin*> plugins = ch.plugins;
-#endif
+ return m_model.get().getChannel(Mixer::MASTER_IN_CHANNEL_ID).volume;
+}
- u::vector::removeIf(model::get().channels, [channelId](const channel::Data& c) {
- return c.id == channelId;
- });
- model::swap(model::SwapType::HARD);
+float MixerHandler::getOutVol() const
+{
+ return m_model.get().getChannel(Mixer::MASTER_OUT_CHANNEL_ID).volume;
+}
- if (wave != nullptr)
- model::remove<Wave>(*wave);
+bool MixerHandler::getInToOut() const
+{
+ return m_model.get().mixer.inToOut;
+}
-#ifdef WITH_VST
- pluginHost::freePlugins(plugins);
-#endif
+/* -------------------------------------------------------------------------- */
- recManager::refreshInputRecMode();
+void MixerHandler::startInputRec(Frame currentFrame)
+{
+ m_mixer.startInputRec(currentFrame);
}
/* -------------------------------------------------------------------------- */
-void renameChannel(ID channelId, const std::string& name)
+Frame MixerHandler::stopInputRec()
{
- model::get().getChannel(channelId).name = name;
- model::swap(model::SwapType::HARD);
+ return m_mixer.stopInputRec();
}
/* -------------------------------------------------------------------------- */
-void updateSoloCount()
+void MixerHandler::finalizeInputRec(Frame recordedFrames, Frame currentFrame)
{
- bool hasSolos = anyChannel_([](const channel::Data& ch) {
- return !ch.isInternal() && ch.solo;
- });
+ for (Channel* ch : getRecordableChannels())
+ recordChannel(*ch, recordedFrames, currentFrame);
+ for (Channel* ch : getOverdubbableChannels())
+ overdubChannel(*ch, currentFrame);
+
+ m_mixer.clearRecBuffer();
- model::get().mixer.hasSolos = hasSolos;
- model::swap(model::SwapType::NONE);
+ onChannelsAltered();
}
/* -------------------------------------------------------------------------- */
-void setInToOut(bool v)
+bool MixerHandler::hasInputRecordableChannels() const
{
- model::get().mixer.inToOut = v;
- model::swap(model::SwapType::NONE);
+ return forAnyChannel([](const Channel& ch) { return ch.canInputRec(); });
}
-/* -------------------------------------------------------------------------- */
+bool MixerHandler::hasActionRecordableChannels() const
+{
+ return forAnyChannel([](const Channel& ch) { return ch.canActionRec(); });
+}
-float getInVol()
+bool MixerHandler::hasLogicalSamples() const
{
- return model::get().getChannel(mixer::MASTER_IN_CHANNEL_ID).volume;
+ return forAnyChannel([](const Channel& ch) { return ch.samplePlayer && ch.samplePlayer->hasLogicalWave(); });
}
-float getOutVol()
+bool MixerHandler::hasEditedSamples() const
{
- return model::get().getChannel(mixer::MASTER_OUT_CHANNEL_ID).volume;
+ return forAnyChannel([](const Channel& ch) {
+ return ch.samplePlayer && ch.samplePlayer->hasEditedWave();
+ });
}
-bool getInToOut()
+bool MixerHandler::hasActions() const
{
- return model::get().mixer.inToOut;
+ return forAnyChannel([](const Channel& ch) { return ch.hasActions; });
+}
+
+bool MixerHandler::hasAudioData() const
+{
+ return forAnyChannel([](const Channel& ch) {
+ return ch.samplePlayer && ch.samplePlayer->hasWave();
+ });
}
/* -------------------------------------------------------------------------- */
-void finalizeInputRec(Frame recordedFrames)
+bool MixerHandler::forAnyChannel(std::function<bool(const Channel&)> f) const
{
- for (channel::Data* ch : getRecordableChannels_())
- recordChannel_(*ch, recordedFrames);
- for (channel::Data* ch : getOverdubbableChannels_())
- overdubChannel_(*ch);
-
- mixer::clearRecBuffer();
+ return std::any_of(m_model.get().channels.begin(), m_model.get().channels.end(), f);
}
/* -------------------------------------------------------------------------- */
-bool hasInputRecordableChannels()
+std::vector<Channel*> MixerHandler::getChannelsIf(std::function<bool(const Channel&)> f)
{
- return anyChannel_([](const channel::Data& ch) { return ch.canInputRec(); });
+ std::vector<Channel*> out;
+ for (Channel& ch : m_model.get().channels)
+ if (f(ch))
+ out.push_back(&ch);
+ return out;
}
-bool hasActionRecordableChannels()
+std::vector<Channel*> MixerHandler::getRecordableChannels()
{
- return anyChannel_([](const channel::Data& ch) { return ch.canActionRec(); });
+ return getChannelsIf([](const Channel& c) { return c.canInputRec() && !c.hasWave(); });
}
-bool hasLogicalSamples()
+std::vector<Channel*> MixerHandler::getOverdubbableChannels()
{
- return anyChannel_([](const channel::Data& ch) { return ch.samplePlayer && ch.samplePlayer->hasLogicalWave(); });
+ return getChannelsIf([](const Channel& c) { return c.canInputRec() && c.hasWave(); });
}
-bool hasEditedSamples()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::setupChannelPostRecording(Channel& ch, Frame currentFrame)
{
- return anyChannel_([](const channel::Data& ch) {
- return ch.samplePlayer && ch.samplePlayer->hasEditedWave();
- });
+ /* Start sample channels in loop mode right away. */
+ if (ch.samplePlayer->isAnyLoopMode())
+ ch.samplePlayer->kickIn(ch, currentFrame);
+ /* Disable 'arm' button if overdub protection is on. */
+ if (ch.audioReceiver->overdubProtection == true)
+ ch.armed = false;
}
-bool hasActions()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::recordChannel(Channel& ch, Frame recordedFrames, Frame currentFrame)
{
- return anyChannel_([](const channel::Data& ch) { return ch.hasActions; });
+ assert(onChannelRecorded != nullptr);
+
+ std::unique_ptr<Wave> wave = onChannelRecorded(recordedFrames);
+
+ G_DEBUG("Created new Wave, size=" << wave->getBuffer().countFrames());
+
+ /* Copy up to wave.getSize() from the mixer's input buffer into wave's. */
+
+ wave->getBuffer().set(m_mixer.getRecBuffer(), wave->getBuffer().countFrames());
+
+ /* Update channel with the new Wave. */
+
+ m_model.addShared(std::move(wave));
+ ch.samplePlayer->loadWave(ch, &m_model.backShared<Wave>());
+ setupChannelPostRecording(ch, currentFrame);
+
+ m_model.swap(model::SwapType::HARD);
}
-bool hasAudioData()
+/* -------------------------------------------------------------------------- */
+
+void MixerHandler::overdubChannel(Channel& ch, Frame currentFrame)
{
- return anyChannel_([](const channel::Data& ch) {
- return ch.samplePlayer && ch.samplePlayer->hasWave();
- });
+ Wave* wave = ch.samplePlayer->getWave();
+
+ /* Need model::DataLock here, as data might be being read by the audio
+ thread at the same time. */
+
+ model::DataLock lock = m_model.lockData();
+
+ wave->getBuffer().sum(m_mixer.getRecBuffer(), /*gain=*/1.0f);
+ wave->setLogical(true);
+
+ setupChannelPostRecording(ch, currentFrame);
}
-} // namespace giada::m::mh
+} // namespace giada::m
#ifndef G_MIXER_HANDLER_H
#define G_MIXER_HANDLER_H
+#include "core/plugins/pluginManager.h"
+#include "core/waveManager.h"
#include "types.h"
+#include <functional>
#include <memory>
#include <string>
+namespace giada::m::model
+{
+class Model;
+}
+
namespace giada::m
{
-class Wave;
class Channel;
-class SampleChannel;
-} // namespace giada::m
-namespace giada::m::mh
+class Wave;
+class Mixer;
+class Plugin;
+class ChannelManager;
+class Sequencer;
+class MixerHandler final
{
-/* init
-Initializes mixer. */
+public:
+ MixerHandler(model::Model&, Mixer&);
+
+ /* hasLogicalSamples
+ True if 1 or more samples are logical (memory only, such as takes). */
+
+ bool hasLogicalSamples() const;
+
+ /* hasEditedSamples
+ True if 1 or more samples have been edited via Sample Editor. */
+
+ bool hasEditedSamples() const;
+
+ /* has(Input|Action)RecordableChannels
+ Tells whether Mixer has one or more input or action recordable channels. */
-void init();
+ bool hasInputRecordableChannels() const;
+ bool hasActionRecordableChannels() const;
-/* close
-Closes mixer and frees resources. */
+ /* hasActions
+ True if at least one Channel has actions recorded in it. */
-void close();
+ bool hasActions() const;
-/* addChannel
-Adds a new channel of type 'type' into the channels stack. Returns the new
-channel ID. */
+ /* hasAudioData
+ True if at least one Sample Channel has some audio recorded in it. */
-void addChannel(ChannelType type, ID columnId);
+ bool hasAudioData() const;
-/* loadChannel
-Loads a new Wave inside a Sample Channel. */
+ float getInVol() const;
+ float getOutVol() const;
+ bool getInToOut() const;
-int loadChannel(ID channelId, const std::string& fname);
+ /* reset
+ Brings everything back to the initial state. */
-/* addAndLoadChannel (1)
-Creates a new channels, fills it with a Wave and then add it to the stack. */
+ void reset(Frame framesInLoop, Frame framesInBuffer, ChannelManager&);
-int addAndLoadChannel(ID columnId, const std::string& fname);
+ /* addChannel
+ Adds a new channel of type 'type' into the channels stack. Returns the new
+ channel ID. */
-/* addAndLoadChannel (2)
-Same as (1), but Wave is already provided. */
+ Channel& addChannel(ChannelType type, ID columnId, int bufferSize, ChannelManager&);
-void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w);
+ /* loadChannel
+ Loads a new Wave inside a Sample Channel. */
-/* freeChannel
-Unloads existing Wave from a Sample Channel. */
+ void loadChannel(ID channelId, std::unique_ptr<Wave> w);
-void freeChannel(ID channelId);
+ /* addAndLoadChannel
+ Creates a new channels, fills it with a Wave and then add it to the stack. */
-/* deleteChannel
-Completely removes a channel from the stack. */
+ void addAndLoadChannel(ID columnId, std::unique_ptr<Wave> w, int bufferSize,
+ ChannelManager&);
-void deleteChannel(ID channelId);
+ /* freeChannel
+ Unloads existing Wave from a Sample Channel. */
-void cloneChannel(ID channelId);
-void renameChannel(ID channelId, const std::string& name);
-void freeAllChannels();
+ void freeChannel(ID channelId);
-void setInToOut(bool v);
+ /* deleteChannel
+ Completely removes a channel from the stack. */
-/* updateSoloCount
-Updates the number of solo-ed channels in mixer. */
+ void deleteChannel(ID channelId);
-void updateSoloCount();
+#ifdef WITH_VST
+ void cloneChannel(ID channelId, int sampleRate, int bufferSize, ChannelManager&,
+ WaveManager&, const Sequencer&, PluginManager&);
+#else
+ void cloneChannel(ID channelId, int bufferSize, ChannelManager&, WaveManager&);
+#endif
+ void renameChannel(ID channelId, const std::string& name);
+ void freeAllChannels();
+
+ void setInToOut(bool v);
+
+ /* updateSoloCount
+ Updates the number of solo-ed channels in mixer. */
+
+ void updateSoloCount();
+
+ /* startInputRec
+ Initializes Mixer for input recording. */
-/* finalizeInputRec
-Fills armed Sample Channels with audio data coming from an input recording
-session. */
+ void startInputRec(Frame currentFrame);
-void finalizeInputRec(Frame recordedFrames);
+ /* stopInputRec
+ Terminates input recording in Mixer. Returns the number of recorded frames.
+ Call finalizeInputRec() if you really want to finish the input recording
+ operation. */
-/* hasLogicalSamples
-True if 1 or more samples are logical (memory only, such as takes) */
+ Frame stopInputRec();
-bool hasLogicalSamples();
+ /* finalizeInputRec
+ Fills armed Sample Channels with audio data coming from an input recording
+ session. */
-/* hasEditedSamples
-True if 1 or more samples was edited via gEditor */
+ void finalizeInputRec(Frame recordedFrames, Frame currentFrame);
-bool hasEditedSamples();
+ /* onChannelsAltered
+ Fired when something is done on channels (added, removed, loaded, ...). */
-/* has(Input|Action)RecordableChannels
-Tells whether Mixer has one or more input or action recordable channels. */
+ std::function<void()> onChannelsAltered;
-bool hasInputRecordableChannels();
-bool hasActionRecordableChannels();
+ /* onChannelRecorded
+ Fired during the input recording finalization, when a new empty Wave must
+ be added to each armed channel in order to store recorded audio coming from
+ Mixer. */
-/* hasActions
-True if at least one Channel has actions recorded in it. */
+ std::function<std::unique_ptr<Wave>(Frame)> onChannelRecorded;
-bool hasActions();
+private:
+ bool forAnyChannel(std::function<bool(const Channel&)> f) const;
-/* hasAudioData
-True if at least one Sample Channel has some audio recorded in it. */
+ std::vector<Channel*> getChannelsIf(std::function<bool(const Channel&)> f);
+ std::vector<Channel*> getRecordableChannels();
+ std::vector<Channel*> getOverdubbableChannels();
-bool hasAudioData();
+ void setupChannelPostRecording(Channel& ch, Frame currentFrame);
-float getInVol();
-float getOutVol();
-bool getInToOut();
-} // namespace giada::m::mh
+ /* recordChannel
+ Records the current Mixer audio input data into an empty channel. */
+
+ void recordChannel(Channel& ch, Frame recordedFrames, Frame currentFrame);
+
+ /* overdubChannel
+ Records the current Mixer audio input data into a channel with an existing
+ Wave, overdub mode. */
+
+ void overdubChannel(Channel& ch, Frame currentFrame);
+
+ model::Model& m_model;
+ Mixer& m_mixer;
+};
+} // namespace giada::m
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/mixer.h"
+
+namespace giada::m::model
+{
+Mixer::Shared& Mixer::Shared::operator=(const Mixer::Shared& o)
+{
+ if (this == &o)
+ return *this;
+ active.store(o.active.load());
+ peakOutL.store(0.0f);
+ peakOutR.store(0.0f);
+ peakInL.store(0.0f);
+ peakInR.store(0.0f);
+ inputTracker.store(0);
+ return *this;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Mixer::a_isActive() const
+{
+ return shared->active.load() == true;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Frame Mixer::a_getInputTracker() const
+{
+ return shared->inputTracker.load();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setActive(bool isActive) const
+{
+ shared->active.store(isActive);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setInputTracker(Frame f) const
+{
+ shared->inputTracker.store(f);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Peak Mixer::a_getPeakOut() const
+{
+ return {shared->peakOutL.load(), shared->peakOutR.load()};
+}
+
+Peak Mixer::a_getPeakIn() const
+{
+ return {shared->peakInL.load(), shared->peakInR.load()};
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Mixer::a_setPeakOut(Peak p) const
+{
+ shared->peakOutL.store(p.left);
+ shared->peakOutR.store(p.right);
+}
+
+void Mixer::a_setPeakIn(Peak p) const
+{
+ shared->peakInL.store(p.left);
+ shared->peakInR.store(p.right);
+}
+
+/* -------------------------------------------------------------------------- */
+
+mcl::AudioBuffer& Mixer::getRecBuffer() const { return shared->recBuffer; }
+mcl::AudioBuffer& Mixer::getInBuffer() const { return shared->inBuffer; }
+} // namespace giada::m::model
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_MIXER_H
+#define G_MODEL_MIXER_H
+
+#include "core/const.h"
+#include "core/types.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include <atomic>
+
+namespace giada::m::model
+{
+class Mixer
+{
+ friend class Model;
+
+public:
+ bool a_isActive() const;
+ Frame a_getInputTracker() const;
+ Peak a_getPeakOut() const;
+ Peak a_getPeakIn() const;
+
+ void a_setActive(bool) const;
+ void a_setInputTracker(Frame) const;
+ void a_setPeakOut(Peak) const;
+ void a_setPeakIn(Peak) const;
+
+ mcl::AudioBuffer& getRecBuffer() const;
+ mcl::AudioBuffer& getInBuffer() const;
+
+ bool hasSolos = false;
+ bool inToOut = false;
+ bool limitOutput = false;
+ bool allowsOverdub = false;
+ Frame maxFramesToRec = 0;
+ float recTriggerLevel = 0.0f;
+
+private:
+ struct Shared
+ {
+ Shared& operator=(const Shared&);
+
+ std::atomic<bool> active = false;
+ WeakAtomic<float> peakOutL = 0.0f;
+ WeakAtomic<float> peakOutR = 0.0f;
+ WeakAtomic<float> peakInL = 0.0f;
+ WeakAtomic<float> peakInR = 0.0f;
+ WeakAtomic<Frame> inputTracker = 0;
+
+ /* recBuffer
+ Working buffer for audio recording. */
+
+ mcl::AudioBuffer recBuffer;
+
+ /* inBuffer
+ Working buffer for input channel. Used for the in->out bridge. */
+
+ mcl::AudioBuffer inBuffer;
+ };
+
+ Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
#include "core/channels/channelManager.h"
#endif
+using namespace mcl;
+
namespace giada::m::model
{
namespace
{
-struct State
-{
- Clock::State clock;
- Mixer::State mixer;
- std::vector<std::unique_ptr<channel::State>> channels;
-};
-
-struct Data
-{
- std::vector<std::unique_ptr<channel::Buffer>> channels;
- std::vector<std::unique_ptr<Wave>> waves;
- recorder::ActionMap actions;
-#ifdef WITH_VST
- std::vector<std::unique_ptr<Plugin>> plugins;
-#endif
-};
-
-/* -------------------------------------------------------------------------- */
-
template <typename T>
auto getIter_(const std::vector<std::unique_ptr<T>>& source, ID id)
{
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-std::function<void(SwapType)> onSwap_ = nullptr;
-
-Swapper<Layout> layout;
-State state;
-Data data;
-
-/* -------------------------------------------------------------------------- */
-
-DataLock::DataLock(SwapType t)
-: m_swapType(t)
+DataLock::DataLock(Model& m, SwapType t)
+: m_model(m)
+, m_swapType(t)
{
- get().locked = true;
- swap(SwapType::NONE);
+ m_model.get().locked = true;
+ m_model.swap(SwapType::NONE);
}
DataLock::~DataLock()
{
- get().locked = false;
- swap(m_swapType);
+ m_model.get().locked = false;
+ m_model.swap(m_swapType);
}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-channel::Data& Layout::getChannel(ID id)
+Channel& Layout::getChannel(ID id)
{
- return const_cast<channel::Data&>(const_cast<const Layout*>(this)->getChannel(id));
+ return const_cast<Channel&>(const_cast<const Layout*>(this)->getChannel(id));
}
-const channel::Data& Layout::getChannel(ID id) const
+const Channel& Layout::getChannel(ID id) const
{
- auto it = std::find_if(channels.begin(), channels.end(), [id](const channel::Data& c) {
+ auto it = std::find_if(channels.begin(), channels.end(), [id](const Channel& c) {
return c.id == id;
});
assert(it != channels.end());
return *it;
}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init()
+Model::Model()
+: onSwap(nullptr)
{
- get().clock.state = &state.clock;
- get().mixer.state = &state.mixer;
- swap(SwapType::NONE);
+ reset();
}
/* -------------------------------------------------------------------------- */
-Layout& get()
+void Model::reset()
{
- return layout.get();
+ m_shared = {};
+
+ get().sequencer.shared = &m_shared.sequencerShared;
+ get().mixer.shared = &m_shared.mixerShared;
+ get().recorder.shared = &m_shared.recorderShared;
+
+ swap(SwapType::NONE);
}
-Lock get_RT()
+/* -------------------------------------------------------------------------- */
+
+Layout& Model::get() { return m_layout.get(); }
+const Layout& Model::get() const { return m_layout.get(); }
+
+LayoutLock Model::get_RT()
{
- return Lock(layout);
+ return LayoutLock(m_layout);
}
-void swap(SwapType t)
+void Model::swap(SwapType t)
{
- layout.swap();
- if (onSwap_)
- onSwap_(t);
+ m_layout.swap();
+ if (onSwap)
+ onSwap(t);
}
-void onSwap(std::function<void(SwapType)> f)
+/* -------------------------------------------------------------------------- */
+
+DataLock Model::lockData(SwapType t)
{
- onSwap_ = f;
+ return DataLock(*this, t);
}
/* -------------------------------------------------------------------------- */
-bool isLocked()
+bool Model::isLocked() const
{
- return layout.isLocked();
+ return m_layout.isLocked();
}
/* -------------------------------------------------------------------------- */
template <typename T>
-T& getAll()
+T& Model::getAllShared()
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, PluginPtrs>)
- return data.plugins;
+ return m_shared.plugins;
#endif
if constexpr (std::is_same_v<T, WavePtrs>)
- return data.waves;
- if constexpr (std::is_same_v<T, Actions>)
- return data.actions;
- if constexpr (std::is_same_v<T, ChannelBufferPtrs>)
- return data.channels;
- if constexpr (std::is_same_v<T, ChannelStatePtrs>)
- return state.channels;
+ return m_shared.waves;
+ if constexpr (std::is_same_v<T, Actions::Map>)
+ return m_shared.actions;
+ if constexpr (std::is_same_v<T, ChannelSharedPtrs>)
+ return m_shared.channelsShared;
assert(false);
}
#ifdef WITH_VST
-template PluginPtrs& getAll<PluginPtrs>();
+template PluginPtrs& Model::getAllShared<PluginPtrs>();
#endif
-template WavePtrs& getAll<WavePtrs>();
-template Actions& getAll<Actions>();
-template ChannelBufferPtrs& getAll<ChannelBufferPtrs>();
-template ChannelStatePtrs& getAll<ChannelStatePtrs>();
+template WavePtrs& Model::getAllShared<WavePtrs>();
+template Actions::Map& Model::getAllShared<Actions::Map>();
+template ChannelSharedPtrs& Model::getAllShared<ChannelSharedPtrs>();
/* -------------------------------------------------------------------------- */
template <typename T>
-T* find(ID id)
+T* Model::findShared(ID id)
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, Plugin>)
- return get_(data.plugins, id);
+ return get_(m_shared.plugins, id);
#endif
if constexpr (std::is_same_v<T, Wave>)
- return get_(data.waves, id);
+ return get_(m_shared.waves, id);
assert(false);
}
#ifdef WITH_VST
-template Plugin* find<Plugin>(ID id);
+template Plugin* Model::findShared<Plugin>(ID id);
#endif
-template Wave* find<Wave>(ID id);
+template Wave* Model::findShared<Wave>(ID id);
/* -------------------------------------------------------------------------- */
template <typename T>
-void add(T obj)
+void Model::addShared(T obj)
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, PluginPtr>)
- data.plugins.push_back(std::move(obj));
+ m_shared.plugins.push_back(std::move(obj));
#endif
if constexpr (std::is_same_v<T, WavePtr>)
- data.waves.push_back(std::move(obj));
- if constexpr (std::is_same_v<T, ChannelBufferPtr>)
- data.channels.push_back(std::move(obj));
- if constexpr (std::is_same_v<T, ChannelStatePtr>)
- state.channels.push_back(std::move(obj));
+ m_shared.waves.push_back(std::move(obj));
+ if constexpr (std::is_same_v<T, ChannelSharedPtr>)
+ m_shared.channelsShared.push_back(std::move(obj));
}
#ifdef WITH_VST
-template void add<PluginPtr>(PluginPtr p);
+template void Model::addShared<PluginPtr>(PluginPtr p);
#endif
-template void add<WavePtr>(WavePtr p);
-template void add<ChannelBufferPtr>(ChannelBufferPtr p);
-template void add<ChannelStatePtr>(ChannelStatePtr p);
+template void Model::addShared<WavePtr>(WavePtr p);
+template void Model::addShared<ChannelSharedPtr>(ChannelSharedPtr p);
/* -------------------------------------------------------------------------- */
template <typename T>
-void remove(const T& ref)
+void Model::removeShared(const T& ref)
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, Plugin>)
- remove_(data.plugins, ref);
+ remove_(m_shared.plugins, ref);
#endif
if constexpr (std::is_same_v<T, Wave>)
- remove_(data.waves, ref);
+ remove_(m_shared.waves, ref);
}
#ifdef WITH_VST
-template void remove<Plugin>(const Plugin& t);
+template void Model::removeShared<Plugin>(const Plugin& t);
#endif
-template void remove<Wave>(const Wave& t);
+template void Model::removeShared<Wave>(const Wave& t);
/* -------------------------------------------------------------------------- */
template <typename T>
-T& back()
+T& Model::backShared()
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, Plugin>)
- return *data.plugins.back().get();
+ return *m_shared.plugins.back().get();
#endif
if constexpr (std::is_same_v<T, Wave>)
- return *data.waves.back().get();
- if constexpr (std::is_same_v<T, channel::State>)
- return *state.channels.back().get();
- if constexpr (std::is_same_v<T, channel::Buffer>)
- return *data.channels.back().get();
+ return *m_shared.waves.back().get();
+ if constexpr (std::is_same_v<T, Channel::Shared>)
+ return *m_shared.channelsShared.back().get();
}
#ifdef WITH_VST
-template Plugin& back<Plugin>();
+template Plugin& Model::backShared<Plugin>();
#endif
-template Wave& back<Wave>();
-template channel::State& back<channel::State>();
-template channel::Buffer& back<channel::Buffer>();
+template Wave& Model::backShared<Wave>();
+template Channel::Shared& Model::backShared<Channel::Shared>();
/* -------------------------------------------------------------------------- */
template <typename T>
-void clear()
+void Model::clearShared()
{
#ifdef WITH_VST
if constexpr (std::is_same_v<T, PluginPtrs>)
- data.plugins.clear();
+ m_shared.plugins.clear();
#endif
if constexpr (std::is_same_v<T, WavePtrs>)
- data.waves.clear();
+ m_shared.waves.clear();
}
#ifdef WITH_VST
-template void clear<PluginPtrs>();
+template void Model::clearShared<PluginPtrs>();
#endif
-template void clear<WavePtrs>();
+template void Model::clearShared<WavePtrs>();
/* -------------------------------------------------------------------------- */
#ifdef G_DEBUG_MODE
-void debug()
+void Model::debug()
{
puts("======== SYSTEM STATUS ========");
- puts("model::layout");
+ puts("model::layout.channels");
int i = 0;
- for (const channel::Data& c : get().channels)
+ for (const Channel& c : get().channels)
{
- printf("\t%d) - ID=%d name='%s' type=%d columnID=%d state=%p\n",
- i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.state);
+ printf("\t%d) - ID=%d name='%s' type=%d columnID=%d shared=%p\n",
+ i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.shared);
#ifdef WITH_VST
if (c.plugins.size() > 0)
{
puts("model::state.channels");
i = 0;
- for (const auto& c : state.channels)
+ for (const auto& c : m_shared.channelsShared)
{
printf("\t%d) - %p\n", i++, (void*)c.get());
}
puts("model::data.waves");
i = 0;
- for (const auto& w : data.waves)
+ for (const auto& w : m_shared.waves)
printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w.get(), w->id, w->getPath().c_str());
puts("model::data.actions");
- for (const auto& [frame, actions] : getAll<Actions>())
+ for (const auto& [frame, actions] : getAllShared<Actions::Map>())
{
printf("\tframe: %d\n", frame);
for (const Action& a : actions)
puts("model::data.plugins");
i = 0;
- for (const auto& p : data.plugins)
+ for (const auto& p : m_shared.plugins)
printf("\t%d) %p - ID=%d\n", i++, (void*)p.get(), p->id);
#endif
*
* -------------------------------------------------------------------------- */
-#ifndef G_RENDER_MODEL_H
-#define G_RENDER_MODEL_H
+#ifndef G_MODEL_H
+#define G_MODEL_H
#include "core/channels/channel.h"
#include "core/const.h"
+#include "core/model/mixer.h"
+#include "core/model/recorder.h"
+#include "core/model/sequencer.h"
#include "core/plugins/plugin.h"
#include "core/recorder.h"
-#include "core/swapper.h"
#include "core/wave.h"
+#include "deps/mcl-atomic-swapper/src/atomic-swapper.hpp"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+#include "src/core/actions/actions.h"
#include "utils/vector.h"
-#include <algorithm>
namespace giada::m::model
{
-struct Kernel
-{
- bool audioReady = false;
- bool midiReady = false;
-};
-
-struct Recorder
-{
- bool isRecordingAction = false;
- bool isRecordingInput = false;
-};
-
struct MidiIn
{
bool enabled = false;
uint32_t metronome = 0x0;
};
-struct Clock
-{
- struct State
- {
- WeakAtomic<int> currentFrameWait = 0;
- WeakAtomic<int> currentFrame = 0;
- WeakAtomic<int> currentBeat = 0;
- };
-
- State* state = nullptr;
- ClockStatus status = ClockStatus::STOPPED;
- int framesInLoop = 0;
- int framesInBar = 0;
- int framesInBeat = 0;
- int framesInSeq = 0;
- int bars = G_DEFAULT_BARS;
- int beats = G_DEFAULT_BEATS;
- float bpm = G_DEFAULT_BPM;
- int quantize = G_DEFAULT_QUANTIZE;
-};
-
-struct Mixer
-{
- struct State
- {
- std::atomic<bool> active = false;
- WeakAtomic<float> peakOut = 0.0f;
- WeakAtomic<float> peakIn = 0.0f;
- };
-
- State* state = nullptr;
- bool hasSolos = false;
- bool inToOut = false;
-};
-
struct Layout
{
- channel::Data& getChannel(ID id);
- const channel::Data& getChannel(ID id) const;
-
- Clock clock;
- Mixer mixer;
- Kernel kernel;
- Recorder recorder;
- MidiIn midiIn;
+ Channel& getChannel(ID id);
+ const Channel& getChannel(ID id) const;
- std::vector<channel::Data> channels;
+ Sequencer sequencer;
+ Mixer mixer;
+ Recorder recorder;
+ MidiIn midiIn;
+ std::vector<Channel> channels;
/* locked
If locked, Mixer won't process channels. This is used to allow editing the
bool locked = false;
};
-/* Lock
+/* LayoutLock
Alias for a REALTIME scoped lock provided by the Swapper class. Use this in the
real-time thread to lock the Layout. */
-using Lock = Swapper<Layout>::RtLock;
+using LayoutLock = mcl::AtomicSwapper<Layout>::RtLock;
/* SwapType
Type of Layout change.
Hard: the structure has changed (e.g. add a new channel);
Soft: a property has changed (e.g. change volume);
None: something has changed but we don't care.
-Used by model listeners to determine the type of change that occured in the
+Used by model listeners to determine the type of change that occurred in the
layout. */
enum class SwapType
NONE
};
+#ifdef WITH_VST
+using PluginPtr = std::unique_ptr<Plugin>;
+#endif
+using WavePtr = std::unique_ptr<Wave>;
+using ChannelSharedPtr = std::unique_ptr<Channel::Shared>;
+
+#ifdef WITH_VST
+using PluginPtrs = std::vector<PluginPtr>;
+#endif
+using WavePtrs = std::vector<WavePtr>;
+using ChannelSharedPtrs = std::vector<ChannelSharedPtr>;
+
/* -------------------------------------------------------------------------- */
-class DataLock
+class DataLock;
+class Model
{
public:
- DataLock(SwapType t = SwapType::HARD);
- ~DataLock();
+ Model();
-private:
- SwapType m_swapType;
-};
-
-/* -------------------------------------------------------------------------- */
+ bool isLocked() const;
-/* init
-Initializes the internal layout. */
+ /* lockData
+ Returns a scoped locker DataLock object. Use this when you want to lock
+ the model: a locked model won't be processed by Mixer. */
-void init();
+ [[nodiscard]] DataLock lockData(SwapType t = SwapType::HARD);
-/* get
-Returns a reference to the NON-REALTIME layout structure. */
+ /* reser
+ Resets the internal layout to default. */
-Layout& get();
+ void reset();
-/* get_RT
-Returns a Lock object for REALTIME processing. Access layout by calling
-Lock::get() method (returns ready-only Layout). */
+ /* get_RT
+ Returns a LayoutLock object for REALTIME processing. Access layout by
+ calling LayoutLock::get() method (returns ready-only Layout). */
-Lock get_RT();
+ LayoutLock get_RT();
-/* swap
-Swap non-rt layout with the rt one. See 'SwapType' notes above. */
+ /* get
+ Returns a reference to the NON-REALTIME layout structure. */
-void swap(SwapType t);
+ Layout& get();
+ const Layout& get() const;
-/* onSwap
-Registers an optional callback fired when the layout has been swapped. Useful
-for listening to model changes. */
+ /* swap
+ Swap non-rt layout with the rt one. See 'SwapType' notes above. */
-void onSwap(std::function<void(SwapType)> f);
+ void swap(SwapType t);
-bool isLocked();
+ template <typename T>
+ T& getAllShared();
-/* -------------------------------------------------------------------------- */
+ /* findShared
+ Finds something in the shared data given an ID. Returns nullptr if the
+ object is not found. */
-/* Model utilities */
+ template <typename T>
+ T* findShared(ID id);
-#ifdef WITH_VST
-using PluginPtr = std::unique_ptr<Plugin>;
-#endif
-using WavePtr = std::unique_ptr<Wave>;
-using ChannelBufferPtr = std::unique_ptr<channel::Buffer>;
-using ChannelStatePtr = std::unique_ptr<channel::State>;
+ /* addShared
+ Adds some shared data (by moving it). */
-#ifdef WITH_VST
-using PluginPtrs = std::vector<PluginPtr>;
-#endif
-using WavePtrs = std::vector<WavePtr>;
-using Actions = recorder::ActionMap;
-using ChannelBufferPtrs = std::vector<ChannelBufferPtr>;
-using ChannelStatePtrs = std::vector<ChannelStatePtr>;
+ template <typename T>
+ void addShared(T);
-// TODO - are ID-based objects still necessary?
+ template <typename T>
+ void removeShared(const T&);
-template <typename T>
-T& getAll();
+ /* backShared
+ Returns a reference to the last added shared item. */
-/* find
-Finds something (Plugins or Waves) given an ID. Returns nullptr if the object is
-not found. */
+ template <typename T>
+ T& backShared();
-template <typename T>
-T* find(ID id);
+ template <typename T>
+ void clearShared();
-template <typename T>
-void add(T);
+#ifdef G_DEBUG_MODE
+ void debug();
+#endif
-template <typename T>
-void remove(const T&);
+ /* onSwap
+ Optional callback fired when the layout has been swapped. Useful for
+ listening to model changes. */
-template <typename T>
-T& back();
+ std::function<void(SwapType)> onSwap = nullptr;
-template <typename T>
-void clear();
+private:
+ struct Shared
+ {
+ Sequencer::Shared sequencerShared;
+ Mixer::Shared mixerShared;
+ Recorder::Shared recorderShared;
+ std::vector<std::unique_ptr<Channel::Shared>> channelsShared;
-#ifdef G_DEBUG_MODE
-void debug();
+ std::vector<std::unique_ptr<Wave>> waves;
+ Actions::Map actions;
+#ifdef WITH_VST
+ std::vector<std::unique_ptr<Plugin>> plugins;
#endif
+ };
+
+ mcl::AtomicSwapper<Layout> m_layout;
+ Shared m_shared;
+};
+
+/* -------------------------------------------------------------------------- */
+
+class DataLock
+{
+public:
+ DataLock(Model&, SwapType t);
+ ~DataLock();
+
+private:
+ Model& m_model;
+ SwapType m_swapType;
+};
} // namespace giada::m::model
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/recorder.h"
+
+namespace giada::m::model
+{
+bool Recorder::a_isRecordingAction() const
+{
+ return shared->isRecordingAction.load();
+}
+
+bool Recorder::a_isRecordingInput() const
+{
+ return shared->isRecordingInput.load();
+}
+
+void Recorder::a_setRecordingAction(bool b) const
+{
+ shared->isRecordingAction.store(b);
+}
+
+void Recorder::a_setRecordingInput(bool b) const
+{
+ shared->isRecordingInput.store(b);
+}
+} // namespace giada::m::model
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_RECORDER_H
+#define G_MODEL_RECORDER_H
+
+#include "core/weakAtomic.h"
+
+namespace giada::m::model
+{
+class Recorder
+{
+ friend class Model;
+
+public:
+ bool a_isRecordingAction() const;
+ bool a_isRecordingInput() const;
+ void a_setRecordingAction(bool) const;
+ void a_setRecordingInput(bool) const;
+
+private:
+ struct Shared
+ {
+ WeakAtomic<bool> isRecordingAction = false;
+ WeakAtomic<bool> isRecordingInput = false;
+ };
+
+ Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/model/sequencer.h"
+
+namespace giada::m::model
+{
+bool Sequencer::isActive() const
+{
+ return status == SeqStatus::RUNNING || status == SeqStatus::WAITING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::canQuantize() const
+{
+ return quantize > 0 && status == SeqStatus::RUNNING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::isRunning() const
+{
+ return status == SeqStatus::RUNNING;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnBar() const
+{
+ const int currentFrame = shared->currentFrame.load();
+
+ if (status == SeqStatus::WAITING || currentFrame == 0)
+ return false;
+ return currentFrame % framesInBar == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnBeat() const
+{
+ return shared->currentFrame.load() % framesInBeat == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Sequencer::a_isOnFirstBeat() const
+{
+ return shared->currentFrame.load() == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+Frame Sequencer::a_getCurrentFrame() const { return shared->currentFrame.load(); }
+Frame Sequencer::a_getCurrentBeat() const { return shared->currentBeat.load(); }
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::a_setCurrentFrame(Frame f) const { shared->currentFrame.store(f); }
+void Sequencer::a_setCurrentBeat(Frame f) const { shared->currentBeat.store(f); }
+} // namespace giada::m::model
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_MODEL_SEQUENCER_H
+#define G_MODEL_SEQUENCER_H
+
+#include "core/const.h"
+#include "core/types.h"
+#include "core/weakAtomic.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
+
+namespace giada::m::model
+{
+class Sequencer
+{
+ friend class Model;
+
+public:
+ /* isRunning
+ When sequencer is actually moving forward, i.e. SeqStatus == RUNNING. */
+
+ bool isRunning() const;
+
+ /* isActive
+ Sequencer is enabled, but might be in wait mode, i.e. SeqStatus == RUNNING or
+ SeqStatus == WAITING. */
+
+ bool isActive() const;
+
+ /* canQuantize
+ Sequencer can quantize only if it's running and quantizer is enabled. */
+
+ bool canQuantize() const;
+
+ bool a_isOnBar() const;
+ bool a_isOnBeat() const;
+ bool a_isOnFirstBeat() const;
+
+ Frame a_getCurrentFrame() const;
+ Frame a_getCurrentBeat() const;
+
+ void a_setCurrentFrame(Frame) const;
+ void a_setCurrentBeat(Frame) const;
+
+ SeqStatus status = SeqStatus::STOPPED;
+ int framesInLoop = 0;
+ int framesInBar = 0;
+ int framesInBeat = 0;
+ int framesInSeq = 0;
+ int bars = G_DEFAULT_BARS;
+ int beats = G_DEFAULT_BEATS;
+ float bpm = G_DEFAULT_BPM;
+ int quantize = G_DEFAULT_QUANTIZE;
+
+private:
+ struct Shared
+ {
+ WeakAtomic<Frame> currentFrame = 0;
+ WeakAtomic<Frame> currentBeat = 0;
+ };
+
+ Shared* shared = nullptr;
+};
+} // namespace giada::m::model
+
+#endif
#include "core/model/storage.h"
#include "core/channels/channelManager.h"
#include "core/conf.h"
+#include "core/engine.h"
#include "core/kernelAudio.h"
#include "core/model/model.h"
#include "core/patch.h"
#include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
#include "core/sequencer.h"
#include "core/waveManager.h"
+#include "src/core/actions/actionRecorder.h"
#include <cassert>
+extern giada::m::Engine g_engine;
+
namespace giada::m::model
{
namespace
{
-void loadChannels_(const std::vector<patch::Channel>& channels, int samplerate)
+void loadChannels_(const std::vector<Patch::Channel>& channels, int samplerate)
{
- float samplerateRatio = conf::conf.samplerate / static_cast<float>(samplerate);
+ float samplerateRatio = g_engine.kernelAudio.getSampleRate() / static_cast<float>(samplerate);
- for (const patch::Channel& pchannel : channels)
- get().channels.push_back(channelManager::deserializeChannel(pchannel, samplerateRatio));
+ for (const Patch::Channel& pchannel : channels)
+ g_engine.model.get().channels.push_back(
+ g_engine.channelManager.deserializeChannel(pchannel, samplerateRatio, g_engine.kernelAudio.getBufferSize()));
}
/* -------------------------------------------------------------------------- */
-void loadActions_(const std::vector<patch::Action>& pactions)
+void loadActions_(const std::vector<Patch::Action>& pactions)
{
- getAll<Actions>() = std::move(recorderHandler::deserializeActions(pactions));
+ g_engine.model.getAllShared<Actions::Map>() = std::move(g_engine.actionRecorder.deserializeActions(pactions));
}
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void store(patch::Patch& patch)
+void store(Patch::Data& patch)
{
- const Layout& layout = get();
+ const Layout& layout = g_engine.model.get();
- patch.bars = layout.clock.bars;
- patch.beats = layout.clock.beats;
- patch.bpm = layout.clock.bpm;
- patch.quantize = layout.clock.quantize;
- patch.metronome = sequencer::isMetronomeOn(); // TODO - add bool metronome to Layout
- patch.samplerate = conf::conf.samplerate;
+ patch.bars = layout.sequencer.bars;
+ patch.beats = layout.sequencer.beats;
+ patch.bpm = layout.sequencer.bpm;
+ patch.quantize = layout.sequencer.quantize;
+ patch.metronome = g_engine.sequencer.isMetronomeOn(); // TODO - addShared bool metronome to Layout
+ patch.samplerate = g_engine.kernelAudio.getSampleRate();
#ifdef WITH_VST
- for (const auto& p : getAll<PluginPtrs>())
- patch.plugins.push_back(pluginManager::serializePlugin(*p));
+ patch.plugins.clear();
+ for (const auto& p : g_engine.model.getAllShared<PluginPtrs>())
+ patch.plugins.push_back(g_engine.pluginManager.serializePlugin(*p));
#endif
- patch.actions = recorderHandler::serializeActions(getAll<Actions>());
+ patch.actions = g_engine.actionRecorder.serializeActions(g_engine.model.getAllShared<Actions::Map>());
- for (const auto& w : getAll<WavePtrs>())
- patch.waves.push_back(waveManager::serializeWave(*w));
+ patch.waves.clear();
+ for (const auto& w : g_engine.model.getAllShared<WavePtrs>())
+ patch.waves.push_back(g_engine.waveManager.serializeWave(*w));
- for (const channel::Data& c : layout.channels)
- patch.channels.push_back(channelManager::serializeChannel(c));
+ patch.channels.clear();
+ for (const Channel& c : layout.channels)
+ patch.channels.push_back(g_engine.channelManager.serializeChannel(c));
}
/* -------------------------------------------------------------------------- */
-void store(conf::Conf& conf)
+void store(Conf::Data& conf)
{
- const Layout& layout = get();
+ const Layout& layout = g_engine.model.get();
conf.midiInEnabled = layout.midiIn.enabled;
conf.midiInFilter = layout.midiIn.filter;
/* -------------------------------------------------------------------------- */
-void load(const patch::Patch& patch)
+void load(const Patch::Data& patch)
{
- DataLock lock;
+ DataLock lock = g_engine.model.lockData();
/* Clear and re-initialize channels first. */
- get().channels = {};
- getAll<ChannelBufferPtrs>().clear();
- getAll<ChannelStatePtrs>().clear();
+ g_engine.model.get().channels = {};
+ g_engine.model.getAllShared<ChannelSharedPtrs>().clear();
/* Load external data first: plug-ins and waves. */
#ifdef WITH_VST
- getAll<PluginPtrs>().clear();
- for (const patch::Plugin& pplugin : patch.plugins)
- getAll<PluginPtrs>().push_back(pluginManager::deserializePlugin(pplugin, patch.version));
+ g_engine.model.getAllShared<PluginPtrs>().clear();
+ for (const Patch::Plugin& pplugin : patch.plugins)
+ g_engine.model.getAllShared<PluginPtrs>().push_back(g_engine.pluginManager.deserializePlugin(
+ pplugin, patch.version, g_engine.kernelAudio.getSampleRate(), g_engine.kernelAudio.getBufferSize(), g_engine.sequencer));
#endif
- getAll<WavePtrs>().clear();
- for (const patch::Wave& pwave : patch.waves)
+ g_engine.model.getAllShared<WavePtrs>().clear();
+ for (const Patch::Wave& pwave : patch.waves)
{
- std::unique_ptr<Wave> w = waveManager::deserializeWave(pwave, conf::conf.samplerate,
- conf::conf.rsmpQuality);
+ std::unique_ptr<Wave> w = g_engine.waveManager.deserializeWave(pwave, g_engine.kernelAudio.getSampleRate(),
+ g_engine.conf.data.rsmpQuality);
if (w != nullptr)
- getAll<WavePtrs>().push_back(std::move(w));
+ g_engine.model.getAllShared<WavePtrs>().push_back(std::move(w));
}
/* Then load up channels, actions and global properties. */
- loadChannels_(patch.channels, patch::patch.samplerate);
+ loadChannels_(patch.channels, g_engine.patch.data.samplerate);
loadActions_(patch.actions);
- get().clock.status = ClockStatus::STOPPED;
- get().clock.bars = patch.bars;
- get().clock.beats = patch.beats;
- get().clock.bpm = patch.bpm;
- get().clock.quantize = patch.quantize;
+ g_engine.model.get().sequencer.status = SeqStatus::STOPPED;
+ g_engine.model.get().sequencer.bars = patch.bars;
+ g_engine.model.get().sequencer.beats = patch.beats;
+ g_engine.model.get().sequencer.bpm = patch.bpm;
+ g_engine.model.get().sequencer.quantize = patch.quantize;
}
/* -------------------------------------------------------------------------- */
-void load(const conf::Conf& c)
+void load(const Conf::Data& c)
{
- get().midiIn.enabled = c.midiInEnabled;
- get().midiIn.filter = c.midiInFilter;
- get().midiIn.rewind = c.midiInRewind;
- get().midiIn.startStop = c.midiInStartStop;
- get().midiIn.actionRec = c.midiInActionRec;
- get().midiIn.inputRec = c.midiInInputRec;
- get().midiIn.volumeIn = c.midiInVolumeIn;
- get().midiIn.volumeOut = c.midiInVolumeOut;
- get().midiIn.beatDouble = c.midiInBeatDouble;
- get().midiIn.beatHalf = c.midiInBeatHalf;
- get().midiIn.metronome = c.midiInMetronome;
-
- swap(SwapType::NONE);
+ g_engine.model.get().midiIn.enabled = c.midiInEnabled;
+ g_engine.model.get().midiIn.filter = c.midiInFilter;
+ g_engine.model.get().midiIn.rewind = c.midiInRewind;
+ g_engine.model.get().midiIn.startStop = c.midiInStartStop;
+ g_engine.model.get().midiIn.actionRec = c.midiInActionRec;
+ g_engine.model.get().midiIn.inputRec = c.midiInInputRec;
+ g_engine.model.get().midiIn.volumeIn = c.midiInVolumeIn;
+ g_engine.model.get().midiIn.volumeOut = c.midiInVolumeOut;
+ g_engine.model.get().midiIn.beatDouble = c.midiInBeatDouble;
+ g_engine.model.get().midiIn.beatHalf = c.midiInBeatHalf;
+ g_engine.model.get().midiIn.metronome = c.midiInMetronome;
+
+ g_engine.model.swap(SwapType::NONE);
}
} // namespace giada::m::model
#ifndef G_MODEL_STORAGE_H
#define G_MODEL_STORAGE_H
-namespace giada::m::patch
-{
-struct Patch;
-}
-namespace giada::m::conf
-{
-struct Conf;
-}
+#include "core/patch.h"
+#include "core/conf.h"
+
namespace giada::m::model
{
-void store(conf::Conf& c);
-void store(patch::Patch& p);
-void load(const patch::Patch& p);
-void load(const conf::Conf& c);
+void store(Conf::Data& c);
+void store(Patch::Data& p);
+void load(const Patch::Data& p);
+void load(const Conf::Data& c);
} // namespace giada::m::model
#endif
namespace nl = nlohmann;
-namespace giada
-{
-namespace m
-{
-namespace patch
+namespace giada::m
{
namespace
{
-void readCommons_(const nl::json& j)
+void readCommons_(Patch::Data& patch, const nl::json& j)
{
patch.name = j.value(PATCH_KEY_NAME, G_DEFAULT_PATCH_NAME);
patch.bars = j.value(PATCH_KEY_BARS, G_DEFAULT_BARS);
/* -------------------------------------------------------------------------- */
-void readColumns_(const nl::json& j)
+void readColumns_(Patch::Data& patch, const nl::json& j)
{
ID id = 0;
for (const auto& jcol : j[PATCH_KEY_COLUMNS])
{
- Column c;
+ Patch::Column c;
c.id = jcol.value(PATCH_KEY_COLUMN_ID, ++id);
c.width = jcol.value(PATCH_KEY_COLUMN_WIDTH, G_DEFAULT_COLUMN_WIDTH);
patch.columns.push_back(c);
#ifdef WITH_VST
-void readPlugins_(const nl::json& j)
+void readPlugins_(Patch::Data& patch, const nl::json& j)
{
if (!j.contains(PATCH_KEY_PLUGINS))
return;
ID id = 0;
for (const auto& jplugin : j[PATCH_KEY_PLUGINS])
{
- Plugin p;
+ Patch::Plugin p;
p.id = jplugin.value(PATCH_KEY_PLUGIN_ID, ++id);
p.path = jplugin.value(PATCH_KEY_PLUGIN_PATH, "");
p.bypass = jplugin.value(PATCH_KEY_PLUGIN_BYPASS, false);
- if (patch.version < Version{0, 17, 0})
+ if (patch.version < Patch::Version{0, 17, 0})
for (const auto& jparam : jplugin[PATCH_KEY_PLUGIN_PARAMS])
p.params.push_back(jparam);
else
/* -------------------------------------------------------------------------- */
-void readWaves_(const nl::json& j, const std::string& basePath)
+void readWaves_(Patch::Data& patch, const nl::json& j, const std::string& basePath)
{
if (!j.contains(PATCH_KEY_WAVES))
return;
ID id = 0;
for (const auto& jwave : j[PATCH_KEY_WAVES])
{
- Wave w;
+ Patch::Wave w;
w.id = jwave.value(PATCH_KEY_WAVE_ID, ++id);
- w.path = basePath + jwave.value(PATCH_KEY_WAVE_PATH, "");
+ w.path = basePath + G_SLASH + jwave.value(PATCH_KEY_WAVE_PATH, "");
patch.waves.push_back(w);
}
}
/* -------------------------------------------------------------------------- */
-void readActions_(const nl::json& j)
+void readActions_(Patch::Data& patch, const nl::json& j)
{
if (!j.contains(PATCH_KEY_ACTIONS))
return;
ID id = 0;
for (const auto& jaction : j[PATCH_KEY_ACTIONS])
{
- Action a;
+ Patch::Action a;
a.id = jaction.value(G_PATCH_KEY_ACTION_ID, ++id);
a.channelId = jaction.value(G_PATCH_KEY_ACTION_CHANNEL, 0);
a.frame = jaction.value(G_PATCH_KEY_ACTION_FRAME, 0);
/* -------------------------------------------------------------------------- */
-void readChannels_(const nl::json& j)
+void readChannels_(Patch::Data& patch, const nl::json& j)
{
if (!j.contains(PATCH_KEY_CHANNELS))
return;
- ID defaultId = mixer::PREVIEW_CHANNEL_ID;
+ ID defaultId = Mixer::PREVIEW_CHANNEL_ID;
for (const auto& jchannel : j[PATCH_KEY_CHANNELS])
{
- Channel c;
+ Patch::Channel c;
c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++defaultId);
c.type = static_cast<ChannelType>(jchannel.value(PATCH_KEY_CHANNEL_TYPE, 1));
c.volume = jchannel.value(PATCH_KEY_CHANNEL_VOLUME, G_DEFAULT_VOL);
#ifdef WITH_VST
-void writePlugins_(nl::json& j)
+void writePlugins_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_PLUGINS] = nl::json::array();
- for (const Plugin& p : patch.plugins)
+ for (const Patch::Plugin& p : patch.plugins)
{
nl::json jplugin;
/* -------------------------------------------------------------------------- */
-void writeColumns_(nl::json& j)
+void writeColumns_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_COLUMNS] = nl::json::array();
- for (const Column& column : patch.columns)
+ for (const Patch::Column& column : patch.columns)
{
nl::json jcolumn;
jcolumn[PATCH_KEY_COLUMN_ID] = column.id;
/* -------------------------------------------------------------------------- */
-void writeActions_(nl::json& j)
+void writeActions_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_ACTIONS] = nl::json::array();
- for (const Action& a : patch.actions)
+ for (const Patch::Action& a : patch.actions)
{
nl::json jaction;
jaction[G_PATCH_KEY_ACTION_ID] = a.id;
/* -------------------------------------------------------------------------- */
-void writeWaves_(nl::json& j)
+void writeWaves_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_WAVES] = nl::json::array();
- for (const Wave& w : patch.waves)
+ for (const Patch::Wave& w : patch.waves)
{
nl::json jwave;
jwave[PATCH_KEY_WAVE_ID] = w.id;
/* -------------------------------------------------------------------------- */
-void writeCommons_(nl::json& j)
+void writeCommons_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_HEADER] = "GIADAPTC";
j[PATCH_KEY_VERSION_MAJOR] = G_VERSION_MAJOR;
/* -------------------------------------------------------------------------- */
-void writeChannels_(nl::json& j)
+void writeChannels_(const Patch::Data& patch, nl::json& j)
{
j[PATCH_KEY_CHANNELS] = nl::json::array();
- for (const Channel& c : patch.channels)
+ for (const Patch::Channel& c : patch.channels)
{
-
nl::json jchannel;
jchannel[PATCH_KEY_CHANNEL_ID] = c.id;
/* -------------------------------------------------------------------------- */
-void modernize_()
+void modernize_(Patch::Data& patch)
{
- for (Channel& c : patch.channels)
+ for (Patch::Channel& c : patch.channels)
{
/* 0.16.3
Make sure that ChannelType is correct: ID 1, 2 are MASTER channels, ID 3
is PREVIEW channel. */
- if (c.id == mixer::MASTER_OUT_CHANNEL_ID || c.id == mixer::MASTER_IN_CHANNEL_ID)
+ if (c.id == Mixer::MASTER_OUT_CHANNEL_ID || c.id == Mixer::MASTER_IN_CHANNEL_ID)
c.type = ChannelType::MASTER;
- else if (c.id == mixer::PREVIEW_CHANNEL_ID)
+ else if (c.id == Mixer::PREVIEW_CHANNEL_ID)
c.type = ChannelType::PREVIEW;
/* 0.16.4
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Patch patch;
-
-/* -------------------------------------------------------------------------- */
-
-bool Version::operator==(const Version& o) const
+bool Patch::Version::operator==(const Version& o) const
{
return major == o.major && minor == o.minor && patch == o.patch;
}
-bool Version::operator<(const Version& o) const
+bool Patch::Version::operator<(const Version& o) const
{
if (major < o.major)
return true;
return false;
}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init()
+void Patch::reset()
{
- patch = Patch();
+ data = Data();
}
/* -------------------------------------------------------------------------- */
-bool write(const std::string& file)
+bool Patch::write(const std::string& file) const
{
nl::json j;
- writeCommons_(j);
- writeColumns_(j);
- writeChannels_(j);
- writeActions_(j);
- writeWaves_(j);
+ writeCommons_(data, j);
+ writeColumns_(data, j);
+ writeChannels_(data, j);
+ writeActions_(data, j);
+ writeWaves_(data, j);
#ifdef WITH_VST
- writePlugins_(j);
+ writePlugins_(data, j);
#endif
std::ofstream ofs(file);
/* -------------------------------------------------------------------------- */
-int read(const std::string& file, const std::string& basePath)
+int Patch::read(const std::string& file, const std::string& basePath)
{
std::ifstream ifs(file);
if (!ifs.good())
if (j[PATCH_KEY_HEADER] != "GIADAPTC")
return G_PATCH_INVALID;
- patch.version = {
+ data.version = {
static_cast<int>(j[PATCH_KEY_VERSION_MAJOR]),
static_cast<int>(j[PATCH_KEY_VERSION_MINOR]),
static_cast<int>(j[PATCH_KEY_VERSION_PATCH])};
- if (patch.version < Version{0, 16, 0})
+ if (data.version < Version{0, 16, 0})
return G_PATCH_UNSUPPORTED;
try
{
- readCommons_(j);
- readColumns_(j);
+ readCommons_(data, j);
+ readColumns_(data, j);
#ifdef WITH_VST
- readPlugins_(j);
+ readPlugins_(data, j);
#endif
- readWaves_(j, basePath);
- readActions_(j);
- readChannels_(j);
- modernize_();
+ readWaves_(data, j, basePath);
+ readActions_(data, j);
+ readChannels_(data, j);
+ modernize_(data);
}
catch (nl::json::exception& e)
{
return G_PATCH_OK;
}
-} // namespace patch
-} // namespace m
-} // namespace giada
+} // namespace giada::m
#include <string>
#include <vector>
-namespace giada
+namespace giada::m
{
-namespace m
+class Patch
{
-namespace patch
-{
-struct Version
-{
- int major = G_VERSION_MAJOR;
- int minor = G_VERSION_MINOR;
- int patch = G_VERSION_PATCH;
-
- bool operator==(const Version& o) const;
- bool operator<(const Version& o) const;
-};
-
-struct Column
-{
- ID id;
- int width;
-};
-
-struct Channel
-{
- ID id;
- ChannelType type;
- int height;
- std::string name;
- ID columnId;
- int key;
- bool mute;
- bool solo;
- float volume = G_DEFAULT_VOL;
- float pan = G_DEFAULT_PAN;
- bool hasActions;
- bool armed;
- bool midiIn;
- uint32_t midiInKeyPress;
- uint32_t midiInKeyRel;
- uint32_t midiInKill;
- uint32_t midiInArm;
- uint32_t midiInVolume;
- uint32_t midiInMute;
- uint32_t midiInSolo;
- int midiInFilter;
- bool midiOutL;
- uint32_t midiOutLplaying;
- uint32_t midiOutLmute;
- uint32_t midiOutLsolo;
- // sample channel
- ID waveId = 0;
- SamplePlayerMode mode;
- Frame begin;
- Frame end;
- Frame shift;
- bool readActions;
- float pitch = G_DEFAULT_PITCH;
- bool inputMonitor;
- bool overdubProtection;
- bool midiInVeloAsVol;
- uint32_t midiInReadActions;
- uint32_t midiInPitch;
- // midi channel
- bool midiOut;
- int midiOutChan;
+public:
+ struct Version
+ {
+ int major = G_VERSION_MAJOR;
+ int minor = G_VERSION_MINOR;
+ int patch = G_VERSION_PATCH;
+
+ bool operator==(const Version& o) const;
+ bool operator<(const Version& o) const;
+ };
+
+ struct Column
+ {
+ ID id;
+ int width;
+ };
+
+ struct Channel
+ {
+ ID id;
+ ChannelType type;
+ int height;
+ std::string name;
+ ID columnId;
+ int key;
+ bool mute;
+ bool solo;
+ float volume = G_DEFAULT_VOL;
+ float pan = G_DEFAULT_PAN;
+ bool hasActions;
+ bool armed;
+ bool midiIn;
+ uint32_t midiInKeyPress;
+ uint32_t midiInKeyRel;
+ uint32_t midiInKill;
+ uint32_t midiInArm;
+ uint32_t midiInVolume;
+ uint32_t midiInMute;
+ uint32_t midiInSolo;
+ int midiInFilter;
+ bool midiOutL;
+ uint32_t midiOutLplaying;
+ uint32_t midiOutLmute;
+ uint32_t midiOutLsolo;
+ // sample channel
+ ID waveId = 0;
+ SamplePlayerMode mode;
+ Frame begin;
+ Frame end;
+ Frame shift;
+ bool readActions;
+ float pitch = G_DEFAULT_PITCH;
+ bool inputMonitor;
+ bool overdubProtection;
+ bool midiInVeloAsVol;
+ uint32_t midiInReadActions;
+ uint32_t midiInPitch;
+ // midi channel
+ bool midiOut;
+ int midiOutChan;
#ifdef WITH_VST
- std::vector<ID> pluginIds;
+ std::vector<ID> pluginIds;
#endif
-};
-
-struct Action
-{
- ID id;
- ID channelId;
- Frame frame;
- uint32_t event;
- ID prevId;
- ID nextId;
-};
-
-struct Wave
-{
- ID id;
- std::string path;
-};
+ };
+
+ struct Action
+ {
+ ID id;
+ ID channelId;
+ Frame frame;
+ uint32_t event;
+ ID prevId;
+ ID nextId;
+ };
+
+ struct Wave
+ {
+ ID id;
+ std::string path;
+ };
#ifdef WITH_VST
-struct Plugin
-{
- ID id;
- std::string path;
- bool bypass;
- std::vector<float> params; // TODO - to be removed in 0.18.0
- std::string state;
- std::vector<uint32_t> midiInParams;
-};
+ struct Plugin
+ {
+ ID id;
+ std::string path;
+ bool bypass;
+ std::vector<float> params; // TODO - to be removed in 0.18.0
+ std::string state;
+ std::vector<uint32_t> midiInParams;
+ };
#endif
-struct Patch
-{
- Version version;
- std::string name = G_DEFAULT_PATCH_NAME;
- int bars = G_DEFAULT_BARS;
- int beats = G_DEFAULT_BEATS;
- float bpm = G_DEFAULT_BPM;
- bool quantize = G_DEFAULT_QUANTIZE;
- int lastTakeId = 0;
- int samplerate = G_DEFAULT_SAMPLERATE;
- bool metronome = false;
-
- std::vector<Column> columns;
- std::vector<Channel> channels;
- std::vector<Action> actions;
- std::vector<Wave> waves;
+ struct Data
+ {
+ Version version;
+ std::string name = G_DEFAULT_PATCH_NAME;
+ int bars = G_DEFAULT_BARS;
+ int beats = G_DEFAULT_BEATS;
+ float bpm = G_DEFAULT_BPM;
+ bool quantize = G_DEFAULT_QUANTIZE;
+ int lastTakeId = 0;
+ int samplerate = G_DEFAULT_SAMPLERATE;
+ bool metronome = false;
+
+ std::vector<Column> columns;
+ std::vector<Channel> channels;
+ std::vector<Action> actions;
+ std::vector<Wave> waves;
#ifdef WITH_VST
- std::vector<Plugin> plugins;
+ std::vector<Plugin> plugins;
#endif
-};
-
-/* -------------------------------------------------------------------------- */
+ };
-extern Patch patch;
+ /* write
+ Writes patch to file. */
-/* -------------------------------------------------------------------------- */
+ bool write(const std::string& file) const;
-/* init
-Initializes the patch with default values. */
+ /* reset
+ Initializes the patch with default values. */
-void init();
+ void reset();
-/* read
-Reads patch from file. It takes 'basePath' as parameter for Wave reading. */
+ /* read
+ Reads patch from file. It takes 'basePath' as parameter for Wave reading. */
-int read(const std::string& file, const std::string& basePath);
+ int read(const std::string& file, const std::string& basePath);
-/* write
-Writes patch to file. */
-
-bool write(const std::string& file);
-} // namespace patch
-} // namespace m
-} // namespace giada
+ Data data;
+};
+} // namespace giada::m
#endif
#ifdef WITH_VST
-#include "plugin.h"
+#include "core/plugins/plugin.h"
#include "core/const.h"
-#include "core/plugins/pluginManager.h"
#include "utils/log.h"
#include "utils/time.h"
#include <FL/Fl.H>
/* -------------------------------------------------------------------------- */
-Plugin::Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> plugin, double samplerate,
- int buffersize)
+Plugin::Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> plugin,
+ std::unique_ptr<PluginHost::Info> playHead, double samplerate, int buffersize)
: id(id)
, valid(true)
, onEditorResize(nullptr)
, m_plugin(std::move(plugin))
-, m_playHead(std::make_unique<pluginHost::Info>())
+, m_playHead(std::move(playHead))
, m_bypass(false)
, m_hasEditor(m_plugin->hasEditor())
{
/* -------------------------------------------------------------------------- */
-Plugin::Plugin(const Plugin& o)
-: id(o.id)
-, midiInParams(o.midiInParams)
-, valid(o.valid)
-, onEditorResize(o.onEditorResize)
-, m_plugin(std::move(pluginManager::makePlugin(o)->m_plugin))
-, m_bypass(o.m_bypass.load())
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
Plugin::~Plugin()
{
if (!valid)
class Plugin : private juce::ComponentListener
{
public:
+ /* Plugin (1)
+ Constructs an invalid plug-in. */
+
Plugin(ID id, const std::string& UID);
- Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance> p, double samplerate, int buffersize);
- Plugin(const Plugin& o);
+
+ /* Plugin (2)
+ Constructs a valid and working plug-in. */
+
+ Plugin(ID id, std::unique_ptr<juce::AudioPluginInstance>, std::unique_ptr<PluginHost::Info>,
+ double samplerate, int buffersize);
+
+ Plugin(const Plugin& o) = delete;
+ Plugin(Plugin&& o) = delete;
+ Plugin& operator=(const Plugin&) = delete;
+ Plugin& operator=(Plugin&&) = delete;
+
~Plugin();
- /* TODO - mark is as non-copiable/movable*/
/* getUniqueId
Returns a string-based UID. */
int countMainOutChannels() const;
std::unique_ptr<juce::AudioPluginInstance> m_plugin;
- std::unique_ptr<pluginHost::Info> m_playHead;
+ std::unique_ptr<PluginHost::Info> m_playHead;
juce::AudioBuffer<float> m_buffer;
std::atomic<bool> m_bypass;
#include "core/plugins/pluginHost.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include "core/const.h"
#include "core/model/model.h"
#include "core/plugins/plugin.h"
#include "core/plugins/pluginManager.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include "utils/log.h"
#include "utils/vector.h"
#include <cassert>
-namespace giada::m::pluginHost
+namespace giada::m
{
-namespace
+PluginHost::Info::Info(const Sequencer& s, int sampleRate)
+: m_sequencer(s)
+, m_sampleRate(sampleRate)
{
-std::vector<Plugin*> plugins_;
-juce::MessageManager* messageManager_;
-juce::AudioBuffer<float> audioBuffer_;
-ID pluginId_;
-
-/* -------------------------------------------------------------------------- */
-
-void giadaToJuceTempBuf_(const AudioBuffer& outBuf)
-{
- for (int i = 0; i < outBuf.countFrames(); i++)
- for (int j = 0; j < outBuf.countChannels(); j++)
- audioBuffer_.setSample(j, i, outBuf[i][j]);
-}
-
-/* juceToGiadaOutBuf_
-Converts buffer from Juce to Giada. A note for the future: if we overwrite (=)
-(as we do now) it's SEND, if we add (+) it's INSERT. */
-
-void juceToGiadaOutBuf_(AudioBuffer& outBuf)
-{
- for (int i = 0; i < outBuf.countFrames(); i++)
- for (int j = 0; j < outBuf.countChannels(); j++)
- outBuf[i][j] = audioBuffer_.getSample(j, i);
}
/* -------------------------------------------------------------------------- */
-void processPlugins_(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events)
-{
- for (Plugin* p : plugins)
- {
- if (!p->valid || p->isSuspended() || p->isBypassed())
- continue;
- p->process(audioBuffer_, events);
- }
- events.clear();
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-bool Info::getCurrentPosition(CurrentPositionInfo& result)
+bool PluginHost::Info::getCurrentPosition(CurrentPositionInfo& result)
{
- result.bpm = clock::getBpm();
- result.timeInSamples = clock::getCurrentFrame();
- result.timeInSeconds = clock::getCurrentSecond();
- result.isPlaying = clock::isRunning();
+ result.bpm = m_sequencer.getBpm();
+ result.timeInSamples = m_sequencer.getCurrentFrame();
+ result.timeInSeconds = m_sequencer.getCurrentSecond(m_sampleRate);
+ result.isPlaying = m_sequencer.isRunning();
return true;
}
/* -------------------------------------------------------------------------- */
-bool Info::canControlTransport()
+bool PluginHost::Info::canControlTransport()
{
return false;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void close()
+PluginHost::PluginHost(model::Model& m)
+: m_model(m)
{
- messageManager_->deleteInstance();
- model::clear<model::PluginPtrs>();
}
/* -------------------------------------------------------------------------- */
-void init(int buffersize)
+void PluginHost::reset(int bufferSize)
{
- messageManager_ = juce::MessageManager::getInstance();
- audioBuffer_.setSize(G_MAX_IO_CHANS, buffersize);
- pluginId_ = 0;
+ freeAllPlugins();
+ m_audioBuffer.setSize(G_MAX_IO_CHANS, bufferSize);
}
/* -------------------------------------------------------------------------- */
-void processStack(AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
+void PluginHost::processStack(mcl::AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
juce::MidiBuffer* events)
{
- assert(outBuf.countFrames() == audioBuffer_.getNumSamples());
+ assert(outBuf.countFrames() == m_audioBuffer.getNumSamples());
/* If events are null: Audio stack processing (master in, master out or
sample channels. No need for MIDI events.
if (events == nullptr)
{
- giadaToJuceTempBuf_(outBuf);
+ giadaToJuceTempBuf(outBuf);
juce::MidiBuffer dummyEvents; // empty
- processPlugins_(plugins, dummyEvents);
+ processPlugins(plugins, dummyEvents);
}
else
{
- audioBuffer_.clear();
- processPlugins_(plugins, *events);
+ m_audioBuffer.clear();
+ processPlugins(plugins, *events);
}
- juceToGiadaOutBuf_(outBuf);
+ juceToGiadaOutBuf(outBuf);
}
/* -------------------------------------------------------------------------- */
-void addPlugin(std::unique_ptr<Plugin> p, ID channelId)
+const Plugin& PluginHost::addPlugin(std::unique_ptr<Plugin> p)
{
- model::add(std::move(p));
-
- const Plugin& pluginRef = model::back<Plugin>();
-
- /* TODO - unfortunately JUCE wants mutable plugin objects due to the
- presence of the non-const processBlock() method. Why not const_casting
- only in the Plugin class? */
- model::get().getChannel(channelId).plugins.push_back(const_cast<Plugin*>(&pluginRef));
- model::swap(model::SwapType::HARD);
+ m_model.addShared(std::move(p));
+ return m_model.backShared<Plugin>();
}
/* -------------------------------------------------------------------------- */
-void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, ID channelId)
+void PluginHost::swapPlugin(const m::Plugin& p1, const m::Plugin& p2, std::vector<Plugin*>& plugins)
{
- std::vector<m::Plugin*>& pvec = model::get().getChannel(channelId).plugins;
- std::size_t index1 = u::vector::indexOf(pvec, &p1);
- std::size_t index2 = u::vector::indexOf(pvec, &p2);
- std::swap(pvec.at(index1), pvec.at(index2));
-
- model::swap(model::SwapType::HARD);
+ std::size_t index1 = u::vector::indexOf(plugins, &p1);
+ std::size_t index2 = u::vector::indexOf(plugins, &p2);
+ std::swap(plugins.at(index1), plugins.at(index2));
}
/* -------------------------------------------------------------------------- */
-void freePlugin(const m::Plugin& plugin, ID channelId)
+void PluginHost::freePlugin(const m::Plugin& plugin)
{
- u::vector::remove(model::get().getChannel(channelId).plugins, &plugin);
- model::swap(model::SwapType::HARD);
- model::remove(plugin);
+ m_model.removeShared(plugin);
}
-void freePlugins(const std::vector<Plugin*>& plugins)
+void PluginHost::freePlugins(const std::vector<Plugin*>& plugins)
{
- // TODO - channels???
for (const Plugin* p : plugins)
- model::remove(*p);
+ m_model.removeShared(*p);
}
/* -------------------------------------------------------------------------- */
-std::vector<Plugin*> clonePlugins(const std::vector<Plugin*>& plugins)
+void PluginHost::freeAllPlugins()
{
- std::vector<Plugin*> out;
- for (const Plugin* p : plugins)
- {
- model::add(pluginManager::makePlugin(*p));
- out.push_back(&model::back<Plugin>());
- }
- return out;
+ m_model.clearShared<model::PluginPtrs>();
}
/* -------------------------------------------------------------------------- */
-void setPluginParameter(ID pluginId, int paramIndex, float value)
+void PluginHost::setPluginParameter(ID pluginId, int paramIndex, float value)
{
- model::find<Plugin>(pluginId)->setParameter(paramIndex, value);
+ m_model.findShared<Plugin>(pluginId)->setParameter(paramIndex, value);
}
/* -------------------------------------------------------------------------- */
-void setPluginProgram(ID pluginId, int programIndex)
+void PluginHost::setPluginProgram(ID pluginId, int programIndex)
{
- model::find<Plugin>(pluginId)->setCurrentProgram(programIndex);
+ m_model.findShared<Plugin>(pluginId)->setCurrentProgram(programIndex);
}
/* -------------------------------------------------------------------------- */
-void toggleBypass(ID pluginId)
+void PluginHost::toggleBypass(ID pluginId)
{
- Plugin& plugin = *model::find<Plugin>(pluginId);
+ Plugin& plugin = *m_model.findShared<Plugin>(pluginId);
plugin.setBypass(!plugin.isBypassed());
}
/* -------------------------------------------------------------------------- */
-void runDispatchLoop()
+void PluginHost::giadaToJuceTempBuf(const mcl::AudioBuffer& outBuf)
+{
+ for (int i = 0; i < outBuf.countFrames(); i++)
+ for (int j = 0; j < outBuf.countChannels(); j++)
+ m_audioBuffer.setSample(j, i, outBuf[i][j]);
+}
+
+/* juceToGiadaOutBuf
+Converts buffer from Juce to Giada. A note for the future: if we overwrite (=)
+(as we do now) it's SEND, if we add (+) it's INSERT. */
+
+void PluginHost::juceToGiadaOutBuf(mcl::AudioBuffer& outBuf) const
{
- messageManager_->runDispatchLoopUntil(10);
+ for (int i = 0; i < outBuf.countFrames(); i++)
+ for (int j = 0; j < outBuf.countChannels(); j++)
+ outBuf[i][j] = m_audioBuffer.getSample(j, i);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void PluginHost::processPlugins(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events)
+{
+ for (Plugin* p : plugins)
+ {
+ if (!p->valid || p->isSuspended() || p->isBypassed())
+ continue;
+ p->process(m_audioBuffer, events);
+ }
+ events.clear();
}
-} // namespace giada::m::pluginHost
+} // namespace giada::m
#endif // #ifdef WITH_VST
#include "deps/juce-config.h"
#include <functional>
+namespace mcl
+{
+class AudioBuffer;
+}
+
namespace giada::m
{
class Plugin;
-class AudioBuffer;
-} // namespace giada::m
-namespace giada::m::pluginHost
+}
+
+namespace giada::m::model
{
-struct Info : public juce::AudioPlayHead
+class Model;
+}
+
+namespace giada::m
{
- bool getCurrentPosition(CurrentPositionInfo& result) override;
- bool canControlTransport() override;
-};
+class Sequencer;
+class PluginHost final
+{
+public:
+ class Info final : public juce::AudioPlayHead
+ {
+ public:
+ Info(const Sequencer&, int sampleRate);
-/* -------------------------------------------------------------------------- */
+ bool getCurrentPosition(CurrentPositionInfo& result) override;
+ bool canControlTransport() override;
-void init(int buffersize);
-void close();
+ private:
+ const Sequencer& m_sequencer;
+ int m_sampleRate;
+ };
-/* addPlugin
-Adds a new plugin to channel 'channelId'. */
+ PluginHost(model::Model&);
-void addPlugin(std::unique_ptr<Plugin> p, ID channelId);
+ /* reset
+ Brings everything back to the initial state. */
-/* processStack
-Applies the fx list to the buffer. */
+ void reset(int bufferSize);
-void processStack(AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
- juce::MidiBuffer* events = nullptr);
+ /* addPlugin
+ Loads a new plugin into memory. Returns a reference to the newly created
+ object. */
-/* swapPlugin
-Swaps plug-in 1 with plug-in 2 in Channel 'channelId'. */
+ const Plugin& addPlugin(std::unique_ptr<Plugin> p);
-void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, ID channelId);
+ /* processStack
+ Applies the fx list to the buffer. */
-/* freePlugin.
-Unloads plugin from channel 'channelId'. */
+ void processStack(mcl::AudioBuffer& outBuf, const std::vector<Plugin*>& plugins,
+ juce::MidiBuffer* events = nullptr);
-void freePlugin(const m::Plugin& plugin, ID channelId);
+ /* swapPlugin
+ Swaps plug-in 1 with plug-in 2 in the plug-in vector. */
-/* freePlugins
-Unloads multiple plugins. Useful when freeing or deleting a channel. */
+ void swapPlugin(const m::Plugin& p1, const m::Plugin& p2, std::vector<Plugin*>& plugins);
-void freePlugins(const std::vector<Plugin*>& plugins);
+ /* freePlugin.
+ Unloads plugin from memory. */
-/* clonePlugins
-Clones all the plug-ins in the 'plugins' vector. */
+ void freePlugin(const m::Plugin& plugin);
-std::vector<Plugin*> clonePlugins(const std::vector<Plugin*>& plugins);
+ /* freePlugins
+ Unloads multiple plugins. Useful when freeing or deleting a channel. */
-void setPluginParameter(ID pluginId, int paramIndex, float value);
-void setPluginProgram(ID pluginId, int programIndex);
-void toggleBypass(ID pluginId);
+ void freePlugins(const std::vector<Plugin*>& plugins);
-/* runDispatchLoop
-Wakes up plugins' GUI manager for N milliseconds. */
+ /* freeAllPlugins
+ Just deletes everything. */
-void runDispatchLoop();
-} // namespace giada::m::pluginHost
+ void freeAllPlugins();
+
+ void setPluginParameter(ID pluginId, int paramIndex, float value);
+ void setPluginProgram(ID pluginId, int programIndex);
+ void toggleBypass(ID pluginId);
+
+private:
+ void giadaToJuceTempBuf(const mcl::AudioBuffer& outBuf);
+
+ /* juceToGiadaOutBuf
+ Converts buffer from Juce to Giada. A note for the future: if we overwrite (=)
+ (as we do now) it's SEND, if we add (+) it's INSERT. */
+
+ void juceToGiadaOutBuf(mcl::AudioBuffer& outBuf) const;
+
+ void processPlugins(const std::vector<Plugin*>& plugins, juce::MidiBuffer& events);
+
+ model::Model& m_model;
+
+ juce::AudioBuffer<float> m_audioBuffer;
+};
+} // namespace giada::m
#endif
#ifdef WITH_VST
-#include "pluginManager.h"
-#include "core/conf.h"
+#include "core/plugins/pluginManager.h"
#include "core/const.h"
-#include "core/idManager.h"
#include "core/model/model.h"
#include "core/patch.h"
#include "core/plugins/plugin.h"
#include "utils/string.h"
#include <cassert>
-namespace giada::m::pluginManager
+namespace giada::m
{
-namespace
+void PluginManager::reset(SortMethod sortMethod)
{
-IdManager pluginId_;
+ m_pluginId = IdManager();
+ m_missingPlugins = false;
-int samplerate_;
-int buffersize_;
-
-/* formatManager
-Plugin format manager. */
-
-juce::AudioPluginFormatManager formatManager_;
-
-/* knownPuginList
-List of known (i.e. scanned) plugins. */
-
-juce::KnownPluginList knownPluginList_;
-
-/* unknownPluginList
-List of unrecognized plugins found in a patch. */
-
-std::vector<std::string> unknownPluginList_;
-
-/* missingPlugins
-If some plugins from any stack are missing. */
-
-bool missingPlugins_;
-
-std::unique_ptr<Plugin> makeInvalidPlugin_(const std::string& pid, ID id)
-{
- missingPlugins_ = true;
- unknownPluginList_.push_back(pid);
- return std::make_unique<Plugin>(pluginId_.generate(id), pid); // Invalid plug-in
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init(int samplerate, int buffersize)
-{
- pluginId_ = IdManager();
- samplerate_ = samplerate;
- buffersize_ = buffersize;
- missingPlugins_ = false;
-
- formatManager_.addDefaultFormats();
- unknownPluginList_.clear();
+ m_unknownPluginList.clear();
+ if (m_formatManager.getNumFormats() == 0) // Must be called only once
+ m_formatManager.addDefaultFormats();
loadList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
- sortPlugins(static_cast<pluginManager::SortMethod>(conf::conf.pluginSortMethod));
+ sortPlugins(sortMethod);
}
/* -------------------------------------------------------------------------- */
-int scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
+int PluginManager::scanDirs(const std::string& dirs, const std::function<void(float)>& cb)
{
u::log::print("[pluginManager::scanDir] requested directories: '%s'\n", dirs);
- u::log::print("[pluginManager::scanDir] current plugins: %d\n", knownPluginList_.getNumTypes());
+ u::log::print("[pluginManager::scanDir] current plug-in types: %d\n", m_knownPluginList.getNumTypes());
- knownPluginList_.clear(); // clear up previous plugins
+ m_knownPluginList.clear(); // clear up previous plugins
std::vector<std::string> dirVec = u::string::split(dirs, ";");
for (const std::string& dir : dirVec)
searchPath.add(juce::File(dir));
- for (int i = 0; i < formatManager_.getNumFormats(); i++)
+ for (int i = 0; i < m_formatManager.getNumFormats(); i++)
{
-
- juce::PluginDirectoryScanner scanner(knownPluginList_, *formatManager_.getFormat(i), searchPath,
+ juce::PluginDirectoryScanner scanner(m_knownPluginList, *m_formatManager.getFormat(i), searchPath,
/*recursive=*/true, juce::File());
juce::String name;
}
}
- u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", knownPluginList_.getNumTypes());
- return knownPluginList_.getNumTypes();
+ u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", m_knownPluginList.getNumTypes());
+ return m_knownPluginList.getNumTypes();
}
/* -------------------------------------------------------------------------- */
-bool saveList(const std::string& filepath)
+bool PluginManager::saveList(const std::string& filepath) const
{
- bool out = knownPluginList_.createXml()->writeTo(juce::File(filepath));
+ bool out = m_knownPluginList.createXml()->writeTo(juce::File(filepath));
if (!out)
u::log::print("[pluginManager::saveList] unable to save plugin list to %s\n", filepath);
return out;
/* -------------------------------------------------------------------------- */
-bool loadList(const std::string& filepath)
+bool PluginManager::loadList(const std::string& filepath)
{
std::unique_ptr<juce::XmlElement> elem(juce::XmlDocument::parse(juce::File(filepath)));
if (elem == nullptr)
return false;
- knownPluginList_.recreateFromXml(*elem);
+ m_knownPluginList.recreateFromXml(*elem);
return true;
}
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Plugin> makePlugin(const std::string& pid, ID id)
+std::unique_ptr<Plugin> PluginManager::makePlugin(const std::string& pid,
+ int sampleRate, int bufferSize, const Sequencer& sequencer, ID id)
{
/* Plug-in ID generator is updated anyway, as we store Plugin objects also
if they are in an invalid state. */
- pluginId_.set(id);
+ m_pluginId.set(id);
- const std::unique_ptr<juce::PluginDescription> pd = knownPluginList_.getTypeForIdentifierString(pid);
+ const std::unique_ptr<juce::PluginDescription> pd = m_knownPluginList.getTypeForIdentifierString(pid);
if (pd == nullptr)
{
u::log::print("[pluginManager::makePlugin] no plugin found with pid=%s!\n", pid);
- return makeInvalidPlugin_(pid, id);
+ return makeInvalidPlugin(pid, id);
}
juce::String error;
- std::unique_ptr<juce::AudioPluginInstance> pi = formatManager_.createPluginInstance(*pd, samplerate_, buffersize_, error);
+ std::unique_ptr<juce::AudioPluginInstance> pi = m_formatManager.createPluginInstance(*pd, sampleRate, bufferSize, error);
if (pi == nullptr)
{
u::log::print("[pluginManager::makePlugin] unable to create instance with pid=%s! Error: %s\n",
pid, error.toStdString());
- return makeInvalidPlugin_(pid, id);
+ return makeInvalidPlugin(pid, id);
}
u::log::print("[pluginManager::makePlugin] plugin instance with pid=%s created\n", pid);
- return std::make_unique<Plugin>(pluginId_.generate(id), std::move(pi), samplerate_, buffersize_);
+ return std::make_unique<Plugin>(
+ m_pluginId.generate(id),
+ std::move(pi),
+ std::make_unique<PluginHost::Info>(sequencer, sampleRate),
+ sampleRate, bufferSize);
}
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Plugin> makePlugin(int index)
+std::unique_ptr<Plugin> PluginManager::makePlugin(int index, int sampleRate,
+ int bufferSize, const Sequencer& sequencer)
{
- juce::PluginDescription pd = knownPluginList_.getTypes()[index];
+ juce::PluginDescription pd = m_knownPluginList.getTypes()[index];
- if (pd.uid == 0) // Invalid
+ if (pd.uniqueId == 0) // Invalid
return {};
u::log::print("[pluginManager::makePlugin] plugin found, uid=%s, name=%s...\n",
pd.createIdentifierString().toRawUTF8(), pd.name.toRawUTF8());
- return makePlugin(pd.createIdentifierString().toStdString());
+ return makePlugin(pd.createIdentifierString().toStdString(), sampleRate, bufferSize, sequencer);
}
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Plugin> makePlugin(const Plugin& src)
+std::unique_ptr<Plugin> PluginManager::makePlugin(const Plugin& src, int sampleRate,
+ int bufferSize, const Sequencer& sequencer)
{
- std::unique_ptr<Plugin> p = makePlugin(src.getUniqueId());
+ std::unique_ptr<Plugin> p = makePlugin(src.getUniqueId(), sampleRate, bufferSize, sequencer);
for (int i = 0; i < src.getNumParameters(); i++)
p->setParameter(i, src.getParameter(i));
/* -------------------------------------------------------------------------- */
-const patch::Plugin serializePlugin(const Plugin& p)
+const Patch::Plugin PluginManager::serializePlugin(const Plugin& p) const
{
- patch::Plugin pp;
+ Patch::Plugin pp;
pp.id = p.id;
pp.path = p.getUniqueId();
pp.bypass = p.isBypassed();
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Plugin> deserializePlugin(const patch::Plugin& p, patch::Version version)
+std::unique_ptr<Plugin> PluginManager::deserializePlugin(const Patch::Plugin& p,
+ Patch::Version version, int sampleRate, int bufferSize, const Sequencer& sequencer)
{
- std::unique_ptr<Plugin> plugin = makePlugin(p.path, p.id);
+ std::unique_ptr<Plugin> plugin = makePlugin(p.path, sampleRate, bufferSize, sequencer, p.id);
if (!plugin->valid)
return plugin; // Return invalid version
/* Fill plug-in parameters. */
plugin->setBypass(p.bypass);
- if (version < patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0
+ if (version < Patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0
for (unsigned j = 0; j < p.params.size(); j++)
plugin->setParameter(j, p.params.at(j));
else
/* -------------------------------------------------------------------------- */
-std::vector<Plugin*> hydratePlugins(std::vector<ID> pluginIds)
+std::vector<Plugin*> PluginManager::hydratePlugins(std::vector<ID> pluginIds, model::Model& model)
{
std::vector<Plugin*> out;
for (ID id : pluginIds)
{
- Plugin* plugin = model::find<Plugin>(id);
+ Plugin* plugin = model.findShared<Plugin>(id);
if (plugin != nullptr)
out.push_back(plugin);
}
/* -------------------------------------------------------------------------- */
-int countAvailablePlugins()
+int PluginManager::countAvailablePlugins() const
{
- return knownPluginList_.getNumTypes();
+ return m_knownPluginList.getNumTypes();
}
/* -------------------------------------------------------------------------- */
-int countUnknownPlugins()
+std::vector<PluginManager::PluginInfo> PluginManager::getPluginsInfo() const
{
- return unknownPluginList_.size();
-}
+ std::vector<PluginInfo> out;
-/* -------------------------------------------------------------------------- */
-
-PluginInfo getAvailablePluginInfo(int i)
-{
- juce::PluginDescription pd = knownPluginList_.getTypes()[i];
- PluginInfo pi;
- pi.uid = pd.fileOrIdentifier.toStdString();
- pi.name = pd.descriptiveName.toStdString();
- pi.category = pd.category.toStdString();
- pi.manufacturerName = pd.manufacturerName.toStdString();
- pi.format = pd.pluginFormatName.toStdString();
- pi.isInstrument = pd.isInstrument;
- return pi;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool hasMissingPlugins()
-{
- return missingPlugins_;
-}
+ for (int i = 0; i < m_knownPluginList.getNumTypes(); i++)
+ {
+ juce::PluginDescription pd = m_knownPluginList.getTypes()[i];
+ PluginInfo pi;
+
+ pi.uid = pd.fileOrIdentifier.toStdString();
+ pi.name = pd.descriptiveName.toStdString();
+ pi.category = pd.category.toStdString();
+ pi.manufacturerName = pd.manufacturerName.toStdString();
+ pi.format = pd.pluginFormatName.toStdString();
+ pi.isInstrument = pd.isInstrument;
+ pi.exists = m_formatManager.doesPluginStillExist(*m_knownPluginList.getTypeForFile(pi.uid));
+ pi.isKnown = true;
+ out.push_back(pi);
+ }
-/* -------------------------------------------------------------------------- */
+ for (const std::string& uid : m_unknownPluginList)
+ {
+ PluginInfo pi;
+ pi.uid = uid;
+ pi.isInstrument = false;
+ pi.exists = false;
+ pi.isKnown = false;
+ out.push_back(pi);
+ }
-std::string getUnknownPluginInfo(int i)
-{
- return unknownPluginList_.at(i);
+ return out;
}
/* -------------------------------------------------------------------------- */
-bool doesPluginExist(const std::string& pid)
+bool PluginManager::hasMissingPlugins() const
{
- return formatManager_.doesPluginStillExist(*knownPluginList_.getTypeForFile(pid));
+ return m_missingPlugins;
}
/* -------------------------------------------------------------------------- */
-void sortPlugins(SortMethod method)
+void PluginManager::sortPlugins(SortMethod method)
{
switch (method)
{
case SortMethod::NAME:
- knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true);
+ m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortAlphabetically, true);
break;
case SortMethod::CATEGORY:
- knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByCategory, true);
+ m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByCategory, true);
break;
case SortMethod::MANUFACTURER:
- knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true);
+ m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByManufacturer, true);
break;
case SortMethod::FORMAT:
- knownPluginList_.sort(juce::KnownPluginList::SortMethod::sortByFormat, true);
+ m_knownPluginList.sort(juce::KnownPluginList::SortMethod::sortByFormat, true);
break;
}
}
-} // namespace giada::m::pluginManager
+
+/* -------------------------------------------------------------------------- */
+
+std::unique_ptr<Plugin> PluginManager::makeInvalidPlugin(const std::string& pid, ID id)
+{
+ m_missingPlugins = true;
+ m_unknownPluginList.push_back(pid);
+ return std::make_unique<Plugin>(m_pluginId.generate(id), pid); // Invalid plug-in
+}
+} // namespace giada::m
#endif // #ifdef WITH_VST
#ifndef G_PLUGIN_MANAGER_H
#define G_PLUGIN_MANAGER_H
+#include "core/idManager.h"
+#include "core/patch.h"
#include "deps/juce-config.h"
#include "plugin.h"
struct Plugin;
struct Version;
} // namespace giada::m::patch
-namespace giada::m::pluginManager
-{
-enum class SortMethod : int
+
+namespace giada::m::model
{
- NAME = 0,
- CATEGORY,
- MANUFACTURER,
- FORMAT
-};
+class Model;
+}
-struct PluginInfo
+namespace giada::m
{
- std::string uid;
- std::string name;
- std::string category;
- std::string manufacturerName;
- std::string format;
- bool isInstrument;
-};
+class Sequencer;
+class PluginManager final
+{
+public:
+ enum class SortMethod : int
+ {
+ NAME = 0,
+ CATEGORY,
+ MANUFACTURER,
+ FORMAT
+ };
+
+ struct PluginInfo
+ {
+ std::string uid;
+ std::string name;
+ std::string category;
+ std::string manufacturerName;
+ std::string format;
+ bool isInstrument;
+ bool exists;
+ bool isKnown;
+ };
+
+ /* getPluginsInfo
+ Returns a vector of PluginInfo objects containing all plug-ins, known and
+ unknown, scanned so far. */
-void init(int samplerate, int buffersize);
+ std::vector<PluginInfo> getPluginsInfo() const;
-/* scanDirs
-Parses plugin directories (semicolon-separated) and store list in
-knownPluginList. The callback is called on each plugin found. Used to update the
-main window from the GUI thread. */
+ /* hasMissingPlugins
+ True if some plug-ins have been marked as missing during the initial scan. */
-int scanDirs(const std::string& paths, const std::function<void(float)>& cb);
+ bool hasMissingPlugins() const;
-/* (save|load)List
-(Save|Load) knownPluginList (in|from) an XML file. */
+ /* countAvailablePlugins
+ Returns how many plug-ins are ready and available for usage. */
-bool saveList(const std::string& path);
-bool loadList(const std::string& path);
+ int countAvailablePlugins() const;
-/* countAvailablePlugins
-Returns how many plug-ins are ready and available for usage. */
+ /* reset
+ Brings everything back to the initial state. */
-int countAvailablePlugins();
+ void reset(SortMethod);
-/* countUnknownPlugins
-Returns how many plug-ins are in a unknown/not-found state. */
+ /* scanDirs
+ Parses plugin directories (semicolon-separated) and store list in
+ knownPluginList. The callback is called on each plugin found. Used to update
+ the main window from the GUI thread. */
-int countUnknownPlugins();
+ int scanDirs(const std::string& paths, const std::function<void(float)>& cb);
-std::unique_ptr<Plugin> makePlugin(const std::string& pid, ID id = 0);
-std::unique_ptr<Plugin> makePlugin(int index);
-std::unique_ptr<Plugin> makePlugin(const Plugin& other);
+ /* (save|load)List
+ (Save|Load) knownPluginList (in|from) an XML file. */
-/* (de)serializePlugin
-Transforms patch data into a Plugin object and vice versa. */
+ bool saveList(const std::string& path) const;
+ bool loadList(const std::string& path);
-const patch::Plugin serializePlugin(const Plugin& p);
-std::unique_ptr<Plugin> deserializePlugin(const patch::Plugin& p, patch::Version version);
-std::vector<Plugin*> hydratePlugins(std::vector<ID> pluginIds);
+ std::unique_ptr<Plugin> makePlugin(const std::string& pid, int sampleRate, int bufferSize, const Sequencer&, ID id = 0);
+ std::unique_ptr<Plugin> makePlugin(int index, int sampleRate, int bufferSize, const Sequencer&);
+ std::unique_ptr<Plugin> makePlugin(const Plugin& other, int sampleRate, int bufferSize, const Sequencer&);
-/* getAvailablePluginInfo
-Returns the available plugin information (name, type, ...) given a plug-in
-index. */
+ /* (de)serializePlugin
+ Transforms patch data into a Plugin object and vice versa. */
-PluginInfo getAvailablePluginInfo(int index);
+ const Patch::Plugin serializePlugin(const Plugin& p) const;
+ std::unique_ptr<Plugin> deserializePlugin(const Patch::Plugin& p, Patch::Version version, int sampleRate, int bufferSize, const Sequencer&);
+ std::vector<Plugin*> hydratePlugins(std::vector<ID> pluginIds, model::Model& model);
-std::string getUnknownPluginInfo(int index);
+ void sortPlugins(SortMethod sortMethod);
-bool doesPluginExist(const std::string& pid);
+private:
+ std::unique_ptr<Plugin> makeInvalidPlugin(const std::string& pid, ID id);
-bool hasMissingPlugins();
+ IdManager m_pluginId;
-void sortPlugins(SortMethod sortMethod);
+ /* formatManager
+ Plugin format manager. */
-} // namespace giada::m::pluginManager
+ juce::AudioPluginFormatManager m_formatManager;
+
+ /* knownPuginList
+ List of known (i.e. scanned) plugins. */
+
+ juce::KnownPluginList m_knownPluginList;
+
+ /* unknownPluginList
+ List of unrecognized plugins found in a patch. */
+
+ std::vector<std::string> m_unknownPluginList;
+
+ /* missingPlugins
+ If some plugins from any stack are missing. */
+
+ bool m_missingPlugins;
+};
+} // namespace giada::m
#endif
* -------------------------------------------------------------------------- */
#include "quantizer.h"
-#include "core/clock.h"
#include <cassert>
namespace giada::m
bool hasBeenTriggered() const;
- private:
+private:
std::map<int, std::function<void(Frame)>> m_callbacks;
int m_performId = -1;
};
#include <array>
#include <atomic>
-namespace giada
-{
-namespace m
+namespace giada::m
{
/* Queue
Single producer, single consumer lock-free queue. */
}
Queue(const Queue&) = delete;
+ Queue(Queue&&) = delete;
+ Queue& operator=(const Queue&) = delete;
+ Queue& operator=(Queue&&) = delete;
bool pop(T& item)
{
return true;
}
- private:
+private:
std::size_t increment(std::size_t i) const
{
return (i + 1) % size;
std::atomic<std::size_t> m_head;
std::atomic<std::size_t> m_tail;
};
-} // namespace m
-} // namespace giada
+} // namespace giada::m
#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "core/recManager.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/kernelAudio.h"
-#include "core/midiDispatcher.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/sequencer.h"
-#include "core/types.h"
-#include "gui/dispatcher.h"
-
-namespace giada::m::recManager
-{
-namespace
-{
-bool isKernelReady_()
-{
- return kernelAudio::isReady();
-}
-
-bool canRec_()
-{
- return isKernelReady_() && kernelAudio::isInputEnabled();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void setRecordingAction_(bool v)
-{
- model::get().recorder.isRecordingAction = v;
- model::swap(model::SwapType::NONE);
-}
-
-void setRecordingInput_(bool v)
-{
- model::get().recorder.isRecordingInput = v;
- model::swap(model::SwapType::NONE);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool startActionRec_()
-{
- clock::setStatus(ClockStatus::RUNNING);
- sequencer::start();
- conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
- return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void startInputRec_()
-{
- /* Start recording from the current frame, not the beginning. */
- mixer::startInputRec(clock::getCurrentFrame());
- sequencer::start();
- conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-bool isRecording()
-{
- return isRecordingAction() || isRecordingInput();
-}
-
-bool isRecordingAction()
-{
- return model::get().recorder.isRecordingAction;
-}
-
-bool isRecordingInput()
-{
- return model::get().recorder.isRecordingInput;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void startActionRec(RecTriggerMode mode)
-{
- if (!isKernelReady_())
- return;
-
- if (mode == RecTriggerMode::NORMAL)
- {
- startActionRec_();
- setRecordingAction_(true);
- }
- else
- { // RecTriggerMode::SIGNAL
- clock::setStatus(ClockStatus::WAITING);
- clock::rewind();
- midiDispatcher::setSignalCallback(startActionRec_);
- v::dispatcher::setSignalCallback(startActionRec_);
- setRecordingAction_(true);
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void stopActionRec()
-{
- setRecordingAction_(false);
-
- /* If you stop the Action Recorder in SIGNAL mode before any actual
- recording: just clean up everything and return. */
-
- if (clock::getStatus() == ClockStatus::WAITING)
- {
- clock::setStatus(ClockStatus::STOPPED);
- midiDispatcher::setSignalCallback(nullptr);
- v::dispatcher::setSignalCallback(nullptr);
- return;
- }
-
- std::unordered_set<ID> channels = recorderHandler::consolidate();
-
- /* Enable reading actions for Channels that have just been filled with
- actions. Start reading right away, without checking whether
- conf::treatRecsAsLoops is enabled or not. Same thing for MIDI channels. */
-
- for (ID id : channels)
- {
- channel::Data& ch = model::get().getChannel(id);
- ch.state->readActions.store(true);
- if (ch.type == ChannelType::MIDI)
- ch.state->playStatus.store(ChannelStatus::PLAY);
- }
- model::swap(model::SwapType::HARD);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void toggleActionRec(RecTriggerMode m)
-{
- isRecordingAction() ? stopActionRec() : startActionRec(m);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool startInputRec(RecTriggerMode triggerMode, InputRecMode inputMode)
-{
- if (!canRec_() || !mh::hasInputRecordableChannels())
- return false;
-
- if (triggerMode == RecTriggerMode::SIGNAL || inputMode == InputRecMode::FREE)
- clock::rewind();
-
- if (inputMode == InputRecMode::FREE)
- mixer::setEndOfRecCallback([inputMode] { stopInputRec(inputMode); });
-
- if (triggerMode == RecTriggerMode::NORMAL)
- {
- startInputRec_();
- setRecordingInput_(true);
- G_DEBUG("Start input rec, NORMAL mode");
- }
- else
- {
- clock::setStatus(ClockStatus::WAITING);
- mixer::setSignalCallback([] {
- startInputRec_();
- setRecordingInput_(true);
- });
- G_DEBUG("Start input rec, SIGNAL mode");
- }
-
- return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void stopInputRec(InputRecMode recMode)
-{
- setRecordingInput_(false);
-
- Frame recordedFrames = mixer::stopInputRec();
-
- /* When recording in RIGID mode, the amount of recorded frames is always
- equal to the current loop length. */
-
- if (recMode == InputRecMode::RIGID)
- recordedFrames = clock::getFramesInLoop();
-
- G_DEBUG("Stop input rec, recordedFrames=" << recordedFrames);
-
- /* If you stop the Input Recorder in SIGNAL mode before any actual
- recording: just clean up everything and return. */
-
- if (clock::getStatus() == ClockStatus::WAITING)
- {
- clock::setStatus(ClockStatus::STOPPED);
- mixer::setSignalCallback(nullptr);
- return;
- }
-
- /* Finalize recordings. InputRecMode::FREE requires some adjustments. */
-
- mh::finalizeInputRec(recordedFrames);
-
- if (recMode == InputRecMode::FREE)
- {
- clock::rewind();
- clock::setBpm(clock::calcBpmFromRec(recordedFrames));
- mixer::setEndOfRecCallback(nullptr);
- refreshInputRecMode(); // Back to RIGID mode if necessary
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool toggleInputRec(RecTriggerMode m, InputRecMode i)
-{
- if (isRecordingInput())
- {
- stopInputRec(i);
- return true;
- }
- return startInputRec(m, i);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool canEnableRecOnSignal() { return !clock::isRunning(); }
-bool canEnableFreeInputRec() { return !mh::hasAudioData(); }
-
-void refreshInputRecMode()
-{
- if (!canEnableFreeInputRec())
- conf::conf.inputRecMode = InputRecMode::RIGID;
-}
-} // namespace giada::m::recManager
\ No newline at end of file
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_REC_MANAGER_H
-#define G_REC_MANAGER_H
-
-#include "core/types.h"
-
-namespace giada::m::recManager
-{
-bool isRecording();
-bool isRecordingAction();
-bool isRecordingInput();
-
-void startActionRec(RecTriggerMode);
-void stopActionRec();
-void toggleActionRec(RecTriggerMode);
-
-bool startInputRec(RecTriggerMode, InputRecMode);
-void stopInputRec(InputRecMode);
-bool toggleInputRec(RecTriggerMode, InputRecMode);
-
-/* canEnableRecOnSignal
-True if rec-on-signal can be enabled: can't set it while sequencer is running,
-in order to prevent mistakes while live recording. */
-
-bool canEnableRecOnSignal();
-
-/* canEnableFreeInputRec
-True if free loop-length can be enabled: Can't set it if there's already a
-filled Sample Channel in the current project. */
-
-bool canEnableFreeInputRec();
-
-/* refreshInputRecMode
-Makes sure the input rec mode stays the right one when a new Sample Channel is
-filled with data. See canEnableFreeInputRec() rationale. */
-
-void refreshInputRecMode();
-} // namespace giada::m::recManager
-
-#endif
* -------------------------------------------------------------------------- */
#include "core/recorder.h"
-#include "core/action.h"
-#include "core/idManager.h"
+#include "core/mixerHandler.h"
#include "core/model/model.h"
-#include "utils/log.h"
-#include <algorithm>
-#include <cassert>
-#include <memory>
+#include "core/sequencer.h"
+#include "core/types.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
-namespace giada::m::recorder
+namespace giada::m
{
-namespace
+Recorder::Recorder(model::Model& m, Sequencer& s, MixerHandler& mh)
+: m_model(m)
+, m_sequencer(s)
+, m_mixerHandler(mh)
{
-IdManager actionId_;
-
-/* -------------------------------------------------------------------------- */
-
-Action* findAction_(ActionMap& src, ID id)
-{
- for (auto& [frame, actions] : src)
- for (Action& a : actions)
- if (a.id == id)
- return &a;
- assert(false);
- return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* updateMapPointers_
-Updates all prev/next actions pointers into the action map. This is required
-after an action has been recorded, since pushing back new actions in a Action
-vector makes it reallocating the existing ones. */
-
-void updateMapPointers_(ActionMap& src)
-{
- for (auto& kv : src)
- {
- for (Action& action : kv.second)
- {
- if (action.nextId != 0)
- action.next = findAction_(src, action.nextId);
- if (action.prevId != 0)
- action.prev = findAction_(src, action.prevId);
- }
- }
}
/* -------------------------------------------------------------------------- */
-/* optimize
-Removes frames without actions. */
-
-void optimize_(ActionMap& map)
+bool Recorder::isRecording() const
{
- for (auto it = map.cbegin(); it != map.cend();)
- it->second.size() == 0 ? it = map.erase(it) : ++it;
+ return isRecordingAction() || isRecordingInput();
}
-/* -------------------------------------------------------------------------- */
-
-void removeIf_(std::function<bool(const Action&)> f)
+bool Recorder::isRecordingAction() const
{
- model::DataLock lock;
-
- ActionMap& map = model::getAll<model::Actions>();
- for (auto& [frame, actions] : map)
- actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
- optimize_(map);
- updateMapPointers_(map);
+ return m_model.get().recorder.a_isRecordingAction();
}
-/* -------------------------------------------------------------------------- */
-
-bool exists_(ID channelId, Frame frame, const MidiEvent& event, const ActionMap& target)
-{
- for (const auto& [_, actions] : target)
- for (const Action& a : actions)
- if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw())
- return true;
- return false;
-}
-
-bool exists_(ID channelId, Frame frame, const MidiEvent& event)
-{
- return exists_(channelId, frame, event, model::getAll<model::Actions>());
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
- actionId_ = IdManager();
- clearAll();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearAll()
-{
- model::DataLock lock;
- model::getAll<model::Actions>().clear();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearChannel(ID channelId)
-{
- removeIf_([=](const Action& a) { return a.channelId == channelId; });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearActions(ID channelId, int type)
+bool Recorder::isRecordingInput() const
{
- removeIf_([=](const Action& a) {
- return a.channelId == channelId && a.event.getStatus() == type;
- });
+ return m_model.get().recorder.a_isRecordingInput();
}
/* -------------------------------------------------------------------------- */
-void deleteAction(ID id)
+void Recorder::prepareActionRec(RecTriggerMode mode)
{
- removeIf_([=](const Action& a) { return a.id == id; });
-}
-
-void deleteAction(ID currId, ID nextId)
-{
- removeIf_([=](const Action& a) { return a.id == currId || a.id == nextId; });
+ if (mode == RecTriggerMode::NORMAL)
+ {
+ startActionRec();
+ m_sequencer.setStatus(SeqStatus::RUNNING);
+ G_DEBUG("Start action rec, NORMAL mode");
+ }
+ else
+ { // RecTriggerMode::SIGNAL
+ m_sequencer.setStatus(SeqStatus::WAITING);
+ G_DEBUG("Start action rec, SIGNAL mode (waiting for signal from Midi Dispatcher...)");
+ }
}
/* -------------------------------------------------------------------------- */
-void updateKeyFrames(std::function<Frame(Frame old)> f)
+void Recorder::stopActionRec(ActionRecorder& actionRecorder)
{
- recorder::ActionMap temp;
+ setRecordingAction(false);
- /* Copy all existing actions in local map by cloning them, with just a
- difference: they have a new frame value. */
+ /* If you stop the Action Recorder in SIGNAL mode before any actual
+ recording: just clean up everything and return. */
- for (const auto& [oldFrame, actions] : model::getAll<model::Actions>())
+ if (m_sequencer.getStatus() == SeqStatus::WAITING)
{
- Frame newFrame = f(oldFrame);
- for (const Action& a : actions)
- {
- Action copy = a;
- copy.frame = newFrame;
- temp[newFrame].push_back(copy);
- }
- G_DEBUG(oldFrame << " -> " << newFrame);
+ m_sequencer.setStatus(SeqStatus::STOPPED);
+ return;
}
- updateMapPointers_(temp);
+ std::unordered_set<ID> channels = actionRecorder.consolidate();
- model::DataLock lock;
- model::getAll<model::Actions>() = std::move(temp);
-}
+ /* Enable reading actions for Channels that have just been filled with
+ actions. Start reading right away, without checking whether
+ conf::treatRecsAsLoops is enabled or not. Same thing for MIDI channels. */
-/* -------------------------------------------------------------------------- */
-
-void updateEvent(ID id, MidiEvent e)
-{
- model::DataLock lock;
- findAction_(model::getAll<model::Actions>(), id)->event = e;
+ for (ID id : channels)
+ {
+ Channel& ch = m_model.get().getChannel(id);
+ ch.shared->readActions.store(true);
+ ch.shared->recStatus.store(ChannelStatus::PLAY);
+ if (ch.type == ChannelType::MIDI)
+ ch.shared->playStatus.store(ChannelStatus::PLAY);
+ }
+ m_model.swap(model::SwapType::HARD);
}
/* -------------------------------------------------------------------------- */
-void updateSiblings(ID id, ID prevId, ID nextId)
+bool Recorder::prepareInputRec(RecTriggerMode triggerMode, InputRecMode inputMode)
{
- model::DataLock lock;
-
- Action* pcurr = findAction_(model::getAll<model::Actions>(), id);
- Action* pprev = findAction_(model::getAll<model::Actions>(), prevId);
- Action* pnext = findAction_(model::getAll<model::Actions>(), nextId);
+ if (inputMode == InputRecMode::FREE)
+ m_sequencer.rewind();
- pcurr->prev = pprev;
- pcurr->prevId = pprev->id;
- pcurr->next = pnext;
- pcurr->nextId = pnext->id;
-
- if (pprev != nullptr)
+ if (triggerMode == RecTriggerMode::NORMAL)
{
- pprev->next = pcurr;
- pprev->nextId = pcurr->id;
+ startInputRec();
+ m_sequencer.setStatus(SeqStatus::RUNNING);
+ G_DEBUG("Start input rec, NORMAL mode");
}
- if (pnext != nullptr)
+ else
{
- pnext->prev = pcurr;
- pnext->prevId = pcurr->id;
+ m_sequencer.setStatus(SeqStatus::WAITING);
+ G_DEBUG("Start input rec, SIGNAL mode (waiting for signal from Mixer...)");
}
-}
-/* -------------------------------------------------------------------------- */
-
-bool hasActions(ID channelId, int type)
-{
- for (const auto& [frame, actions] : model::getAll<model::Actions>())
- for (const Action& a : actions)
- if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
- return true;
- return false;
+ return true;
}
/* -------------------------------------------------------------------------- */
-Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e)
+void Recorder::stopInputRec(InputRecMode recMode, int sampleRate)
{
- Action out{actionId_.generate(id), channelId, frame, e, -1, -1};
- actionId_.set(id);
- return out;
-}
+ setRecordingInput(false);
-Action makeAction(const patch::Action& a)
-{
- actionId_.set(a.id);
- return Action{a.id, a.channelId, a.frame, a.event, -1, -1, a.prevId,
- a.nextId};
-}
+ Frame recordedFrames = m_mixerHandler.stopInputRec();
-/* -------------------------------------------------------------------------- */
+ /* When recording in RIGID mode, the amount of recorded frames is always
+ equal to the current loop length. */
-Action rec(ID channelId, Frame frame, MidiEvent event)
-{
- /* Skip duplicates. */
+ if (recMode == InputRecMode::RIGID)
+ recordedFrames = m_sequencer.getFramesInLoop();
- if (exists_(channelId, frame, event))
- return {};
+ G_DEBUG("Stop input rec, recordedFrames=" << recordedFrames);
- Action a = makeAction(0, channelId, frame, event);
+ /* If you stop the Input Recorder in SIGNAL mode before any actual
+ recording: just clean up everything and return. */
- /* If key frame doesn't exist yet, the [] operator in std::map is smart
- enough to insert a new item first. No plug-in data for now. */
+ if (m_sequencer.getStatus() == SeqStatus::WAITING)
+ {
+ m_sequencer.setStatus(SeqStatus::STOPPED);
+ return;
+ }
- model::DataLock lock;
+ /* Finalize recordings. InputRecMode::FREE requires some adjustments. */
- model::getAll<model::Actions>()[frame].push_back(a);
- updateMapPointers_(model::getAll<model::Actions>());
+ m_mixerHandler.finalizeInputRec(recordedFrames, m_sequencer.getCurrentFrame());
- return a;
+ if (recMode == InputRecMode::FREE)
+ {
+ m_sequencer.rewind();
+ m_sequencer.setBpm(m_sequencer.calcBpmFromRec(recordedFrames, sampleRate), sampleRate);
+ }
}
/* -------------------------------------------------------------------------- */
-void rec(std::vector<Action>& actions)
-{
- if (actions.size() == 0)
- return;
-
- model::DataLock lock;
+bool Recorder::canEnableRecOnSignal() const { return !m_sequencer.isRunning(); }
+bool Recorder::canEnableFreeInputRec() const { return !m_mixerHandler.hasAudioData(); }
- ActionMap& map = model::getAll<model::Actions>();
+/* -------------------------------------------------------------------------- */
- for (const Action& a : actions)
- if (!exists_(a.channelId, a.frame, a.event, map))
- map[a.frame].push_back(a);
- updateMapPointers_(map);
+bool Recorder::canRecordActions() const
+{
+ return isRecordingAction() && m_sequencer.isRunning() && !isRecordingInput();
}
/* -------------------------------------------------------------------------- */
-void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2)
+void Recorder::setRecordingAction(bool v)
{
- model::DataLock lock;
-
- ActionMap& map = model::getAll<model::Actions>();
-
- map[f1].push_back(makeAction(0, channelId, f1, e1));
- map[f2].push_back(makeAction(0, channelId, f2, e2));
-
- Action* a1 = findAction_(map, map[f1].back().id);
- Action* a2 = findAction_(map, map[f2].back().id);
- a1->nextId = a2->id;
- a2->prevId = a1->id;
-
- updateMapPointers_(map);
+ m_model.get().recorder.a_setRecordingAction(v);
}
-/* -------------------------------------------------------------------------- */
-
-const std::vector<Action>* getActionsOnFrame(Frame frame)
+void Recorder::setRecordingInput(bool v)
{
- if (model::getAll<model::Actions>().count(frame) == 0)
- return nullptr;
- return &model::getAll<model::Actions>().at(frame);
+ m_model.get().recorder.a_setRecordingInput(v);
}
/* -------------------------------------------------------------------------- */
-Action getClosestAction(ID channelId, Frame f, int type)
+void Recorder::startActionRec()
{
- Action out = {};
- forEachAction([&](const Action& a) {
- if (a.event.getStatus() != type || a.channelId != channelId)
- return;
- if (!out.isValid() || (a.frame <= f && a.frame > out.frame))
- out = a;
- });
- return out;
+ setRecordingAction(true);
}
/* -------------------------------------------------------------------------- */
-std::vector<Action> getActionsOnChannel(ID channelId)
+void Recorder::startActionRecOnCallback()
{
- std::vector<Action> out;
- forEachAction([&](const Action& a) {
- if (a.channelId == channelId)
- out.push_back(a);
- });
- return out;
+ if (m_sequencer.getStatus() != SeqStatus::WAITING)
+ return;
+ startActionRec();
+ m_sequencer.setStatus(SeqStatus::RUNNING);
}
/* -------------------------------------------------------------------------- */
-void forEachAction(std::function<void(const Action&)> f)
+void Recorder::startInputRec()
{
- for (auto& [_, actions] : model::getAll<model::Actions>())
- for (const Action& action : actions)
- f(action);
+ /* Start recording from the current frame, not the beginning. */
+ m_mixerHandler.startInputRec(m_sequencer.getCurrentFrame());
+ setRecordingInput(true);
}
/* -------------------------------------------------------------------------- */
-ID getNewActionId()
+void Recorder::startInputRecOnCallback()
{
- return actionId_.generate();
+ if (m_sequencer.getStatus() != SeqStatus::WAITING)
+ return;
+ startInputRec();
+ m_sequencer.setStatus(SeqStatus::RUNNING);
}
-} // namespace giada::m::recorder
+} // namespace giada::m
\ No newline at end of file
*
* -------------------------------------------------------------------------- */
-#ifndef G_RECORDER_H
-#define G_RECORDER_H
+#ifndef G_REC_MANAGER_H
+#define G_REC_MANAGER_H
-#include "core/action.h"
-#include "core/midiEvent.h"
-#include "core/patch.h"
#include "core/types.h"
-#include <functional>
-#include <map>
-#include <memory>
-#include <vector>
-namespace giada::m::recorder
+namespace giada::m::model
{
-using ActionMap = std::map<Frame, std::vector<Action>>;
+class Model;
+}
-/* init
-Initializes the recorder: everything starts from here. */
-
-void init();
-
-/* clearAll
-Deletes all recorded actions. */
-
-void clearAll();
-
-/* clearChannel
-Clears all actions from a channel. */
-
-void clearChannel(ID channelId);
-
-/* clearActions
-Clears the actions by type from a channel. */
-
-void clearActions(ID channelId, int type);
-
-/* deleteAction (1)
-Deletes a specific action. */
-
-void deleteAction(ID id);
-
-/* deleteAction (2)
-Deletes a specific pair of actions. Useful for composite stuff (i.e. MIDI). */
-
-void deleteAction(ID currId, ID nextId);
-
-/* updateKeyFrames
-Update all the key frames in the internal map of actions, according to a lambda
-function 'f'. */
-
-void updateKeyFrames(std::function<Frame(Frame old)> f);
-
-/* updateEvent
-Changes the event in action 'a'. */
-
-void updateEvent(ID id, MidiEvent e);
-
-/* updateSiblings
-Changes previous and next actions in action with id 'id'. Mostly used for
-chained actions such as envelopes. */
-
-void updateSiblings(ID id, ID prevId, ID nextId);
-
-/* hasActions
-Checks if the channel has at least one action recorded. */
-
-bool hasActions(ID channelId, int type = 0);
-
-/* makeAction
-Makes a new action given some data. */
-
-Action makeAction(ID id, ID channelId, Frame frame, MidiEvent e);
-Action makeAction(const patch::Action& a);
-
-/* rec (1)
-Records an action and returns it. Used by the Action Editor. */
-
-Action rec(ID channelId, Frame frame, MidiEvent e);
-
-/* rec (2)
-Transfer a vector of actions into the current ActionMap. This is called by
-recordHandler when a live session is over and consolidation is required. */
-
-void rec(std::vector<Action>& actions);
-
-/* rec (3)
-Records two actions on channel 'channel'. Useful when recording composite
-actions in the Action Editor. */
+namespace giada::m
+{
+class ActionRecorder;
+class MixerHandler;
+class Sequencer;
+class Recorder final
+{
+public:
+ Recorder(model::Model&, Sequencer&, MixerHandler&);
-void rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2);
+ bool isRecording() const;
+ bool isRecordingAction() const;
+ bool isRecordingInput() const;
-/* forEachAction
-Applies a read-only callback on each action recorded. NEVER do anything inside
-the callback that might alter the ActionMap. */
+ /* canEnableRecOnSignal
+ True if rec-on-signal can be enabled: can't set it while sequencer is
+ running, in order to prevent mistakes while live recording. */
-void forEachAction(std::function<void(const Action&)> f);
+ bool canEnableRecOnSignal() const;
-/* getActionsOnFrame
-Returns a pointer to a vector of actions recorded on frame 'f', or nullptr if
-the frame has no actions. */
+ /* canEnableFreeInputRec
+ True if free loop-length can be enabled: Can't set it if there's already a
+ filled Sample Channel in the current project. */
-const std::vector<Action>* getActionsOnFrame(Frame f);
+ bool canEnableFreeInputRec() const;
-/* getActionsOnChannel
-Returns a vector of actions belonging to channel 'ch'. */
+ /* canRecordActions
+ True if actions are recordable right now. */
-std::vector<Action> getActionsOnChannel(ID channelId);
+ bool canRecordActions() const;
-/* getClosestAction
-Given a frame 'f' returns the closest action. */
+ void prepareActionRec(RecTriggerMode);
+ void startActionRec();
+ void startActionRecOnCallback();
+ void stopActionRec(ActionRecorder&);
-Action getClosestAction(ID channelId, Frame f, int type);
+ bool prepareInputRec(RecTriggerMode, InputRecMode);
+ void startInputRec();
+ void startInputRecOnCallback();
+ void stopInputRec(InputRecMode, int sampleRate);
-/* getNewActionId
-Returns a new action ID, internally generated. */
+private:
+ void setRecordingAction(bool v);
+ void setRecordingInput(bool v);
-ID getNewActionId();
-} // namespace giada::m::recorder
+ model::Model& m_model;
+ Sequencer& m_sequencer;
+ MixerHandler& m_mixerHandler;
+};
+} // namespace giada::m
#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "recorderHandler.h"
-#include "action.h"
-#include "clock.h"
-#include "const.h"
-#include "model/model.h"
-#include "patch.h"
-#include "recorder.h"
-#include "utils/log.h"
-#include "utils/ver.h"
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <unordered_map>
-
-namespace giada::m::recorderHandler
-{
-namespace
-{
-constexpr int MAX_LIVE_RECS_CHUNK = 128;
-
-std::vector<Action> recs_;
-
-/* -------------------------------------------------------------------------- */
-
-const Action* getActionPtrById_(int id, const recorder::ActionMap& source)
-{
- for (const auto& [_, actions] : source)
- for (const Action& action : actions)
- if (action.id == id)
- return &action;
- return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* areComposite_
-Composite: NOTE_ON + NOTE_OFF on the same note. */
-
-bool areComposite_(const Action& a1, const Action& a2)
-{
- return a1.event.getStatus() == MidiEvent::NOTE_ON &&
- a2.event.getStatus() == MidiEvent::NOTE_OFF &&
- a1.event.getNote() == a2.event.getNote() &&
- a1.channelId == a2.channelId;
-}
-
-/* -------------------------------------------------------------------------- */
-
-/* consolidate_
-Given an action 'a1' tries to find the matching NOTE_OFF. This algorithm must
-start searching from the element next to 'a1': since live actions are recorded
-in linear sequence, the potential partner of 'a1' always lies beyond a1 itself.
-Without this trick (i.e. if it loops from vector.begin() each time) the
-algorithm would end up matching wrong partners. */
-
-void consolidate_(const Action& a1, std::size_t i)
-{
- for (auto it = recs_.begin() + i; it != recs_.end(); ++it)
- {
-
- const Action& a2 = *it;
-
- if (!areComposite_(a1, a2))
- continue;
-
- const_cast<Action&>(a1).nextId = a2.id;
- const_cast<Action&>(a2).prevId = a1.id;
-
- break;
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void consolidate_()
-{
- for (auto it = recs_.begin(); it != recs_.end(); ++it)
- consolidate_(*it, it - recs_.begin()); // Pass current index
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void init()
-{
- recs_.reserve(MAX_LIVE_RECS_CHUNK);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool isBoundaryEnvelopeAction(const Action& a)
-{
- assert(a.prev != nullptr);
- assert(a.next != nullptr);
- return a.prev->frame > a.frame || a.next->frame < a.frame;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateBpm(float ratio, int quantizerStep)
-{
- if (ratio == 1.0f)
- return;
-
- recorder::updateKeyFrames([=](Frame old) {
- /* The division here cannot be precise. A new frame can be 44099 and the
- quantizer set to 44100. That would mean two recs completely useless. So we
- compute a reject value ('delta'): if it's lower than 6 frames the new frame
- is collapsed with a quantized frame. FIXME - maybe 6 frames are too low. */
- Frame frame = static_cast<Frame>(old * ratio);
- if (frame != 0)
- {
- Frame delta = quantizerStep % frame;
- if (delta > 0 && delta <= 6)
- frame = frame + delta;
- }
- return frame;
- });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateSamplerate(int systemRate, int patchRate)
-{
- if (systemRate == patchRate)
- return;
-
- float ratio = systemRate / (float)patchRate;
-
- recorder::updateKeyFrames([=](Frame old) { return floorf(old * ratio); });
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool cloneActions(ID channelId, ID newChannelId)
-{
- bool cloned = false;
- std::vector<Action> actions;
- std::unordered_map<ID, ID> map; // Action ID mapper, old -> new
-
- recorder::forEachAction([&](const Action& a) {
- if (a.channelId != channelId)
- return;
-
- ID newActionId = recorder::getNewActionId();
-
- map.insert({a.id, newActionId});
-
- Action clone(a);
- clone.id = newActionId;
- clone.channelId = newChannelId;
-
- actions.push_back(clone);
- cloned = true;
- });
-
- /* Update nextId and prevId relationships given the new action ID. */
-
- for (Action& a : actions)
- {
- if (a.prevId != 0)
- a.prevId = map.at(a.prevId);
- if (a.nextId != 0)
- a.nextId = map.at(a.nextId);
- }
-
- recorder::rec(actions);
-
- return cloned;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void liveRec(ID channelId, MidiEvent e, Frame globalFrame)
-{
- assert(e.isNoteOnOff()); // Can't record any other kind of events for now
-
- /* TODO - this might allocate on the MIDI thread */
- if (recs_.size() >= recs_.capacity())
- recs_.reserve(recs_.size() + MAX_LIVE_RECS_CHUNK);
-
- recs_.push_back(recorder::makeAction(recorder::getNewActionId(), channelId, globalFrame, e));
-}
-
-/* -------------------------------------------------------------------------- */
-
-std::unordered_set<ID> consolidate()
-{
- consolidate_();
- recorder::rec(recs_);
-
- std::unordered_set<ID> out;
- for (const Action& action : recs_)
- out.insert(action.channelId);
-
- recs_.clear();
- return out;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void clearAllActions()
-{
- for (channel::Data& ch : model::get().channels)
- ch.hasActions = false;
-
- model::swap(model::SwapType::HARD);
-
- recorder::clearAll();
-}
-
-/* -------------------------------------------------------------------------- */
-
-recorder::ActionMap deserializeActions(const std::vector<patch::Action>& pactions)
-{
- recorder::ActionMap out;
-
- /* First pass: add actions with no relationship, that is with no prev/next
- pointers filled in. */
-
- for (const patch::Action& paction : pactions)
- out[paction.frame].push_back(recorder::makeAction(paction));
-
- /* Second pass: fill in previous and next actions, if any. Is this the
- fastest/smartest way to do it? Maybe not. Optimizations are welcome. */
-
- for (const patch::Action& paction : pactions)
- {
- if (paction.nextId == 0 && paction.prevId == 0)
- continue;
- Action* curr = const_cast<Action*>(getActionPtrById_(paction.id, out));
- assert(curr != nullptr);
- if (paction.nextId != 0)
- {
- curr->next = getActionPtrById_(paction.nextId, out);
- assert(curr->next != nullptr);
- }
- if (paction.prevId != 0)
- {
- curr->prev = getActionPtrById_(paction.prevId, out);
- assert(curr->prev != nullptr);
- }
- }
-
- return out;
-}
-
-/* -------------------------------------------------------------------------- */
-
-std::vector<patch::Action> serializeActions(const recorder::ActionMap& actions)
-{
- std::vector<patch::Action> out;
- for (const auto& kv : actions)
- {
- for (const Action& a : kv.second)
- {
- out.push_back({
- a.id,
- a.channelId,
- a.frame,
- a.event.getRaw(),
- a.prevId,
- a.nextId,
- });
- }
- }
- return out;
-}
-} // namespace giada::m::recorderHandler
\ No newline at end of file
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_RECORDER_HANDLER_H
-#define G_RECORDER_HANDLER_H
-
-#include "core/midiEvent.h"
-#include "core/recorder.h"
-#include "core/types.h"
-#include <unordered_set>
-
-namespace giada::m::patch
-{
-struct Action;
-}
-namespace giada::m
-{
-struct Action;
-}
-namespace giada::m::recorderHandler
-{
-void init();
-
-bool isBoundaryEnvelopeAction(const Action& a);
-
-/* updateBpm
-Changes actions position by calculating the new bpm value. */
-
-void updateBpm(float ratio, int quantizerStep);
-
-/* updateSamplerate
-Changes actions position by taking in account the new samplerate. If
-f_system == f_patch nothing will change, otherwise the conversion is
-mandatory. */
-
-void updateSamplerate(int systemRate, int patchRate);
-
-/* cloneActions
-Clones actions in channel 'channelId', giving them a new channel ID. Returns
-whether any action has been cloned. */
-
-bool cloneActions(ID channelId, ID newChannelId);
-
-/* liveRec
-Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
-
-void liveRec(ID channelId, MidiEvent e, Frame global);
-
-/* consolidate
-Records all live actions. Returns a set of channels IDs that have been
-recorded. */
-
-std::unordered_set<ID> consolidate();
-
-/* clearAllActions
-Deletes all recorded actions. */
-
-void clearAllActions();
-
-/* (de)serializeActions
-Creates new Actions given the patch raw data and vice versa. */
-
-recorder::ActionMap deserializeActions(const std::vector<patch::Action>& as);
-std::vector<patch::Action> serializeActions(const recorder::ActionMap& as);
-} // namespace giada::m::recorderHandler
-
-#endif
*
* -------------------------------------------------------------------------- */
-#include "sequencer.h"
-#include "core/clock.h"
-#include "core/conf.h"
-#include "core/const.h"
+#include "core/sequencer.h"
+#include "core/actions/actionRecorder.h"
+#include "core/jackTransport.h"
#include "core/kernelAudio.h"
#include "core/metronome.h"
-#include "core/mixer.h"
#include "core/model/model.h"
#include "core/quantizer.h"
-#include "core/recManager.h"
+#include "core/synchronizer.h"
+#include "utils/log.h"
+#include "utils/math.h"
-namespace giada::m::sequencer
+namespace giada::m
{
namespace
{
constexpr int Q_ACTION_REWIND = 0;
+} // namespace
-/* eventBuffer_
-Buffer of events found in each block sent to channels for event parsing. This is
-filled during react(). */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
-EventBuffer eventBuffer_;
+Sequencer::Sequencer(model::Model& m, Synchronizer& s, JackTransport& j)
+: onAboutStart(nullptr)
+, onAboutStop(nullptr)
+, m_model(m)
+, m_synchronizer(s)
+, m_jackTransport(j)
+, m_quantizerStep(1)
+{
+ quantizer.schedule(Q_ACTION_REWIND, [this](Frame delta) { rewindQ(delta); });
+}
+/* -------------------------------------------------------------------------- */
-Metronome metronome_;
+bool Sequencer::canQuantize() const { return m_model.get().sequencer.canQuantize(); }
+bool Sequencer::isRunning() const { return m_model.get().sequencer.isRunning(); }
+bool Sequencer::isActive() const { return m_model.get().sequencer.isActive(); }
+bool Sequencer::isOnBar() const { return m_model.get().sequencer.a_isOnBar(); }
+bool Sequencer::isOnBeat() const { return m_model.get().sequencer.a_isOnBeat(); }
+bool Sequencer::isOnFirstBeat() const { return m_model.get().sequencer.a_isOnFirstBeat(); }
+float Sequencer::getBpm() const { return m_model.get().sequencer.bpm; }
+int Sequencer::getBeats() const { return m_model.get().sequencer.beats; }
+int Sequencer::getBars() const { return m_model.get().sequencer.bars; }
+int Sequencer::getCurrentBeat() const { return m_model.get().sequencer.a_getCurrentBeat(); }
+Frame Sequencer::getCurrentFrame() const { return m_model.get().sequencer.a_getCurrentFrame(); }
+Frame Sequencer::getCurrentFrameQuantized() const { return quantize(getCurrentFrame()); }
+float Sequencer::getCurrentSecond(int sampleRate) const { return getCurrentFrame() / static_cast<float>(sampleRate); }
+Frame Sequencer::getFramesInBar() const { return m_model.get().sequencer.framesInBar; }
+Frame Sequencer::getFramesInBeat() const { return m_model.get().sequencer.framesInBeat; }
+Frame Sequencer::getFramesInLoop() const { return m_model.get().sequencer.framesInLoop; }
+Frame Sequencer::getFramesInSeq() const { return m_model.get().sequencer.framesInSeq; }
+int Sequencer::getQuantizerValue() const { return m_model.get().sequencer.quantize; }
+int Sequencer::getQuantizerStep() const { return m_quantizerStep; }
+SeqStatus Sequencer::getStatus() const { return m_model.get().sequencer.status; }
/* -------------------------------------------------------------------------- */
-void rewindQ_(Frame delta)
+Frame Sequencer::getMaxFramesInLoop(int sampleRate) const
{
- clock::rewind();
- eventBuffer_.push_back({EventType::REWIND, 0, delta});
+ return (sampleRate * (60.0f / G_MIN_BPM)) * getBeats();
}
-} // namespace
/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
+
+float Sequencer::calcBpmFromRec(Frame recordedFrames, int sampleRate) const
+{
+ return (60.0f * getBeats()) / (recordedFrames / static_cast<float>(sampleRate));
+}
+
/* -------------------------------------------------------------------------- */
-Quantizer quantizer;
+Frame Sequencer::quantize(Frame f) const
+{
+ if (!canQuantize())
+ return f;
+ return u::math::quantize(f, m_quantizerStep) % getFramesInLoop(); // No overflow
+}
/* -------------------------------------------------------------------------- */
-void init()
+void Sequencer::reset(int sampleRate)
{
- quantizer.schedule(Q_ACTION_REWIND, rewindQ_);
- clock::rewind();
+ model::Sequencer& s = m_model.get().sequencer;
+
+ s.bars = G_DEFAULT_BARS;
+ s.beats = G_DEFAULT_BEATS;
+ s.bpm = G_DEFAULT_BPM;
+ s.quantize = G_DEFAULT_QUANTIZE;
+ recomputeFrames(sampleRate); // Model swap is done here, no need to call it twice
+ rewind();
}
/* -------------------------------------------------------------------------- */
-void react(const eventDispatcher::EventBuffer& events)
+void Sequencer::react(const EventDispatcher::EventBuffer& events)
{
- for (const eventDispatcher::Event& e : events)
+ for (const EventDispatcher::Event& e : events)
{
- if (e.type == eventDispatcher::EventType::SEQUENCER_START)
+ if (e.type == EventDispatcher::EventType::SEQUENCER_START)
{
- start();
+ if (!m_jackTransport.start())
+ rawStart();
break;
}
- if (e.type == eventDispatcher::EventType::SEQUENCER_STOP)
+ if (e.type == EventDispatcher::EventType::SEQUENCER_STOP)
{
- stop();
+ if (!m_jackTransport.stop())
+ rawStop();
break;
}
- if (e.type == eventDispatcher::EventType::SEQUENCER_REWIND)
+ if (e.type == EventDispatcher::EventType::SEQUENCER_REWIND)
{
- rewind();
+ if (!m_jackTransport.setPosition(0))
+ rawRewind();
break;
}
}
/* -------------------------------------------------------------------------- */
-const EventBuffer& advance(Frame bufferSize)
+const Sequencer::EventBuffer& Sequencer::advance(Frame bufferSize, const ActionRecorder& actionRecorder)
{
- eventBuffer_.clear();
+ m_eventBuffer.clear();
+
+ const model::Sequencer& sequencer = m_model.get().sequencer;
- const Frame start = clock::getCurrentFrame();
+ const Frame start = sequencer.a_getCurrentFrame();
const Frame end = start + bufferSize;
- const Frame framesInLoop = clock::getFramesInLoop();
- const Frame framesInBar = clock::getFramesInBar();
- const Frame framesInBeat = clock::getFramesInBeat();
+ const Frame framesInLoop = sequencer.framesInLoop;
+ const Frame framesInBar = sequencer.framesInBar;
+ const Frame framesInBeat = sequencer.framesInBeat;
+ const Frame nextFrame = end % framesInLoop;
+ const int nextBeat = nextFrame / framesInBeat;
+
+ /* Process events in the current block. */
for (Frame i = start, local = 0; i < end; i++, local++)
{
if (global == 0)
{
- eventBuffer_.push_back({EventType::FIRST_BEAT, global, local});
- metronome_.trigger(Metronome::Click::BEAT, local);
+ m_eventBuffer.push_back({EventType::FIRST_BEAT, global, local});
+ m_metronome.trigger(Metronome::Click::BEAT, local);
}
else if (global % framesInBar == 0)
{
- eventBuffer_.push_back({EventType::BAR, global, local});
- metronome_.trigger(Metronome::Click::BAR, local);
+ m_eventBuffer.push_back({EventType::BAR, global, local});
+ m_metronome.trigger(Metronome::Click::BAR, local);
}
else if (global % framesInBeat == 0)
{
- metronome_.trigger(Metronome::Click::BEAT, local);
+ m_metronome.trigger(Metronome::Click::BEAT, local);
}
- const std::vector<Action>* as = recorder::getActionsOnFrame(global);
+ const std::vector<Action>* as = actionRecorder.getActionsOnFrame(global);
if (as != nullptr)
- eventBuffer_.push_back({EventType::ACTIONS, global, local, as});
+ m_eventBuffer.push_back({EventType::ACTIONS, global, local, as});
}
- /* Advance clock and quantizer after the event parsing. */
- clock::advance(bufferSize);
- quantizer.advance(Range<Frame>(start, end), clock::getQuantizerStep());
+ /* Advance this and quantizer after the event parsing. */
+
+ sequencer.a_setCurrentFrame(nextFrame);
+ sequencer.a_setCurrentBeat(nextBeat);
+ quantizer.advance(Range<Frame>(start, end), getQuantizerStep());
- return eventBuffer_;
+ return m_eventBuffer;
}
/* -------------------------------------------------------------------------- */
-void render(AudioBuffer& outBuf)
+void Sequencer::render(mcl::AudioBuffer& outBuf)
{
- if (metronome_.running)
- metronome_.render(outBuf);
+ if (m_metronome.running)
+ m_metronome.render(outBuf);
}
/* -------------------------------------------------------------------------- */
-void rawStart()
+void Sequencer::rawStart()
{
- switch (clock::getStatus())
+ assert(onAboutStart != nullptr);
+
+ const SeqStatus status = getStatus();
+
+ onAboutStart(status);
+
+ switch (status)
{
- case ClockStatus::STOPPED:
- clock::setStatus(ClockStatus::RUNNING);
+ case SeqStatus::STOPPED:
+ setStatus(SeqStatus::RUNNING);
break;
- case ClockStatus::WAITING:
- clock::setStatus(ClockStatus::RUNNING);
- recManager::stopActionRec();
+ case SeqStatus::WAITING:
+ setStatus(SeqStatus::RUNNING);
break;
default:
break;
/* -------------------------------------------------------------------------- */
-void rawStop()
+void Sequencer::rawStop()
{
- clock::setStatus(ClockStatus::STOPPED);
+ assert(onAboutStop != nullptr);
- /* If recordings (both input and action) are active deactivate them, but
- store the takes. RecManager takes care of it. */
-
- if (recManager::isRecordingAction())
- recManager::stopActionRec();
- else if (recManager::isRecordingInput())
- recManager::stopInputRec(conf::conf.inputRecMode);
+ onAboutStop();
+ setStatus(SeqStatus::STOPPED);
}
/* -------------------------------------------------------------------------- */
-void rawRewind()
+void Sequencer::rawRewind()
{
- if (clock::canQuantize())
+ if (canQuantize())
quantizer.trigger(Q_ACTION_REWIND);
else
- rewindQ_(/*delta=*/0);
+ rewindQ(/*delta=*/0);
}
/* -------------------------------------------------------------------------- */
-void start()
+void Sequencer::rewind()
{
-#ifdef WITH_AUDIO_JACK
- if (kernelAudio::getAPI() == G_SYS_API_JACK)
- kernelAudio::jackStart();
- else
-#endif
- rawStart();
+ const model::Sequencer& c = m_model.get().sequencer;
+
+ c.a_setCurrentFrame(0);
+ c.a_setCurrentBeat(0);
}
/* -------------------------------------------------------------------------- */
-void stop()
+bool Sequencer::isMetronomeOn() const { return m_metronome.running; }
+void Sequencer::toggleMetronome() { m_metronome.running = !m_metronome.running; }
+void Sequencer::setMetronome(bool v) { m_metronome.running = v; }
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::rewindQ(Frame delta)
{
-#ifdef WITH_AUDIO_JACK
- if (kernelAudio::getAPI() == G_SYS_API_JACK)
- kernelAudio::jackStop();
- else
-#endif
- rawStop();
+ rewind();
+ m_eventBuffer.push_back({EventType::REWIND, 0, delta});
}
/* -------------------------------------------------------------------------- */
-void rewind()
+void Sequencer::recomputeFrames(int sampleRate)
{
-#ifdef WITH_AUDIO_JACK
- if (kernelAudio::getAPI() == G_SYS_API_JACK)
- kernelAudio::jackSetPosition(0);
- else
-#endif
- rawRewind();
+ model::Sequencer& s = m_model.get().sequencer;
+
+ s.framesInLoop = static_cast<int>((sampleRate * (60.0f / s.bpm)) * s.beats);
+ s.framesInBar = static_cast<int>(s.framesInLoop / (float)s.bars);
+ s.framesInBeat = static_cast<int>(s.framesInLoop / (float)s.beats);
+ s.framesInSeq = s.framesInBeat * G_MAX_BEATS;
+
+ if (s.quantize != 0)
+ m_quantizerStep = s.framesInBeat / s.quantize;
+
+ m_model.swap(model::SwapType::NONE);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setBpm(float b, int sampleRate)
+{
+ b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
+
+ /* If JACK is being used, let it handle the bpm change. */
+ if (!m_jackTransport.setBpm(b))
+ rawSetBpm(b, sampleRate);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::rawSetBpm(float v, int sampleRate)
+{
+ assert(onBpmChange != nullptr);
+
+ const float oldVal = m_model.get().sequencer.bpm;
+ const float newVal = v;
+
+ m_model.get().sequencer.bpm = newVal;
+ recomputeFrames(sampleRate);
+ m_model.swap(model::SwapType::HARD);
+
+ onBpmChange(oldVal, newVal, m_quantizerStep);
+
+ u::log::print("[clock::rawSetBpm] Bpm changed to %f\n", newVal);
+}
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setBeats(int newBeats, int newBars, int sampleRate)
+{
+ newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
+ newBars = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
+
+ m_model.get().sequencer.beats = newBeats;
+ m_model.get().sequencer.bars = newBars;
+ recomputeFrames(sampleRate);
+
+ m_model.swap(model::SwapType::HARD);
}
/* -------------------------------------------------------------------------- */
-bool isMetronomeOn() { return metronome_.running; }
-void toggleMetronome() { metronome_.running = !metronome_.running; }
-void setMetronome(bool v) { metronome_.running = v; }
-} // namespace giada::m::sequencer
\ No newline at end of file
+void Sequencer::setQuantize(int q, int sampleRate)
+{
+ m_model.get().sequencer.quantize = q;
+ recomputeFrames(sampleRate);
+
+ m_model.swap(model::SwapType::HARD);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Sequencer::setStatus(SeqStatus s)
+{
+ m_model.get().sequencer.status = s;
+ m_model.swap(model::SwapType::SOFT);
+
+ /* Additional things to do when the status changes. */
+
+ switch (s)
+ {
+ case SeqStatus::WAITING:
+ rewind();
+ m_synchronizer.sendMIDIrewind();
+ break;
+ case SeqStatus::STOPPED:
+ m_synchronizer.sendMIDIstop();
+ break;
+ case SeqStatus::RUNNING:
+ m_synchronizer.sendMIDIstart();
+ break;
+ default:
+ break;
+ }
+}
+} // namespace giada::m
\ No newline at end of file
#define G_SEQUENCER_H
#include "core/eventDispatcher.h"
+#include "core/metronome.h"
#include "core/quantizer.h"
#include <vector>
-namespace giada::m
+namespace mcl
{
class AudioBuffer;
}
-namespace giada::m::sequencer
-{
-enum class EventType
+
+namespace giada::m::model
{
- NONE,
- FIRST_BEAT,
- BAR,
- REWIND,
- ACTIONS
-};
+class Model;
+}
-struct Event
+namespace giada::m
{
- EventType type = EventType::NONE;
- Frame global = 0;
- Frame delta = 0;
- const std::vector<Action>* actions = nullptr;
-};
+class JackTransport;
+class ActionRecorder;
+class Synchronizer;
+class Sequencer final
+{
+public:
+ enum class EventType
+ {
+ NONE,
+ FIRST_BEAT,
+ BAR,
+ REWIND,
+ ACTIONS
+ };
+
+ struct Event
+ {
+ EventType type = EventType::NONE;
+ Frame global = 0;
+ Frame delta = 0;
+ const std::vector<Action>* actions = nullptr;
+ };
+
+ using EventBuffer = RingBuffer<Event, G_MAX_SEQUENCER_EVENTS>;
+
+ Sequencer(model::Model&, Synchronizer&, JackTransport&);
+
+ /* canQuantize
+ Tells whether the quantizer value is > 0 and the sequencer is running. */
+
+ bool canQuantize() const;
+
+ /* isRunning
+ When sequencer is actually moving forward, i.e. SeqStatus == RUNNING. */
+
+ bool isRunning() const;
+
+ /* isActive
+ Sequencer is enabled, but might be in wait mode, i.e. SeqStatus == RUNNING or
+ SeqStatus == WAITING. */
+
+ bool isActive() const;
+
+ bool isOnBeat() const;
+ bool isOnBar() const;
+ bool isOnFirstBeat() const;
+ bool isMetronomeOn() const;
+
+ float getBpm() const;
+ int getBeats() const;
+ int getBars() const;
+ int getCurrentBeat() const;
+ Frame getCurrentFrame() const;
+ Frame getCurrentFrameQuantized() const;
+ float getCurrentSecond(int sampleRate) const;
+ Frame getFramesInBar() const;
+ Frame getFramesInBeat() const;
+ Frame getFramesInLoop() const;
+ Frame getFramesInSeq() const;
+ int getQuantizerValue() const;
+ int getQuantizerStep() const;
+ SeqStatus getStatus() const;
-using EventBuffer = RingBuffer<Event, G_MAX_SEQUENCER_EVENTS>;
+ /* getMaxFramesInLoop
+ Returns how many frames the current loop length might contain at the slowest
+ speed possible (G_MIN_BPM). Call this whenever you change the number or
+ beats. */
-/* quantizer
-Used by the sequencer itself and each sample channel. */
+ Frame getMaxFramesInLoop(int sampleRate) const;
-extern Quantizer quantizer;
+ /* calcBpmFromRec
+ Given the amount of recorded frames, returns the speed of the current
+ performance. Used while input recording in FREE mode. */
-void init();
+ float calcBpmFromRec(Frame recordedFrames, int sampleRate) const;
-/* react
-Reacts to live events coming from the EventDispatcher (human events). */
+ /* quantize
+ Quantizes the frame 'f'. */
-void react(const eventDispatcher::EventBuffer& e);
+ Frame quantize(Frame f) const;
-/* advance
-Parses sequencer events that might occur in a block and advances the internal
-quantizer. Returns a reference to the internal EventBuffer filled with events
-(if any). Call this on each new audio block. */
+ /* reset
+ Brings everything back to the initial state. */
-const EventBuffer& advance(Frame bufferSize);
+ void reset(int sampleRate);
-/* render
-Renders audio coming out from the sequencer: that is, the metronome! */
+ /* react
+ Reacts to live events coming from the EventDispatcher (human events). */
-void render(AudioBuffer& outBuf);
+ void react(const EventDispatcher::EventBuffer&);
-/* raw[*]
-Raw functions to start, stop and rewind the sequencer. These functions must be
-called only by clock:: when the JACK signal is received. Other modules should
-use the non-raw versions below. */
+ /* advance
+ Parses sequencer events that might occur in a block and advances the internal
+ quantizer. Returns a reference to the internal EventBuffer filled with events
+ (if any). Call this on each new audio block. */
-void rawStart();
-void rawStop();
-void rawRewind();
+ const EventBuffer& advance(Frame bufferSize, const ActionRecorder&);
-void start();
-void stop();
-void rewind();
+ /* render
+ Renders audio coming out from the sequencer: that is, the metronome! */
-bool isMetronomeOn();
-void toggleMetronome();
-void setMetronome(bool v);
-} // namespace giada::m::sequencer
+ void render(mcl::AudioBuffer& outBuf);
+
+ /* raw[*]
+ Raw functions to start, stop and rewind the sequencer. These functions must
+ be called only when the JACK signal is received. Other modules should send
+ a SEQUENCER_* event to the Event Dispatcher. */
+
+ void rawStart();
+ void rawStop();
+ void rawRewind();
+
+ /* rawSetBpm
+ Raw function to set the bpm, bypassing any JACK instruction. This function
+ must be called only by the Synchronizer when the JACK signal is received.
+ Other modules should use the non-raw version below. */
+
+ void rawSetBpm(float v, int sampleRate);
+
+ void rewind();
+ void toggleMetronome();
+ void setMetronome(bool v);
+ void setBpm(float b, int sampleRate);
+ void setBeats(int beats, int bars, int sampleRate);
+ void setQuantize(int q, int sampleRate);
+ void setStatus(SeqStatus);
+
+ /* recomputeFrames
+ Updates bpm, frames, beats and so on. */
+
+ void recomputeFrames(int sampleRate);
+
+ /* quantizer
+ Used by the sequencer itself and each sample channel. */
+
+ Quantizer quantizer;
+
+ std::function<void(SeqStatus)> onAboutStart;
+ std::function<void()> onAboutStop;
+ std::function<void(float, float, int)> onBpmChange;
+
+private:
+ /* rewindQ
+ Rewinds sequencer, quantized mode. */
+
+ void rewindQ(Frame delta);
+
+ model::Model& m_model;
+ Synchronizer& m_synchronizer;
+ JackTransport& m_jackTransport;
+
+ /* m_eventBuffer
+ Buffer of events found in each block sent to channels for event parsing.
+ This is filled during react(). */
+
+ EventBuffer m_eventBuffer;
+
+ Metronome m_metronome;
+
+ /* m_quantizerStep
+ Tells how many frames to wait to perform a quantized action. */
+
+ int m_quantizerStep;
+};
+} // namespace giada::m
#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_SWAPPER_H
-#define G_SWAPPER_H
-
-#include <atomic>
-#include <functional>
-
-namespace giada
-{
-/* Swapper
-A template class that performs atomic double buffering on type T. */
-
-template <typename T>
-class Swapper
-{
-public:
- class RtLock
- {
- friend Swapper;
-
- public:
- RtLock(Swapper& s)
- : m_swapper(s)
- {
- m_swapper.rt_lock();
- }
-
- ~RtLock()
- {
- m_swapper.rt_unlock();
- }
-
- const T& get() const
- {
- return m_swapper.rt_get();
- }
-
- private:
- Swapper& m_swapper;
- };
-
- Swapper()
- {
- static_assert(std::is_assignable_v<T, T>);
- }
-
- /* get
- Returns local data for non-realtime thread. */
-
- T& get()
- {
- return m_data[(m_bits.load() & BIT_INDEX) ^ 1];
- }
-
- void swap()
- {
- int bits = m_bits.load();
-
- /* Wait for the audio thread to finish, i.e. until the BUSY bit becomes
- zero. Only then, swap indexes. This will let the audio thread to pick
- the updated data on its next cycle. */
- int desired;
- do
- {
- bits = bits & ~BIT_BUSY; // Expected: current value without busy bit set
- desired = (bits ^ BIT_INDEX) & BIT_INDEX; // Desired: flipped (xor) index
- } while (!m_bits.compare_exchange_weak(bits, desired));
-
- bits = desired;
-
- /* After the swap above, m_data[(bits & BIT_INDEX) ^ 1] has become the
- non-realtime slot and it points to the data previously read by the
- realtime thread. That data is old, so update it: overwrite it with the
- realtime data in the realtime slot (m_data[bits & BIT_INDEX]) that is
- currently being read by the realtime thread. */
- m_data[(bits & BIT_INDEX) ^ 1] = m_data[bits & BIT_INDEX];
- }
-
- bool isLocked()
- {
- return m_bits.load() & BIT_BUSY;
- }
-
-private:
- static constexpr int BIT_INDEX = (1 << 0); // 0001
- static constexpr int BIT_BUSY = (1 << 1); // 0010
-
- /* [realtime] lock. */
-
- void rt_lock()
- {
- /* Set the busy bit and also get the current index. */
- m_index = m_bits.fetch_or(BIT_BUSY) & BIT_INDEX;
- }
-
- /* [realtime] unlock. */
-
- void rt_unlock()
- {
- m_bits.store(m_index & BIT_INDEX);
- }
-
- /* [realtime] Get data currently being ready by the rt thread. */
-
- const T& rt_get() const
- {
- return m_data[m_bits.load() & BIT_INDEX];
- }
-
- std::array<T, 2> m_data;
- std::atomic<int> m_bits{0};
- int m_index{0};
-};
-} // namespace giada
-
-#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "core/synchronizer.h"
+#include "core/conf.h"
+#include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/model/model.h"
+
+namespace giada::m
+{
+Synchronizer::Synchronizer(const Conf::Data& c, KernelMidi& k)
+#ifdef WITH_AUDIO_JACK
+: onJackRewind(nullptr)
+, onJackChangeBpm(nullptr)
+, onJackStart(nullptr)
+, onJackStop(nullptr)
+, m_kernelMidi(k)
+, m_conf(c)
+#else
+: m_kernelMidi(k)
+, m_conf(c)
+#endif
+{
+ reset();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::reset()
+{
+ m_midiTCrate = static_cast<int>((m_conf.samplerate / m_conf.midiTCfps) * G_MAX_IO_CHANS); // stereo values
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIsync(const model::Sequencer& sequencer)
+{
+ /* Sending MIDI sync while waiting is meaningless. */
+
+ if (sequencer.status == SeqStatus::WAITING)
+ return;
+
+ const int currentFrame = sequencer.a_getCurrentFrame();
+
+ /* TODO - only Master (_M) is implemented so far. */
+
+ if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+ {
+ if (currentFrame % (sequencer.framesInBeat / 24) == 0)
+ m_kernelMidi.send(MIDI_CLOCK, -1, -1);
+ return;
+ }
+
+ if (m_conf.midiSync == G_MIDI_SYNC_MTC_M)
+ {
+
+ /* check if a new timecode frame has passed. If so, send MIDI TC
+ * quarter frames. 8 quarter frames, divided in two branches:
+ * 1-4 and 5-8. We check timecode frame's parity: if even, send
+ * range 1-4, if odd send 5-8. */
+
+ if (currentFrame % m_midiTCrate != 0) // no timecode frame passed
+ return;
+
+ /* frame low nibble
+ * frame high nibble
+ * seconds low nibble
+ * seconds high nibble */
+
+ if (m_midiTCframes % 2 == 0)
+ {
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes & 0x0F) | 0x00, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes >> 4) | 0x10, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds & 0x0F) | 0x20, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds >> 4) | 0x30, -1);
+ }
+
+ /* minutes low nibble
+ * minutes high nibble
+ * hours low nibble
+ * hours high nibble SMPTE frame rate */
+
+ else
+ {
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes & 0x0F) | 0x40, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes >> 4) | 0x50, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours & 0x0F) | 0x60, -1);
+ m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours >> 4) | 0x70, -1);
+ }
+
+ m_midiTCframes++;
+
+ /* check if total timecode frames are greater than timecode fps:
+ * if so, a second has passed */
+
+ if (m_midiTCframes > m_conf.midiTCfps)
+ {
+ m_midiTCframes = 0;
+ m_midiTCseconds++;
+ if (m_midiTCseconds >= 60)
+ {
+ m_midiTCminutes++;
+ m_midiTCseconds = 0;
+ if (m_midiTCminutes >= 60)
+ {
+ m_midiTChours++;
+ m_midiTCminutes = 0;
+ }
+ }
+ //u::log::print("%d:%d:%d:%d\n", m_midiTChours, m_midiTCminutes, m_midiTCseconds, m_midiTCframes);
+ }
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIrewind()
+{
+ m_midiTCframes = 0;
+ m_midiTCseconds = 0;
+ m_midiTCminutes = 0;
+ m_midiTChours = 0;
+
+ /* For cueing the slave to a particular start point, Quarter Frame messages
+ are not used. Instead, an MTC Full Frame message should be sent. The Full
+ Frame is a SysEx message that encodes the entire SMPTE time in one message. */
+
+ if (m_conf.midiSync == G_MIDI_SYNC_MTC_M)
+ {
+ m_kernelMidi.send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0
+ m_kernelMidi.send(0x01, 0x01, 0x00); // hours 0
+ m_kernelMidi.send(0x00, 0x00, 0x00); // mins, secs, frames 0
+ m_kernelMidi.send(MIDI_EOX, -1, -1); // end of sysex
+ }
+ else if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+ m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIstart()
+{
+ if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+ {
+ m_kernelMidi.send(MIDI_START, -1, -1);
+ m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0);
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Synchronizer::sendMIDIstop()
+{
+ if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M)
+ m_kernelMidi.send(MIDI_STOP, -1, -1);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_AUDIO_JACK
+
+void Synchronizer::recvJackSync(const JackTransport::State& state)
+{
+ assert(onJackRewind != nullptr);
+ assert(onJackChangeBpm != nullptr);
+ assert(onJackStart != nullptr);
+ assert(onJackStop != nullptr);
+
+ JackTransport::State jackStateCurr = state;
+
+ if (jackStateCurr != m_jackStatePrev)
+ {
+ if (jackStateCurr.frame != m_jackStatePrev.frame && jackStateCurr.frame == 0)
+ {
+ G_DEBUG("JackState received - rewind to frame 0");
+ onJackRewind();
+ }
+
+ // jackStateCurr.bpm == 0 if JACK doesn't send that info
+ if (jackStateCurr.bpm != m_jackStatePrev.bpm && jackStateCurr.bpm > 1.0f)
+ {
+ G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
+ onJackChangeBpm(jackStateCurr.bpm);
+ }
+
+ if (jackStateCurr.running != m_jackStatePrev.running)
+ {
+ G_DEBUG("JackState received - running=" << jackStateCurr.running);
+ jackStateCurr.running ? onJackStart() : onJackStop();
+ }
+ }
+
+ m_jackStatePrev = jackStateCurr;
+}
+
+#endif
+} // namespace giada::m
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_SYNC_H
+#define G_SYNC_H
+
+#ifdef WITH_AUDIO_JACK
+#include "core/jackTransport.h"
+#endif
+#include "core/types.h"
+#include "core/conf.h"
+#include <functional>
+
+namespace giada::m::kernelAudio
+{
+struct JackState;
+}
+
+namespace giada::m::model
+{
+class Sequencer;
+}
+
+namespace giada::m
+{
+class KernelMidi;
+class Synchronizer final
+{
+public:
+ Synchronizer(const Conf::Data&, KernelMidi&);
+
+ /* reset
+ Brings everything back to the initial state. */
+
+ void reset();
+
+ /* sendMIDIsync
+ Generates MIDI sync output data. */
+
+ void sendMIDIsync(const model::Sequencer& clock);
+
+ /* sendMIDIrewind
+ Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */
+
+ void sendMIDIrewind();
+
+ void sendMIDIstart();
+ void sendMIDIstop();
+
+#ifdef WITH_AUDIO_JACK
+
+ /* recvJackSync
+ Receives a new JACK state. Called by Kernel Audio on each audio block. */
+
+ void recvJackSync(const JackTransport::State& state);
+
+ /* onJack[...]
+ Callbacks called when something happens in the JACK state. */
+
+ std::function<void()> onJackRewind;
+ std::function<void(float)> onJackChangeBpm;
+ std::function<void()> onJackStart;
+ std::function<void()> onJackStop;
+#endif
+
+private:
+ /* midiTC*
+ MIDI timecode variables. */
+
+ int m_midiTCrate = 0; // Send MTC data every m_midiTCrate frames
+ int m_midiTCframes = 0;
+ int m_midiTCseconds = 0;
+ int m_midiTCminutes = 0;
+ int m_midiTChours = 0;
+
+#ifdef WITH_AUDIO_JACK
+
+ JackTransport::State m_jackStatePrev;
+
+#endif
+
+ KernelMidi& m_kernelMidi;
+ const Conf::Data& m_conf;
+};
+} // namespace giada::m
+
+#endif
#ifdef _WIN32
#undef VOID
#endif
-enum class ClockStatus
+enum class SeqStatus
{
STOPPED,
WAITING,
SINGLE_BASIC,
SINGLE_PRESS,
SINGLE_RETRIG,
- SINGLE_ENDLESS
+ SINGLE_ENDLESS,
+ SINGLE_BASIC_PAUSE
};
enum class RecTriggerMode : int
AUTO = 0,
MANUAL
};
+
+/* Peak
+Audio peak information for two In/Out channels. */
+
+struct Peak
+{
+ float left;
+ float right;
+};
} // namespace giada
#endif
/* -------------------------------------------------------------------------- */
-AudioBuffer& Wave::getBuffer() { return m_buffer; }
-const AudioBuffer& Wave::getBuffer() const { return m_buffer; }
+mcl::AudioBuffer& Wave::getBuffer() { return m_buffer; }
+const mcl::AudioBuffer& Wave::getBuffer() const { return m_buffer; }
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void Wave::replaceData(AudioBuffer&& b)
+void Wave::replaceData(mcl::AudioBuffer&& b)
{
m_buffer = std::move(b);
}
#ifndef G_WAVE_H
#define G_WAVE_H
-#include "core/audioBuffer.h"
#include "core/types.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include <string>
namespace giada::m
/* getBuffer
Returns a (non-)const reference to the underlying audio buffer. */
- AudioBuffer& getBuffer();
- const AudioBuffer& getBuffer() const;
+ mcl::AudioBuffer& getBuffer();
+ const mcl::AudioBuffer& getBuffer() const;
/* setPath
Sets new path 'p'. If 'id' != -1 inserts a numeric id next to the file
/* replaceData
Replaces internal audio buffer with 'b' by moving it. */
- void replaceData(AudioBuffer&& b);
+ void replaceData(mcl::AudioBuffer&& b);
void alloc(Frame size, int channels, int rate, int bits, const std::string& path);
ID id;
private:
- AudioBuffer m_buffer;
- int m_rate;
- int m_bits;
- bool m_logical; // memory only (a take)
- bool m_edited; // edited via editor
- std::string m_path; // E.g. /path/to/my/sample.wav
+ mcl::AudioBuffer m_buffer;
+ int m_rate;
+ int m_bits;
+ bool m_logical; // memory only (a take)
+ bool m_edited; // edited via editor
+ std::string m_path; // E.g. /path/to/my/sample.wav
};
} // namespace giada::m
#include "waveFx.h"
#include "const.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include "utils/log.h"
#include "wave.h"
#include <algorithm>
if (w.getBuffer().countChannels() >= G_MAX_IO_CHANS)
return G_RES_OK;
- AudioBuffer newData;
+ mcl::AudioBuffer newData;
newData.alloc(w.getBuffer().countFrames(), G_MAX_IO_CHANS);
for (int i = 0; i < newData.countFrames(); i++)
int newSize = w.getBuffer().countFrames() - (b - a);
- AudioBuffer newData;
+ mcl::AudioBuffer newData;
newData.alloc(newSize, w.getBuffer().countChannels());
u::log::print("[wfx::cut] cutting from %d to %d\n", a, b);
Frame newSize = b - a;
- AudioBuffer newData;
+ mcl::AudioBuffer newData;
newData.alloc(newSize, w.getBuffer().countChannels());
u::log::print("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b - a);
{
assert(src.getBuffer().countChannels() == des.getBuffer().countChannels());
- AudioBuffer newData;
+ mcl::AudioBuffer newData;
newData.alloc(src.getBuffer().countFrames() + des.getBuffer().countFrames(), des.getBuffer().countChannels());
/* |---original data---|///paste data///|---original data---|
{
class Wave;
}
+
namespace giada::m::wfx
{
/* Windows fix */
#include "waveManager.h"
#include "const.h"
+#include "deps/mcl-audio-buffer/src/audioBuffer.hpp"
#include "idManager.h"
-#include "model/model.h"
#include "patch.h"
#include "utils/fs.h"
#include "utils/log.h"
#include <samplerate.h>
#include <sndfile.h>
-namespace giada::m::waveManager
+namespace giada::m
{
namespace
{
-IdManager waveId_;
-
-/* -------------------------------------------------------------------------- */
-
int getBits_(const SF_INFO& header)
{
if (header.format & SF_FORMAT_PCM_S8)
return 64;
return 0;
}
+
+/* -------------------------------------------------------------------------- */
+
+std::string makeWavePath_(const std::string& base, const m::Wave& w, int k)
+{
+ return base + G_SLASH + w.getBasename(/*ext=*/false) + "-" + std::to_string(k) + w.getExtension();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool isWavePathUnique_(const m::Wave& skip, const std::string& path,
+ const std::vector<std::unique_ptr<Wave>>& waves)
+{
+ for (const auto& w : waves)
+ if (w->id != skip.id && w->getPath() == path)
+ return false;
+ return true;
+}
+
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void init()
+std::string makeUniqueWavePath(const std::string& base, const m::Wave& w,
+ const std::vector<std::unique_ptr<Wave>>& waves)
{
- waveId_ = IdManager();
+ std::string path = base + G_SLASH + w.getBasename(/*ext=*/true);
+ if (isWavePathUnique_(w, path, waves))
+ return path;
+
+ // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp())
+ int k = 0;
+ path = makeWavePath_(base, w, k);
+ while (!isWavePathUnique_(w, path, waves))
+ path = makeWavePath_(base, w, k++);
+
+ return path;
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+void WaveManager::reset()
+{
+ m_waveId = IdManager();
}
/* -------------------------------------------------------------------------- */
-Result createFromFile(const std::string& path, ID id, int samplerate, int quality)
+WaveManager::Result WaveManager::createFromFile(const std::string& path, ID id,
+ int samplerate, int quality)
{
if (path == "" || u::fs::isDir(path))
{
return {G_RES_ERR_WRONG_DATA};
}
- waveId_.set(id);
+ m_waveId.set(id);
- std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate(id));
+ std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate(id));
wave->alloc(header.frames, header.channels, header.samplerate, getBits_(header), path);
if (sf_readf_float(fileIn, wave->getBuffer()[0], header.frames) != header.frames)
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
+std::unique_ptr<Wave> WaveManager::createEmpty(int frames, int channels, int samplerate,
const std::string& name)
{
- std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate());
+ std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate());
wave->alloc(frames, channels, samplerate, G_DEFAULT_BIT_DEPTH, name);
wave->setLogical(true);
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b)
+std::unique_ptr<Wave> WaveManager::createFromWave(const Wave& src, int a, int b)
{
int channels = src.getBuffer().countChannels();
int frames = b - a;
- std::unique_ptr<Wave> wave = std::make_unique<Wave>(waveId_.generate());
+ std::unique_ptr<Wave> wave = std::make_unique<Wave>(m_waveId.generate());
wave->alloc(frames, channels, src.getRate(), src.getBits(), src.getPath());
wave->getBuffer().set(src.getBuffer(), frames);
wave->setLogical(true);
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Wave> deserializeWave(const patch::Wave& w, int samplerate, int quality)
+std::unique_ptr<Wave> WaveManager::deserializeWave(const Patch::Wave& w, int samplerate, int quality)
{
return createFromFile(w.path, w.id, samplerate, quality).wave;
}
-const patch::Wave serializeWave(const Wave& w)
+const Patch::Wave WaveManager::serializeWave(const Wave& w) const
{
return {w.id, u::fs::basename(w.getPath())};
}
/* -------------------------------------------------------------------------- */
-Wave* hydrateWave(ID waveId)
-{
- return model::find<Wave>(waveId);
-}
-
-/* -------------------------------------------------------------------------- */
-
-int resample(Wave& w, int quality, int samplerate)
+int WaveManager::resample(Wave& w, int quality, int samplerate)
{
float ratio = samplerate / (float)w.getRate();
int newSizeFrames = static_cast<int>(ceil(w.getBuffer().countFrames() * ratio));
- AudioBuffer newData;
+ mcl::AudioBuffer newData;
newData.alloc(newSizeFrames, w.getBuffer().countChannels());
SRC_DATA src_data;
/* -------------------------------------------------------------------------- */
-int save(const Wave& w, const std::string& path)
+int WaveManager::save(const Wave& w, const std::string& path)
{
SF_INFO header;
header.samplerate = w.getRate();
return G_RES_OK;
}
-} // namespace giada::m::waveManager
+} // namespace giada::m
#ifndef G_WAVE_MANAGER_H
#define G_WAVE_MANAGER_H
+#include "core/idManager.h"
+#include "core/patch.h"
#include "core/types.h"
+#include "core/wave.h"
#include <memory>
#include <string>
namespace giada::m
{
-class Wave;
-}
-namespace giada::m::patch
-{
-struct Wave;
-}
-namespace giada::m::waveManager
-{
-struct Result
+std::string makeUniqueWavePath(const std::string& base, const m::Wave& w,
+ const std::vector<std::unique_ptr<Wave>>& waves);
+
+/* -------------------------------------------------------------------------- */
+
+class WaveManager final
{
- int status;
- std::unique_ptr<Wave> wave = nullptr;
-};
-/* init
-Initializes internal data. */
+public:
+ struct Result
+ {
+ int status;
+ std::unique_ptr<Wave> wave = nullptr;
+ };
+
+ /* reset
+ Resets internal ID generator. */
-void init();
+ void reset();
-/* create
-Creates a new Wave object with data read from file 'path'. Pass id = 0 to
-auto-generate it. The function converts the Wave sample rate if it doesn't match
-the desired one as specified in 'samplerate'. */
+ /* create
+ Creates a new Wave object with data read from file 'path'. Pass id = 0 to
+ auto-generate it. The function converts the Wave sample rate if it doesn't
+ match the desired one as specified in 'samplerate'. */
-Result createFromFile(const std::string& path, ID id, int samplerate, int quality);
+ Result createFromFile(const std::string& path, ID id, int samplerate, int quality);
-/* createEmpty
-Creates a new silent Wave object. */
+ /* createEmpty
+ Creates a new silent Wave object. */
-std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
- const std::string& name);
+ std::unique_ptr<Wave> createEmpty(int frames, int channels, int samplerate,
+ const std::string& name);
-/* createFromWave
-Creates a new Wave from an existing one, copying the data in range a - b. */
+ /* createFromWave
+ Creates a new Wave from an existing one, copying the data in range a - b. */
-std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b);
+ std::unique_ptr<Wave> createFromWave(const Wave& src, int a, int b);
-/* (de)serializeWave
-Creates a new Wave given the patch raw data and vice versa. */
+ /* (de)serializeWave
+ Creates a new Wave given the patch raw data and vice versa. */
-std::unique_ptr<Wave> deserializeWave(const patch::Wave& w, int samplerate, int quality);
-const patch::Wave serializeWave(const Wave& w);
-Wave* hydrateWave(ID waveId);
+ std::unique_ptr<Wave> deserializeWave(const Patch::Wave& w, int samplerate, int quality);
+ const Patch::Wave serializeWave(const Wave& w) const;
-/* resample
-Change sample rate of 'w' to the desider value. The 'quality' parameter sets the
-algorithm to use for the conversion. */
+ /* resample
+ Change sample rate of 'w' to the desider value. The 'quality' parameter sets
+ the algorithm to use for the conversion. */
-int resample(Wave& w, int quality, int samplerate);
+ int resample(Wave& w, int quality, int samplerate);
-/* save
-Writes Wave data to file 'path'. Only 'wav' format is supported for now. */
+ /* save
+ Writes Wave data to file 'path'. Only 'wav' format is supported for now. */
-int save(const Wave& w, const std::string& path);
-} // namespace giada::m::waveManager
+ int save(const Wave& w, const std::string& path);
+
+private:
+ IdManager m_waveId;
+};
+} // namespace giada::m
#endif
#define G_WEAK_ATOMIC_H
#include <atomic>
+#include <functional>
namespace giada
{
WeakAtomic() = default;
WeakAtomic(T t)
- : m_value(t)
+ : m_atomic(t)
+ , m_value(t)
{
}
WeakAtomic(const WeakAtomic& o)
- : m_value(o.m_value.load(std::memory_order_relaxed))
+ : m_atomic(o.load())
+ , m_value(o.m_value)
{
}
- WeakAtomic(WeakAtomic&& o)
- : m_value(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed)
- {
- }
+ WeakAtomic(WeakAtomic&& o) = delete;
WeakAtomic& operator=(const WeakAtomic& o)
{
if (this == &o)
return *this;
- m_value.store(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed);
+ store(o.load());
+ m_value = o.m_value;
return *this;
}
T load() const
{
- return m_value.load(std::memory_order_relaxed);
+ return m_atomic.load(std::memory_order_relaxed);
}
void store(T t)
{
- return m_value.store(t, std::memory_order_relaxed);
+ m_atomic.store(t, std::memory_order_relaxed);
+ if (onChange != nullptr && t != m_value)
+ onChange(t);
+ m_value = t;
}
- private:
- std::atomic<T> m_value;
+ std::function<void(T)> onChange = nullptr;
+
+private:
+ std::atomic<T> m_atomic;
+ T m_value;
};
} // namespace giada
*
* -------------------------------------------------------------------------- */
-#include "actionEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
+#include "glue/actionEditor.h"
+#include "core/actions/action.h"
+#include "core/actions/actionRecorder.h"
+#include "core/actions/actions.h"
#include "core/const.h"
+#include "core/engine.h"
#include "core/model/model.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
#include "glue/events.h"
#include "glue/recorder.h"
#include <cassert>
+extern giada::m::Engine g_engine;
+
namespace giada::c::actionEditor
{
namespace
void recordFirstEnvelopeAction_(ID channelId, Frame frame, int value)
{
- namespace mr = m::recorder;
-
// TODO - use MidiEvent(float)
m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, G_MAX_VELOCITY);
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
- const m::Action a1 = mr::rec(channelId, 0, e1);
- const m::Action a2 = mr::rec(channelId, frame, e2);
- const m::Action a3 = mr::rec(channelId, m::clock::getFramesInLoop() - 1, e1);
+ const m::Action a1 = g_engine.actionRecorder.rec(channelId, 0, e1);
+ const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2);
+ const m::Action a3 = g_engine.actionRecorder.rec(channelId, g_engine.sequencer.getFramesInLoop() - 1, e1);
- mr::updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin)
- mr::updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id);
- mr::updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end)
+ g_engine.actionRecorder.updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin)
+ g_engine.actionRecorder.updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id);
+ g_engine.actionRecorder.updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end)
}
/* -------------------------------------------------------------------------- */
void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value)
{
- namespace mr = m::recorder;
-
- const m::Action a1 = mr::getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE);
+ const m::Action a1 = g_engine.actionRecorder.getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE);
const m::Action a3 = a1.next != nullptr ? *a1.next : m::Action{};
assert(a1.isValid());
// TODO - use MidiEvent(float)
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
- const m::Action a2 = mr::rec(channelId, frame, e2);
+ const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2);
- mr::updateSiblings(a2.id, a1.id, a3.id);
+ g_engine.actionRecorder.updateSiblings(a2.id, a1.id, a3.id);
}
/* -------------------------------------------------------------------------- */
bool isSinglePressMode_(ID channelId)
{
/* TODO - use m::model getChannel utils (to be added) */
- return m::model::get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS;
+ return g_engine.model.get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS;
}
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-SampleData::SampleData(const m::samplePlayer::Data& s)
+SampleData::SampleData(const m::SamplePlayer& s)
: channelMode(s.mode)
, isLoopMode(s.isAnyLoopMode())
{
/* -------------------------------------------------------------------------- */
-Data::Data(const m::channel::Data& c)
+Data::Data(const m::Channel& c)
: channelId(c.id)
, channelName(c.name)
-, actions(m::recorder::getActionsOnChannel(c.id))
+, framesInSeq(g_engine.sequencer.getFramesInSeq())
+, framesInBeat(g_engine.sequencer.getFramesInBeat())
+, framesInBar(g_engine.sequencer.getFramesInBar())
+, framesInLoop(g_engine.sequencer.getFramesInLoop())
+, actions(g_engine.actionRecorder.getActionsOnChannel(c.id))
{
if (c.type == ChannelType::SAMPLE)
sample = std::make_optional<SampleData>(c.samplePlayer.value());
}
+/* -------------------------------------------------------------------------- */
+
+Frame Data::getCurrentFrame() const
+{
+ return g_engine.sequencer.getCurrentFrame();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Data::isChannelPlaying() const
+{
+ return g_engine.model.get().getChannel(channelId).isPlaying();
+}
+
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
Data getData(ID channelId)
{
- return Data(m::model::get().getChannel(channelId));
+ return Data(g_engine.model.get().getChannel(channelId));
}
/* -------------------------------------------------------------------------- */
void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
-
if (f2 == 0)
f2 = f1 + G_DEFAULT_ACTION_SIZE;
/* Avoid frame overflow. */
- Frame overflow = f2 - (m::clock::getFramesInLoop());
+ Frame overflow = f2 - (g_engine.sequencer.getFramesInLoop());
if (overflow > 0)
{
f2 -= overflow;
m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity);
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity);
- mr::rec(channelId, f1, f2, e1, e2);
+ g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2);
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
}
void deleteMidiAction(ID channelId, const m::Action& a)
{
- namespace mr = m::recorder;
-
assert(a.isValid());
assert(a.event.getStatus() == m::MidiEvent::NOTE_ON);
if (a.next != nullptr)
{
events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN);
- mr::deleteAction(a.id, a.next->id);
+ g_engine.actionRecorder.deleteAction(a.id, a.next->id);
}
else
- mr::deleteAction(a.id);
+ g_engine.actionRecorder.deleteAction(a.id);
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
}
void updateMidiAction(ID channelId, const m::Action& a, int note, int velocity,
Frame f1, Frame f2)
{
- namespace mr = m::recorder;
-
- mr::deleteAction(a.id, a.next->id);
+ g_engine.actionRecorder.deleteAction(a.id, a.next->id);
recordMidiAction(channelId, note, velocity, f1, f2);
}
void recordSampleAction(ID channelId, int type, Frame f1, Frame f2)
{
- namespace mr = m::recorder;
-
if (isSinglePressMode_(channelId))
{
if (f2 == 0)
f2 = f1 + G_DEFAULT_ACTION_SIZE;
m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, 0, 0);
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, 0, 0);
- mr::rec(channelId, f1, f2, e1, e2);
+ g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2);
}
else
{
m::MidiEvent e1 = m::MidiEvent(type, 0, 0);
- mr::rec(channelId, f1, e1);
+ g_engine.actionRecorder.rec(channelId, f1, e1);
}
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
void updateSampleAction(ID channelId, const m::Action& a, int type,
Frame f1, Frame f2)
{
- namespace mr = m::recorder;
-
if (isSinglePressMode_(channelId))
- mr::deleteAction(a.id, a.next->id);
+ g_engine.actionRecorder.deleteAction(a.id, a.next->id);
else
- mr::deleteAction(a.id);
+ g_engine.actionRecorder.deleteAction(a.id);
recordSampleAction(channelId, type, f1, f2);
}
void deleteSampleAction(ID channelId, const m::Action& a)
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
-
if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo
- mr::deleteAction(a.id, a.next->id);
+ g_engine.actionRecorder.deleteAction(a.id, a.next->id);
else
- mr::deleteAction(a.id);
+ g_engine.actionRecorder.deleteAction(a.id);
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
}
void recordEnvelopeAction(ID channelId, Frame f, int value)
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
-
assert(value >= 0 && value <= G_MAX_VELOCITY);
/* First action ever? Add actions at boundaries. Else, find action right
before frame 'f' and inject a new action in there. Vertical envelope points
are forbidden for now. */
- if (!mr::hasActions(channelId, m::MidiEvent::ENVELOPE))
+ if (!g_engine.actionRecorder.hasActions(channelId, m::MidiEvent::ENVELOPE))
recordFirstEnvelopeAction_(channelId, f, value);
else
recordNonFirstEnvelopeAction_(channelId, f, value);
void deleteEnvelopeAction(ID channelId, const m::Action& a)
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
- namespace mrh = m::recorderHandler;
-
/* Deleting a boundary action wipes out everything. If is volume, remember
to restore _i and _d members in channel. */
/* TODO - move this to c::*/
/* TODO - FIX*/
- if (mrh::isBoundaryEnvelopeAction(a))
+ if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a))
{
if (a.isVolumeEnvelope())
{
- /*
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
- {
- c.volume_i = 1.0;
- c.volume_d = 0.0;
- });*/
+ // TODO reset all volume vars to 1.0
}
- mr::clearActions(channelId, a.event.getStatus());
+ g_engine.actionRecorder.clearActions(channelId, a.event.getStatus());
}
else
{
/* Original status: a1--->a--->a3
Modified status: a1-------->a3
Order is important, here: first update siblings, then delete the action.
- Otherwise mr::deleteAction would complain of missing prevId/nextId no
- longer found. */
+ Otherwise ActionRecorder::deleteAction() would complain of missing
+ prevId/nextId no longer found. */
- mr::updateSiblings(a1.id, a1prev.id, a3.id);
- mr::updateSiblings(a3.id, a1.id, a3next.id);
- mr::deleteAction(a.id);
+ g_engine.actionRecorder.updateSiblings(a1.id, a1prev.id, a3.id);
+ g_engine.actionRecorder.updateSiblings(a3.id, a1.id, a3next.id);
+ g_engine.actionRecorder.deleteAction(a.id);
}
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
void updateEnvelopeAction(ID channelId, const m::Action& a, Frame f, int value)
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
- namespace mrh = m::recorderHandler;
-
/* Update the action directly if it is a boundary one. Else, delete the
previous one and record a new action. */
- if (mrh::isBoundaryEnvelopeAction(a))
- mr::updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value));
+ if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a))
+ g_engine.actionRecorder.updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value));
else
{
deleteEnvelopeAction(channelId, a);
std::vector<m::Action> getActions(ID channelId)
{
- return m::recorder::getActionsOnChannel(channelId);
+ return g_engine.actionRecorder.getActionsOnChannel(channelId);
}
/* -------------------------------------------------------------------------- */
void updateVelocity(const m::Action& a, int value)
{
- namespace mr = m::recorder;
-
m::MidiEvent event(a.event);
event.setVelocity(value);
- mr::updateEvent(a.id, event);
+ g_engine.actionRecorder.updateEvent(a.id, event);
}
} // namespace giada::c::actionEditor
namespace giada::m
{
struct Action;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
-namespace giada::m::samplePlayer
-{
-struct Data;
-}
+class SamplePlayer;
+class Channel;
+} // namespace giada::m
+
namespace giada::c::actionEditor
{
struct SampleData
{
- SampleData(const m::samplePlayer::Data&);
+ SampleData(const m::SamplePlayer&);
SamplePlayerMode channelMode;
bool isLoopMode;
struct Data
{
Data() = default;
- Data(const m::channel::Data&);
+ Data(const m::Channel&);
+
+ Frame getCurrentFrame() const;
+ bool isChannelPlaying() const;
ID channelId;
std::string channelName;
+ Frame framesInSeq;
+ Frame framesInBeat;
+ Frame framesInBar;
+ Frame framesInLoop;
std::vector<m::Action> actions;
std::optional<SampleData> sample;
* -------------------------------------------------------------------------- */
#include "gui/elems/mainWindow/keyboard/channel.h"
-#include "channel.h"
-#include "core/clock.h"
+#include "core/actions/actionRecorder.h"
+#include "core/channels/channelManager.h"
#include "core/conf.h"
+#include "core/engine.h"
#include "core/kernelAudio.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/model/model.h"
+#include "core/patch.h"
#include "core/plugins/plugin.h"
#include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/plugins/pluginManager.h"
#include "core/recorder.h"
#include "core/wave.h"
#include "core/waveManager.h"
+#include "glue/channel.h"
+#include "glue/main.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/sampleEditor.h"
#include "gui/dialogs/warnings.h"
+#include "gui/dispatcher.h"
#include "gui/elems/basics/dial.h"
#include "gui/elems/basics/input.h"
#include "gui/elems/mainWindow/keyboard/channelButton.h"
#include "gui/elems/sampleEditor/volumeTool.h"
#include "gui/elems/sampleEditor/waveTools.h"
#include "gui/elems/sampleEditor/waveform.h"
-#include "main.h"
+#include "gui/ui.h"
+#include "src/core/actions/actions.h"
#include "utils/fs.h"
#include "utils/gui.h"
#include "utils/log.h"
#include <cmath>
#include <functional>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::channel
{
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-// TODO - just pass const channel::Data&
-SampleData::SampleData(const m::channel::Data& ch)
+// TODO - just pass const Channel&
+SampleData::SampleData(const m::Channel& ch)
: waveId(ch.samplePlayer->getWaveId())
, mode(ch.samplePlayer->mode)
, isLoop(ch.samplePlayer->isAnyLoopMode())
{
}
-Frame SampleData::getTracker() const { return m_channel->state->tracker.load(); }
+Frame SampleData::getTracker() const { return m_channel->shared->tracker.load(); }
/* TODO - useless methods, turn them into member vars */
Frame SampleData::getBegin() const { return m_channel->samplePlayer->begin; }
Frame SampleData::getEnd() const { return m_channel->samplePlayer->end; }
/* -------------------------------------------------------------------------- */
-MidiData::MidiData(const m::channel::Data& m)
+MidiData::MidiData(const m::Channel& m)
: m_channel(&m)
{
}
/* -------------------------------------------------------------------------- */
-Data::Data(const m::channel::Data& c)
-: id(c.id)
+Data::Data(const m::Channel& c)
+: viewDispatcher(g_ui.dispatcher)
+, id(c.id)
, columnId(c.columnId)
#ifdef WITH_VST
, plugins(c.plugins)
midi = std::make_optional<MidiData>(c);
}
-ChannelStatus Data::getPlayStatus() const { return m_channel.state->playStatus.load(); }
-ChannelStatus Data::getRecStatus() const { return m_channel.state->recStatus.load(); }
-bool Data::getReadActions() const { return m_channel.state->readActions.load(); }
-bool Data::isRecordingInput() const { return m::recManager::isRecordingInput(); }
-bool Data::isRecordingAction() const { return m::recManager::isRecordingAction(); }
+ChannelStatus Data::getPlayStatus() const { return m_channel.shared->playStatus.load(); }
+ChannelStatus Data::getRecStatus() const { return m_channel.shared->recStatus.load(); }
+bool Data::getReadActions() const { return m_channel.shared->readActions.load(); }
+bool Data::isRecordingInput() const { return g_engine.recorder.isRecordingInput(); }
+bool Data::isRecordingAction() const { return g_engine.recorder.isRecordingAction(); }
/* TODO - useless methods, turn them into member vars */
-bool Data::getSolo() const { return m_channel.solo; }
-bool Data::getMute() const { return m_channel.mute; }
+bool Data::getSolo() const { return m_channel.isSoloed(); }
+bool Data::getMute() const { return m_channel.isMuted(); }
bool Data::isArmed() const { return m_channel.armed; }
/* -------------------------------------------------------------------------- */
Data getData(ID channelId)
{
- return Data(m::model::get().getChannel(channelId));
+ return Data(g_engine.model.get().getChannel(channelId));
}
std::vector<Data> getChannels()
{
std::vector<Data> out;
- for (const m::channel::Data& ch : m::model::get().channels)
+ for (const m::Channel& ch : g_engine.model.get().channels)
if (!ch.isInternal())
out.push_back(Data(ch));
return out;
int loadChannel(ID channelId, const std::string& fname)
{
+ m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0,
+ g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+
+ if (res.status != G_RES_OK)
+ {
+ printLoadError_(res.status);
+ return res.status;
+ }
+
/* Save the patch and take the last browser's dir in order to re-use it the
next time. */
- m::conf::conf.samplePath = u::fs::dirname(fname);
-
- int res = m::mh::loadChannel(channelId, fname);
- if (res != G_RES_OK)
- printLoadError_(res);
+ g_engine.conf.data.samplePath = u::fs::dirname(fname);
+ g_engine.mixerHandler.loadChannel(channelId, std::move(res.wave));
- return res;
+ return G_RES_OK;
}
/* -------------------------------------------------------------------------- */
void addChannel(ID columnId, ChannelType type)
{
- m::mh::addChannel(type, columnId);
+ g_engine.mixerHandler.addChannel(type, columnId, g_engine.kernelAudio.getBufferSize(), g_engine.channelManager);
}
/* -------------------------------------------------------------------------- */
-void addAndLoadChannel(ID columnId, const std::string& fpath)
+void addAndLoadChannel(ID columnId, const std::string& fname)
{
- int res = m::mh::addAndLoadChannel(columnId, fpath);
- if (res != G_RES_OK)
- printLoadError_(res);
+ m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0,
+ g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+ if (res.status == G_RES_OK)
+ g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(),
+ g_engine.channelManager);
+ else
+ printLoadError_(res.status);
}
-void addAndLoadChannels(ID columnId, const std::vector<std::string>& fpaths)
+void addAndLoadChannels(ID columnId, const std::vector<std::string>& fnames)
{
- if (fpaths.size() == 1)
- return addAndLoadChannel(columnId, fpaths[0]);
-
bool errors = false;
- for (const std::string& f : fpaths)
- if (m::mh::addAndLoadChannel(columnId, f) != G_RES_OK)
+ for (const std::string& f : fnames)
+ {
+ m::WaveManager::Result res = g_engine.waveManager.createFromFile(f, /*id=*/0,
+ g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality);
+ if (res.status == G_RES_OK)
+ g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(),
+ g_engine.channelManager);
+ else
errors = true;
+ }
if (errors)
- v::gdAlert("Some files weren't loaded sucessfully.");
+ v::gdAlert("Some files weren't loaded successfully.");
}
/* -------------------------------------------------------------------------- */
{
if (!v::gdConfirmWin("Warning", "Delete channel: are you sure?"))
return;
- u::gui::closeAllSubwindows();
- m::recorder::clearChannel(channelId);
- m::mh::deleteChannel(channelId);
+ g_ui.closeAllSubwindows();
+
+#ifdef WITH_VST
+ const std::vector<m::Plugin*> plugins = g_engine.model.get().getChannel(channelId).plugins;
+#endif
+ g_engine.mixerHandler.deleteChannel(channelId);
+ g_engine.actionRecorder.clearChannel(channelId);
+#ifdef WITH_VST
+ g_engine.pluginHost.freePlugins(plugins);
+#endif
}
/* -------------------------------------------------------------------------- */
{
if (!v::gdConfirmWin("Warning", "Free channel: are you sure?"))
return;
- u::gui::closeAllSubwindows();
- m::recorder::clearChannel(channelId);
- m::mh::freeChannel(channelId);
+ g_ui.closeAllSubwindows();
+ g_engine.actionRecorder.clearChannel(channelId);
+ g_engine.mixerHandler.freeChannel(channelId);
}
/* -------------------------------------------------------------------------- */
void setInputMonitor(ID channelId, bool value)
{
- m::model::get().getChannel(channelId).audioReceiver->inputMonitor = value;
- m::model::swap(m::model::SwapType::SOFT);
+ g_engine.model.get().getChannel(channelId).audioReceiver->inputMonitor = value;
+ g_engine.model.swap(m::model::SwapType::SOFT);
}
/* -------------------------------------------------------------------------- */
void setOverdubProtection(ID channelId, bool value)
{
- m::channel::Data& ch = m::model::get().getChannel(channelId);
+ m::Channel& ch = g_engine.model.get().getChannel(channelId);
ch.audioReceiver->overdubProtection = value;
if (value == true && ch.armed)
ch.armed = false;
- m::model::swap(m::model::SwapType::SOFT);
+ g_engine.model.swap(m::model::SwapType::SOFT);
}
/* -------------------------------------------------------------------------- */
void cloneChannel(ID channelId)
{
- m::mh::cloneChannel(channelId);
+ g_engine.actionRecorder.cloneActions(channelId, g_engine.channelManager.getNextId());
+#ifdef WITH_VST
+ g_engine.mixerHandler.cloneChannel(channelId, g_engine.patch.data.samplerate,
+ g_engine.kernelAudio.getBufferSize(), g_engine.channelManager, g_engine.waveManager,
+ g_engine.sequencer, g_engine.pluginManager);
+#else
+ g_engine.mixerHandler.cloneChannel(channelId, g_engine.kernelAudio.getBufferSize(),
+ g_engine.channelManager, g_engine.waveManager);
+#endif
}
/* -------------------------------------------------------------------------- */
void setSamplePlayerMode(ID channelId, SamplePlayerMode mode)
{
- m::model::get().getChannel(channelId).samplePlayer->mode = mode;
- m::model::swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method
- u::gui::refreshActionEditor();
+ g_engine.model.get().getChannel(channelId).samplePlayer->mode = mode;
+ g_engine.model.swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method
+ g_ui.refreshSubWindow(WID_ACTION_EDITOR);
}
/* -------------------------------------------------------------------------- */
void setHeight(ID channelId, Pixel p)
{
- m::model::get().getChannel(channelId).height = p;
- m::model::swap(m::model::SwapType::SOFT);
+ g_engine.model.get().getChannel(channelId).height = p;
+ g_engine.model.swap(m::model::SwapType::SOFT);
}
/* -------------------------------------------------------------------------- */
void setName(ID channelId, const std::string& name)
{
- m::mh::renameChannel(channelId, name);
+ g_engine.mixerHandler.renameChannel(channelId, name);
}
} // namespace giada::c::channel
{
class Plugin;
}
+
+namespace giada::v
+{
+class Dispatcher;
+}
+
namespace giada::c::channel
{
struct SampleData
{
SampleData() = delete;
- SampleData(const m::channel::Data&);
+ SampleData(const m::Channel&);
Frame getTracker() const;
Frame getBegin() const;
bool isLoop;
float pitch;
- private:
- const m::channel::Data* m_channel;
+private:
+ const m::Channel* m_channel;
};
struct MidiData
{
MidiData() = delete;
- MidiData(const m::channel::Data&);
+ MidiData(const m::Channel&);
bool isOutputEnabled() const;
int getFilter() const;
- private:
- const m::channel::Data* m_channel;
+private:
+ const m::Channel* m_channel;
};
struct Data
{
- Data(const m::channel::Data&);
+ Data(const m::Channel&);
bool getMute() const;
bool getSolo() const;
bool isRecordingInput() const;
bool isRecordingAction() const;
+ v::Dispatcher& viewDispatcher;
+
ID id;
ID columnId;
#ifdef WITH_VST
std::optional<SampleData> sample;
std::optional<MidiData> midi;
- private:
- const m::channel::Data& m_channel;
+private:
+ const m::Channel& m_channel;
};
/* getChannels
*
* -------------------------------------------------------------------------- */
-#include "config.h"
+#include "glue/config.h"
#include "core/conf.h"
#include "core/const.h"
+#include "core/engine.h"
#include "core/kernelAudio.h"
+#include "core/kernelMidi.h"
+#include "core/midiMapper.h"
+#include "core/plugins/pluginManager.h"
#include "deps/rtaudio/RtAudio.h"
+#include "gui/dialogs/browser/browserDir.h"
+#include "gui/dialogs/config.h"
+#include "gui/dialogs/warnings.h"
+#include "gui/elems/config/tabPlugins.h"
+#include "gui/ui.h"
+#include "utils/fs.h"
+#include "utils/vector.h"
+#include <FL/Fl_Tooltip.H>
+
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::config
{
{
AudioDeviceData getAudioDeviceData_(DeviceType type, size_t index, int channelsCount, int channelsStart)
{
- for (const m::kernelAudio::Device& device : m::kernelAudio::getDevices())
+ for (const m::KernelAudio::Device& device : g_engine.kernelAudio.getDevices())
if (device.index == index)
return AudioDeviceData(type, device, channelsCount, channelsStart);
return AudioDeviceData();
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-AudioDeviceData::AudioDeviceData(DeviceType type, const m::kernelAudio::Device& device,
+AudioDeviceData::AudioDeviceData(DeviceType type, const m::KernelAudio::Device& device,
int channelsCount, int channelsStart)
: type(type)
, index(device.index)
#if defined(G_OS_LINUX)
- if (m::kernelAudio::hasAPI(RtAudio::LINUX_ALSA))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_ALSA))
audioData.apis[G_SYS_API_ALSA] = "ALSA";
- if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK))
audioData.apis[G_SYS_API_JACK] = "Jack";
- if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE))
audioData.apis[G_SYS_API_PULSE] = "PulseAudio";
#elif defined(G_OS_FREEBSD)
- if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK))
audioData.apis[G_SYS_API_JACK] = "Jack";
- if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE))
audioData.apis[G_SYS_API_PULSE] = "PulseAudio";
#elif defined(G_OS_WINDOWS)
- if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_DS))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_DS))
audioData.apis[G_SYS_API_DS] = "DirectSound";
- if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_ASIO))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_ASIO))
audioData.apis[G_SYS_API_ASIO] = "ASIO";
- if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_WASAPI))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_WASAPI))
audioData.apis[G_SYS_API_WASAPI] = "WASAPI";
#elif defined(G_OS_MAC)
- if (m::kernelAudio::hasAPI(RtAudio::MACOSX_CORE))
+ if (g_engine.kernelAudio.hasAPI(RtAudio::MACOSX_CORE))
audioData.apis[G_SYS_API_CORE] = "CoreAudio";
#endif
- std::vector<m::kernelAudio::Device> devices = m::kernelAudio::getDevices();
+ std::vector<m::KernelAudio::Device> devices = g_engine.kernelAudio.getDevices();
- for (const m::kernelAudio::Device& device : devices)
+ for (const m::KernelAudio::Device& device : devices)
{
if (device.maxOutputChannels > 0)
audioData.outputDevices.push_back(AudioDeviceData(DeviceType::OUTPUT, device, G_MAX_IO_CHANS, 0));
audioData.inputDevices.push_back(AudioDeviceData(DeviceType::INPUT, device, 1, 0));
}
- audioData.api = m::conf::conf.soundSystem;
- audioData.bufferSize = m::conf::conf.buffersize;
- audioData.sampleRate = m::conf::conf.samplerate;
- audioData.limitOutput = m::conf::conf.limitOutput;
- audioData.recTriggerLevel = m::conf::conf.recTriggerLevel;
- audioData.resampleQuality = m::conf::conf.rsmpQuality;
+ audioData.api = g_engine.conf.data.soundSystem;
+ audioData.bufferSize = g_engine.conf.data.buffersize;
+ audioData.sampleRate = g_engine.conf.data.samplerate;
+ audioData.limitOutput = g_engine.conf.data.limitOutput;
+ audioData.recTriggerLevel = g_engine.conf.data.recTriggerLevel;
+ audioData.resampleQuality = g_engine.conf.data.rsmpQuality;
audioData.outputDevice = getAudioDeviceData_(DeviceType::OUTPUT,
- m::conf::conf.soundDeviceOut, m::conf::conf.channelsOutCount,
- m::conf::conf.channelsOutStart);
+ g_engine.conf.data.soundDeviceOut, g_engine.conf.data.channelsOutCount,
+ g_engine.conf.data.channelsOutStart);
audioData.inputDevice = getAudioDeviceData_(DeviceType::INPUT,
- m::conf::conf.soundDeviceIn, m::conf::conf.channelsInCount,
- m::conf::conf.channelsInStart);
+ g_engine.conf.data.soundDeviceIn, g_engine.conf.data.channelsInCount,
+ g_engine.conf.data.channelsInStart);
return audioData;
}
/* -------------------------------------------------------------------------- */
+MidiData getMidiData()
+{
+ MidiData midiData;
+
+#if defined(G_OS_LINUX)
+
+ if (g_engine.kernelMidi.hasAPI(RtMidi::LINUX_ALSA))
+ midiData.apis[G_MIDI_API_ALSA] = "ALSA";
+ if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK))
+ midiData.apis[G_MIDI_API_JACK] = "JACK";
+
+#elif defined(G_OS_FREEBSD)
+
+ if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK))
+ midiData.apis[G_MIDI_API_JACK] = "JACK";
+
+#elif defined(G_OS_WINDOWS)
+
+ if (g_engine.kernelMidi.hasAPI(RtMidi::WINDOWS_MM))
+ midiData.apis[G_MIDI_API_MM] = "Multimedia MIDI";
+
+#elif defined(G_OS_MAC)
+
+ if (g_engine.kernelMidi.hasAPI(RtMidi::MACOSX_CORE))
+ midiData.apis[G_MIDI_API_CORE] = "OSX Core MIDI";
+
+#endif
+
+ midiData.syncModes[G_MIDI_SYNC_NONE] = "(disabled)";
+ midiData.syncModes[G_MIDI_SYNC_CLOCK_M] = "MIDI Clock (master)";
+ midiData.syncModes[G_MIDI_SYNC_MTC_M] = "MTC (master)";
+
+ midiData.midiMaps = g_engine.midiMapper.getMapFilesFound();
+ midiData.midiMap = u::vector::indexOf(midiData.midiMaps, g_engine.conf.data.midiMapPath);
+
+ for (unsigned i = 0; i < g_engine.kernelMidi.countOutPorts(); i++)
+ midiData.outPorts.push_back(g_engine.kernelMidi.getOutPortName(i));
+ for (unsigned i = 0; i < g_engine.kernelMidi.countInPorts(); i++)
+ midiData.inPorts.push_back(g_engine.kernelMidi.getInPortName(i));
+
+ midiData.api = g_engine.conf.data.midiSystem;
+ midiData.syncMode = g_engine.conf.data.midiSync;
+ midiData.outPort = g_engine.conf.data.midiPortOut;
+ midiData.inPort = g_engine.conf.data.midiPortIn;
+
+ return midiData;
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+PluginData getPluginData()
+{
+ PluginData pluginData;
+ pluginData.numAvailablePlugins = g_engine.pluginManager.countAvailablePlugins();
+ pluginData.pluginPath = g_engine.conf.data.pluginPath;
+ return pluginData;
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+MiscData getMiscData()
+{
+ MiscData miscData;
+ miscData.logMode = g_engine.conf.data.logMode;
+ miscData.showTooltips = g_engine.conf.data.showTooltips;
+ return miscData;
+}
+/* -------------------------------------------------------------------------- */
+
void save(const AudioData& data)
{
- m::conf::conf.soundSystem = data.api;
- m::conf::conf.soundDeviceOut = data.outputDevice.index;
- m::conf::conf.soundDeviceIn = data.inputDevice.index;
- m::conf::conf.channelsOutCount = data.outputDevice.channelsCount;
- m::conf::conf.channelsOutStart = data.outputDevice.channelsStart;
- m::conf::conf.channelsInCount = data.inputDevice.channelsCount;
- m::conf::conf.channelsInStart = data.inputDevice.channelsStart;
- m::conf::conf.limitOutput = data.limitOutput;
- m::conf::conf.rsmpQuality = data.resampleQuality;
- m::conf::conf.buffersize = data.bufferSize;
- m::conf::conf.recTriggerLevel = data.recTriggerLevel;
- m::conf::conf.samplerate = data.sampleRate;
+ g_engine.conf.data.soundSystem = data.api;
+ g_engine.conf.data.soundDeviceOut = data.outputDevice.index;
+ g_engine.conf.data.soundDeviceIn = data.inputDevice.index;
+ g_engine.conf.data.channelsOutCount = data.outputDevice.channelsCount;
+ g_engine.conf.data.channelsOutStart = data.outputDevice.channelsStart;
+ g_engine.conf.data.channelsInCount = data.inputDevice.channelsCount;
+ g_engine.conf.data.channelsInStart = data.inputDevice.channelsStart;
+ g_engine.conf.data.limitOutput = data.limitOutput;
+ g_engine.conf.data.rsmpQuality = data.resampleQuality;
+ g_engine.conf.data.buffersize = data.bufferSize;
+ g_engine.conf.data.recTriggerLevel = data.recTriggerLevel;
+ g_engine.conf.data.samplerate = data.sampleRate;
+ g_engine.updateMixerModel();
}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void save(const PluginData& data)
+{
+ g_engine.conf.data.pluginPath = data.pluginPath;
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void save(const MidiData& data)
+{
+ g_engine.conf.data.midiSystem = data.api;
+ g_engine.conf.data.midiPortOut = data.outPort;
+ g_engine.conf.data.midiPortIn = data.inPort;
+ //g_engine.conf.data.midiMapPath = data.midiMap >= 0 && data.midiMap < (int)data.midiMaps.size() ? data.midiMaps[data.midiMap] : "";
+ g_engine.conf.data.midiMapPath = u::vector::atOr(data.midiMaps, data.midiMap, "");
+ g_engine.conf.data.midiSync = data.syncMode;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void save(const MiscData& data)
+{
+ g_engine.conf.data.logMode = data.logMode;
+ g_engine.conf.data.showTooltips = data.showTooltips;
+ Fl_Tooltip::enable(g_engine.conf.data.showTooltips);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void scanPlugins(std::string dir, const std::function<void(float)>& progress)
+{
+ g_engine.pluginManager.scanDirs(dir, progress);
+ g_engine.pluginManager.saveList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
+}
+
+/* -------------------------------------------------------------------------- */
+
+void setPluginPathCb(void* data)
+{
+ v::gdBrowserDir* browser = static_cast<v::gdBrowserDir*>(data);
+ std::string& pluginPath = g_engine.conf.data.pluginPath;
+
+ if (browser->getCurrentPath() == "")
+ {
+ v::gdAlert("Invalid path.");
+ return;
+ }
+
+ if (!pluginPath.empty() && pluginPath.back() != ';')
+ pluginPath += ";";
+ pluginPath += browser->getCurrentPath();
+
+ browser->do_callback();
+
+ v::gdConfig* configWin = static_cast<v::gdConfig*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_CONFIG));
+ configWin->tabPlugins->rebuild();
+}
+#endif
} // namespace giada::c::config
\ No newline at end of file
#ifndef G_GLUE_CONFIG_H
#define G_GLUE_CONFIG_H
+#include "core/kernelAudio.h"
#include "core/types.h"
#include <map>
#include <string>
#include <vector>
-namespace giada::m::kernelAudio
-{
-struct Device;
-}
namespace giada::c::config
{
enum class DeviceType
struct AudioDeviceData
{
AudioDeviceData() = default;
- AudioDeviceData(DeviceType t, const m::kernelAudio::Device&, int channelsCount, int channelsStart);
+ AudioDeviceData(DeviceType t, const m::KernelAudio::Device&, int channelsCount, int channelsStart);
DeviceType type = DeviceType::OUTPUT;
int index = -1;
int resampleQuality;
};
-/* getAudioData
-Returns viewModel object filled with data. */
+struct MidiData
+{
+ std::map<int, std::string> apis;
+ std::map<int, std::string> syncModes;
+ std::vector<std::string> midiMaps;
+ std::vector<std::string> outPorts;
+ std::vector<std::string> inPorts;
+
+ /* Selectable values. */
+
+ int api;
+ int syncMode;
+ int midiMap;
+ int outPort;
+ int inPort;
+};
+
+#ifdef WITH_VST
+
+struct PluginData
+{
+ int numAvailablePlugins;
+ std::string pluginPath;
+};
+
+#endif
+
+struct MiscData
+{
+ int logMode;
+ bool showTooltips;
+};
+
+/* get*
+Return viewModel objects filled with data. */
AudioData getAudioData();
-/*
-AudioDeviceData getAudioDeviceData(size_t index, int channelsCount, int channelsStart);
-*/
+MidiData getMidiData();
+MiscData getMiscData();
+#ifdef WITH_VST
+PluginData getPluginData();
+#endif
+
void save(const AudioData&);
+void save(const MidiData&);
+void save(const MiscData&);
+#ifdef WITH_VST
+void save(const PluginData&);
+void scanPlugins(std::string dir, const std::function<void(float)>& progress);
+
+/* setPluginPathCb
+Callback attached to the DirBrowser for adding new Plug-in search paths in the
+configuration window. */
+
+void setPluginPathCb(void* data);
+#endif
} // namespace giada::c::config
#endif
* -------------------------------------------------------------------------- */
#include "events.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/const.h"
+#include "core/engine.h"
#include "core/eventDispatcher.h"
+#include "core/kernelAudio.h"
#include "core/midiEvent.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/model/model.h"
#include "core/plugins/pluginHost.h"
-#include "core/recManager.h"
+#include "core/recorder.h"
#include "core/sequencer.h"
#include "core/types.h"
#include "glue/main.h"
#include "gui/elems/sampleEditor/panTool.h"
#include "gui/elems/sampleEditor/pitchTool.h"
#include "gui/elems/sampleEditor/volumeTool.h"
+#include "gui/ui.h"
#include "utils/log.h"
#include <FL/Fl.H>
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::events
{
namespace
{
-void pushEvent_(m::eventDispatcher::Event e, Thread t)
+void pushEvent_(m::EventDispatcher::Event e, Thread t)
{
bool res = true;
if (t == Thread::MAIN)
- res = m::eventDispatcher::UIevents.push(e);
+ res = g_engine.eventDispatcher.UIevents.push(e);
else if (t == Thread::MIDI)
- res = m::eventDispatcher::MidiEvents.push(e);
+ res = g_engine.eventDispatcher.MidiEvents.push(e);
else
assert(false);
{
m::MidiEvent e;
e.setVelocity(velocity);
- pushEvent_({m::eventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t);
+ pushEvent_({m::EventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t);
}
void releaseChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t);
}
void killChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t);
}
/* -------------------------------------------------------------------------- */
{
v = std::clamp(v, 0.0f, G_MAX_VOLUME);
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t);
sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.volumeTool->update(v); });
if (t != Thread::MAIN)
{
Fl::lock();
- G_MainWin->keyboard->getChannel(channelId)->vol->value(v);
+ g_ui.mainWindow->keyboard->getChannel(channelId)->vol->value(v);
Fl::unlock();
}
}
{
v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH);
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t);
sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); });
}
v = std::clamp(v, 0.0f, G_MAX_PAN);
/* Pan event is currently triggered only by the main thread. */
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN);
sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); });
}
void toggleMuteChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t);
}
void toggleSoloChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t);
}
/* -------------------------------------------------------------------------- */
void toggleArmChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t);
}
void toggleReadActionsChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t);
}
void killReadActionsChannel(ID channelId, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t);
}
/* -------------------------------------------------------------------------- */
void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t);
+ pushEvent_({m::EventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t);
}
/* -------------------------------------------------------------------------- */
void toggleMetronome()
{
- m::sequencer::toggleMetronome();
+ g_engine.sequencer.toggleMetronome();
}
/* -------------------------------------------------------------------------- */
void setMasterInVolume(float v, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_IN_CHANNEL_ID, v}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_IN_CHANNEL_ID, v}, t);
if (t != Thread::MAIN)
{
Fl::lock();
- G_MainWin->mainIO->setInVol(v);
+ g_ui.mainWindow->mainIO->setInVol(v);
Fl::unlock();
}
}
void setMasterOutVolume(float v, Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_OUT_CHANNEL_ID, v}, t);
+ pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_OUT_CHANNEL_ID, v}, t);
if (t != Thread::MAIN)
{
Fl::lock();
- G_MainWin->mainIO->setOutVol(v);
+ g_ui.mainWindow->mainIO->setOutVol(v);
Fl::unlock();
}
}
void multiplyBeats()
{
- main::setBeats(m::clock::getBeats() * 2, m::clock::getBars());
+ main::setBeats(g_engine.sequencer.getBeats() * 2, g_engine.sequencer.getBars());
}
void divideBeats()
{
- main::setBeats(m::clock::getBeats() / 2, m::clock::getBars());
+ main::setBeats(g_engine.sequencer.getBeats() / 2, g_engine.sequencer.getBars());
}
/* -------------------------------------------------------------------------- */
void startSequencer(Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t);
- m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
+ pushEvent_({m::EventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t);
}
void stopSequencer(Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t);
}
void toggleSequencer(Thread t)
{
- m::clock::isRunning() ? stopSequencer(t) : startSequencer(t);
+ g_engine.sequencer.isRunning() ? stopSequencer(t) : startSequencer(t);
}
void rewindSequencer(Thread t)
{
- pushEvent_({m::eventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t);
+ pushEvent_({m::EventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t);
}
/* -------------------------------------------------------------------------- */
void toggleActionRecording()
{
- m::recManager::toggleActionRec(m::conf::conf.recTriggerMode);
+ if (!g_engine.kernelAudio.isReady())
+ return;
+ if (g_engine.recorder.isRecordingAction())
+ g_engine.recorder.stopActionRec(g_engine.actionRecorder);
+ else
+ g_engine.recorder.prepareActionRec(g_engine.conf.data.recTriggerMode);
}
void toggleInputRecording()
{
- m::recManager::toggleInputRec(m::conf::conf.recTriggerMode, m::conf::conf.inputRecMode);
+ if (!g_engine.kernelAudio.isReady() || !g_engine.kernelAudio.isInputEnabled() || !g_engine.mixerHandler.hasInputRecordableChannels())
+ return;
+ if (g_engine.recorder.isRecordingInput())
+ g_engine.recorder.stopInputRec(g_engine.conf.data.inputRecMode, g_engine.kernelAudio.getSampleRate());
+ else
+ g_engine.recorder.prepareInputRec(g_engine.conf.data.recTriggerMode, g_engine.conf.data.inputRecMode);
}
/* -------------------------------------------------------------------------- */
#ifdef WITH_VST
void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui)
{
- m::pluginHost::setPluginParameter(pluginId, paramIndex, value);
+ g_engine.pluginHost.setPluginParameter(pluginId, paramIndex, value);
c::plugin::updateWindow(pluginId, gui);
}
#endif
{
class MidiEvent;
}
+
namespace giada::c::events
{
/* Channel*
*
* -------------------------------------------------------------------------- */
-#include "io.h"
-#include "channel.h"
-#include "core/clock.h"
+#include "glue/io.h"
#include "core/conf.h"
+#include "core/engine.h"
#include "core/kernelAudio.h"
#include "core/midiDispatcher.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/model/model.h"
-#include "core/recManager.h"
#include "core/recorder.h"
-#include "core/recorderHandler.h"
#include "core/wave.h"
+#include "glue/channel.h"
+#include "glue/main.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/midiIO/midiInputBase.h"
#include "gui/dialogs/warnings.h"
#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
#include "gui/elems/mainWindow/mainTimer.h"
#include "gui/elems/mainWindow/mainTransport.h"
-#include "main.h"
-#include "utils/gui.h"
+#include "gui/ui.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
#include "utils/log.h"
#include "utils/math.h"
#include <FL/Fl.H>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::io
{
{
void rebuildMidiWindows_()
{
- u::gui::rebuildSubWindow(WID_MIDI_INPUT);
- u::gui::rebuildSubWindow(WID_MIDI_OUTPUT);
+ g_ui.rebuildSubWindow(WID_MIDI_INPUT);
+ g_ui.rebuildSubWindow(WID_MIDI_OUTPUT);
}
} // namespace
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Channel_InputData::Channel_InputData(const m::channel::Data& c)
+Channel_InputData::Channel_InputData(const m::Channel& c)
: channelId(c.id)
, channelType(c.type)
, enabled(c.midiLearner.enabled)
/* -------------------------------------------------------------------------- */
-MidiChannel_OutputData::MidiChannel_OutputData(const m::midiSender::Data& s)
+MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s)
: enabled(s.enabled)
, filter(s.filter)
{
/* -------------------------------------------------------------------------- */
-Channel_OutputData::Channel_OutputData(const m::channel::Data& c)
+Channel_OutputData::Channel_OutputData(const m::Channel& c)
: channelId(c.id)
, lightningEnabled(c.midiLighter.enabled)
, lightningPlaying(c.midiLighter.playing.getValue())
Channel_InputData channel_getInputData(ID channelId)
{
- return Channel_InputData(m::model::get().getChannel(channelId));
+ return Channel_InputData(g_engine.model.get().getChannel(channelId));
}
/* -------------------------------------------------------------------------- */
Channel_OutputData channel_getOutputData(ID channelId)
{
- return Channel_OutputData(m::model::get().getChannel(channelId));
+ return Channel_OutputData(g_engine.model.get().getChannel(channelId));
}
/* -------------------------------------------------------------------------- */
Master_InputData master_getInputData()
{
- return Master_InputData(m::model::get().midiIn);
+ return Master_InputData(g_engine.model.get().midiIn);
}
/* -------------------------------------------------------------------------- */
void channel_enableMidiLearn(ID channelId, bool v)
{
- m::model::get().getChannel(channelId).midiLearner.enabled = v;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).midiLearner.enabled = v;
+ g_engine.model.swap(m::model::SwapType::NONE);
rebuildMidiWindows_();
}
void channel_enableMidiLightning(ID channelId, bool v)
{
- m::model::get().getChannel(channelId).midiLighter.enabled = v;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).midiLighter.enabled = v;
+ g_engine.model.swap(m::model::SwapType::NONE);
rebuildMidiWindows_();
}
void channel_enableMidiOutput(ID channelId, bool v)
{
- m::model::get().getChannel(channelId).midiSender->enabled = v;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).midiSender->enabled = v;
+ g_engine.model.swap(m::model::SwapType::NONE);
rebuildMidiWindows_();
}
void channel_enableVelocityAsVol(ID channelId, bool v)
{
- m::model::get().getChannel(channelId).samplePlayer->velocityAsVol = v;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).samplePlayer->velocityAsVol = v;
+ g_engine.model.swap(m::model::SwapType::NONE);
}
/* -------------------------------------------------------------------------- */
void channel_setMidiInputFilter(ID channelId, int ch)
{
- m::model::get().getChannel(channelId).midiLearner.filter = ch;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).midiLearner.filter = ch;
+ g_engine.model.swap(m::model::SwapType::NONE);
}
void channel_setMidiOutputFilter(ID channelId, int ch)
{
- m::model::get().getChannel(channelId).midiSender->filter = ch;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().getChannel(channelId).midiSender->filter = ch;
+ g_engine.model.swap(m::model::SwapType::NONE);
}
/* -------------------------------------------------------------------------- */
void channel_setKey(ID channelId, int k)
{
- m::model::get().getChannel(channelId).key = k;
- m::model::swap(m::model::SwapType::HARD);
+ g_engine.model.get().getChannel(channelId).key = k;
+ g_engine.model.swap(m::model::SwapType::HARD);
}
/* -------------------------------------------------------------------------- */
void channel_startMidiLearn(int param, ID channelId)
{
- m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_);
+ g_engine.midiDispatcher.startChannelLearn(param, channelId, rebuildMidiWindows_);
}
void master_startMidiLearn(int param)
{
- m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_);
+ g_engine.midiDispatcher.startMasterLearn(param, rebuildMidiWindows_);
}
#ifdef WITH_VST
void plugin_startMidiLearn(int paramIndex, ID pluginId)
{
- m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
+ g_engine.midiDispatcher.startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
}
#endif
void stopMidiLearn()
{
- m::midiDispatcher::stopLearn();
+ g_engine.midiDispatcher.stopLearn();
rebuildMidiWindows_();
}
void channel_clearMidiLearn(int param, ID channelId)
{
- m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_);
+ g_engine.midiDispatcher.clearChannelLearn(param, channelId, rebuildMidiWindows_);
}
void master_clearMidiLearn(int param)
{
- m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_);
+ g_engine.midiDispatcher.clearMasterLearn(param, rebuildMidiWindows_);
}
#ifdef WITH_VST
void plugin_clearMidiLearn(int param, ID pluginId)
{
- m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_);
+ g_engine.midiDispatcher.clearPluginLearn(param, pluginId, rebuildMidiWindows_);
}
#endif
void master_enableMidiLearn(bool v)
{
- m::model::get().midiIn.enabled = v;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().midiIn.enabled = v;
+ g_engine.model.swap(m::model::SwapType::NONE);
rebuildMidiWindows_();
}
void master_setMidiFilter(int c)
{
- m::model::get().midiIn.filter = c;
- m::model::swap(m::model::SwapType::NONE);
+ g_engine.model.get().midiIn.filter = c;
+ g_engine.model.swap(m::model::SwapType::NONE);
}
} // namespace giada::c::io
#include "core/model/model.h"
#include "core/types.h"
-namespace giada::m::channel
+namespace giada::m
{
-struct Data;
+class Channel;
}
+
namespace giada::c::io
{
struct PluginParamData
struct Channel_InputData
{
Channel_InputData() = default;
- Channel_InputData(const m::channel::Data&);
+ Channel_InputData(const m::Channel&);
ID channelId;
ChannelType channelType;
struct MidiChannel_OutputData
{
- MidiChannel_OutputData(const m::midiSender::Data&);
+ MidiChannel_OutputData(const m::MidiSender&);
bool enabled;
int filter;
struct Channel_OutputData
{
Channel_OutputData() = default;
- Channel_OutputData(const m::channel::Data&);
+ Channel_OutputData(const m::Channel&);
ID channelId;
bool lightningEnabled;
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "glue/layout.h"
+#include "core/conf.h"
+#include "core/engine.h"
+#include "core/patch.h"
+#include "core/sequencer.h"
+#include "glue/config.h"
+#include "glue/storage.h"
+#include "gui/dialogs/about.h"
+#include "gui/dialogs/actionEditor/midiActionEditor.h"
+#include "gui/dialogs/actionEditor/sampleActionEditor.h"
+#include "gui/dialogs/beatsInput.h"
+#include "gui/dialogs/bpmInput.h"
+#include "gui/dialogs/browser/browserDir.h"
+#include "gui/dialogs/browser/browserLoad.h"
+#include "gui/dialogs/browser/browserSave.h"
+#include "gui/dialogs/channelNameInput.h"
+#include "gui/dialogs/config.h"
+#include "gui/dialogs/keyGrabber.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dialogs/midiIO/midiInputChannel.h"
+#include "gui/dialogs/midiIO/midiInputMaster.h"
+#include "gui/dialogs/midiIO/midiOutputMidiCh.h"
+#include "gui/dialogs/midiIO/midiOutputSampleCh.h"
+#include "gui/dialogs/pluginChooser.h"
+#include "gui/dialogs/pluginList.h"
+#include "gui/dialogs/sampleEditor.h"
+#include "gui/ui.h"
+
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
+
+namespace giada::c::layout
+{
+void openBrowserForProjectLoad()
+{
+ v::gdWindow* childWin = new v::gdBrowserLoad("Open project", g_engine.conf.data.patchPath,
+ c::storage::loadProject, 0, g_engine.conf.data);
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForProjectSave()
+{
+ v::gdWindow* childWin = new v::gdBrowserSave("Save project", g_engine.conf.data.patchPath,
+ g_engine.patch.data.name, c::storage::saveProject, 0, g_engine.conf.data);
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForSampleLoad(ID channelId)
+{
+ v::gdWindow* w = new v::gdBrowserLoad("Browse sample",
+ g_engine.conf.data.samplePath.c_str(), c::storage::loadSample, channelId, g_engine.conf.data);
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBrowserForSampleSave(ID channelId)
+{
+ v::gdWindow* w = new v::gdBrowserSave("Save sample",
+ g_engine.conf.data.samplePath.c_str(), "", c::storage::saveSample, channelId, g_engine.conf.data);
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openAboutWindow()
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdAbout(), WID_ABOUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openKeyGrabberWindow(const c::channel::Data& data)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdKeyGrabber(data), WID_KEY_GRABBER);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBpmWindow(std::string bpmValue)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBpmInput(bpmValue.c_str()), WID_BPM);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openBeatsWindow(int beats, int bars)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBeatsInput(beats, bars), WID_BEATS);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openConfigWindow()
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdConfig(400, 370, g_engine.conf.data), WID_CONFIG);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterMidiInputWindow()
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputMaster(g_engine.conf.data), WID_MIDI_INPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openChannelMidiInputWindow(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputChannel(channelId, g_engine.conf.data),
+ WID_MIDI_INPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleChannelMidiOutputWindow(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputSampleCh(channelId),
+ WID_MIDI_OUTPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMidiChannelMidiOutputWindow(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputMidiCh(channelId), WID_MIDI_OUTPUT);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleActionEditor(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(),
+ new v::gdSampleActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()),
+ WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMidiActionEditor(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(),
+ new v::gdMidiActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()),
+ WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openSampleEditor(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdSampleEditor(channelId, g_engine.conf.data),
+ WID_SAMPLE_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openRenameChannelWindow(const c::channel::Data& data)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdChannelNameInput(data),
+ WID_SAMPLE_NAME);
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void openBrowserForPlugins(v::gdWindow& parent)
+{
+ v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory",
+ g_engine.conf.data.patchPath, c::config::setPluginPathCb, g_engine.conf.data);
+ parent.addSubWindow(browser);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openChannelPluginListWindow(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(channelId, g_engine.conf.data),
+ WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterInPluginListWindow()
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_IN_CHANNEL_ID, g_engine.conf.data),
+ WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openMasterOutPluginListWindow()
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_OUT_CHANNEL_ID, g_engine.conf.data),
+ WID_FX_LIST);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void openPluginChooser(ID channelId)
+{
+ g_ui.openSubWindow(*g_ui.mainWindow.get(),
+ new v::gdPluginChooser(g_engine.conf.data.pluginChooserX,
+ g_engine.conf.data.pluginChooserY, g_engine.conf.data.pluginChooserW,
+ g_engine.conf.data.pluginChooserH, channelId, g_engine.conf.data),
+ WID_FX_CHOOSER);
+}
+
+#endif
+} // namespace giada::c::layout
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_GLUE_LAYOUT_H
+#define G_GLUE_LAYOUT_H
+
+#include "core/types.h"
+#include <string>
+
+namespace giada::v
+{
+class gdWindow;
+}
+
+namespace giada::c::channel
+{
+struct Data;
+}
+
+namespace giada::c::layout
+{
+void openBrowserForProjectLoad();
+void openBrowserForProjectSave();
+void openBrowserForSampleLoad(ID channelId);
+void openBrowserForSampleSave(ID channelId);
+void openAboutWindow();
+void openKeyGrabberWindow(const c::channel::Data&);
+void openBpmWindow(std::string bpmValue);
+void openBeatsWindow(int beats, int bars);
+void openConfigWindow();
+void openMasterMidiInputWindow();
+void openChannelMidiInputWindow(ID channelId);
+void openSampleChannelMidiOutputWindow(ID channelId);
+void openMidiChannelMidiOutputWindow(ID channelId);
+void openSampleActionEditor(ID channelId);
+void openMidiActionEditor(ID channelId);
+void openSampleEditor(ID channelId);
+void openRenameChannelWindow(const c::channel::Data&);
+#ifdef WITH_VST
+void openBrowserForPlugins(v::gdWindow& parent);
+void openChannelPluginListWindow(ID channelId);
+void openMasterInPluginListWindow();
+void openMasterOutPluginListWindow();
+void openPluginChooser(ID channelId);
+#endif
+} // namespace giada::c::layout
+
+#endif
*
* -------------------------------------------------------------------------- */
-#include "main.h"
-#include "core/clock.h"
+#include "glue/main.h"
#include "core/conf.h"
#include "core/const.h"
+#include "core/engine.h"
#include "core/init.h"
#include "core/kernelAudio.h"
#include "core/kernelMidi.h"
#include "core/model/model.h"
#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
-#include "core/recManager.h"
#include "core/recorder.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
+#include "core/synchronizer.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/warnings.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
#include "gui/elems/mainWindow/mainIO.h"
#include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/ui.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
#include "utils/gui.h"
#include "utils/log.h"
#include "utils/string.h"
#include <cassert>
#include <cmath>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::main
{
-Timer::Timer(const m::model::Clock& c)
+Timer::Timer(const m::model::Sequencer& c)
: bpm(c.bpm)
, beats(c.beats)
, bars(c.bars)
, quantize(c.quantize)
-, isUsingJack(m::kernelAudio::getAPI() == G_SYS_API_JACK)
-, isRecordingInput(m::recManager::isRecordingInput())
+, isUsingJack(g_engine.kernelAudio.getAPI() == G_SYS_API_JACK)
+, isRecordingInput(g_engine.recorder.isRecordingInput())
{
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-IO::IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m)
+IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m)
: masterOutVol(out.volume)
, masterInVol(in.volume)
#ifdef WITH_VST
/* -------------------------------------------------------------------------- */
-float IO::getMasterOutPeak()
+Peak IO::getMasterOutPeak()
{
- return m::mixer::getPeakOut();
+ return g_engine.mixer.getPeakOut();
}
-float IO::getMasterInPeak()
+Peak IO::getMasterInPeak()
{
- return m::mixer::getPeakIn();
+ return g_engine.mixer.getPeakIn();
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool IO::isKernelReady()
+{
+ return g_engine.kernelAudio.isReady();
}
/* -------------------------------------------------------------------------- */
Timer getTimer()
{
- return Timer(m::model::get().clock);
+ return Timer(g_engine.model.get().sequencer);
}
/* -------------------------------------------------------------------------- */
IO getIO()
{
- return IO(m::model::get().getChannel(m::mixer::MASTER_OUT_CHANNEL_ID),
- m::model::get().getChannel(m::mixer::MASTER_IN_CHANNEL_ID),
- m::model::get().mixer);
+ return IO(g_engine.model.get().getChannel(m::Mixer::MASTER_OUT_CHANNEL_ID),
+ g_engine.model.get().getChannel(m::Mixer::MASTER_IN_CHANNEL_ID),
+ g_engine.model.get().mixer);
}
/* -------------------------------------------------------------------------- */
{
Sequencer out;
- m::mixer::RecordInfo recInfo = m::mixer::getRecordInfo();
+ m::Mixer::RecordInfo recInfo = g_engine.mixer.getRecordInfo();
- out.isFreeModeInputRec = m::recManager::isRecordingInput() && m::conf::conf.inputRecMode == InputRecMode::FREE;
- out.shouldBlink = u::gui::shouldBlink() && (m::clock::getStatus() == ClockStatus::WAITING || out.isFreeModeInputRec);
- out.beats = m::clock::getBeats();
- out.bars = m::clock::getBars();
- out.currentBeat = m::clock::getCurrentBeat();
+ out.isFreeModeInputRec = g_engine.recorder.isRecordingInput() && g_engine.conf.data.inputRecMode == InputRecMode::FREE;
+ out.shouldBlink = g_ui.shouldBlink() && (g_engine.sequencer.getStatus() == SeqStatus::WAITING || out.isFreeModeInputRec);
+ out.beats = g_engine.sequencer.getBeats();
+ out.bars = g_engine.sequencer.getBars();
+ out.currentBeat = g_engine.sequencer.getCurrentBeat();
out.recPosition = recInfo.position;
out.recMaxLength = recInfo.maxLength;
/* -------------------------------------------------------------------------- */
+Transport getTransport()
+{
+ Transport transport;
+ transport.isRunning = g_engine.sequencer.isRunning();
+ transport.isRecordingAction = g_engine.recorder.isRecordingAction();
+ transport.isRecordingInput = g_engine.recorder.isRecordingInput();
+ transport.isMetronomeOn = g_engine.sequencer.isMetronomeOn();
+ transport.recTriggerMode = g_engine.conf.data.recTriggerMode;
+ transport.inputRecMode = g_engine.conf.data.inputRecMode;
+ return transport;
+}
+
+/* -------------------------------------------------------------------------- */
+
+MainMenu getMainMenu()
+{
+ MainMenu mainMenu;
+ mainMenu.hasAudioData = g_engine.mixerHandler.hasAudioData();
+ mainMenu.hasActions = g_engine.mixerHandler.hasActions();
+ return mainMenu;
+}
+
+/* -------------------------------------------------------------------------- */
+
void setBpm(const char* i, const char* f)
{
/* Never change this stuff while recording audio. */
- if (m::recManager::isRecordingInput())
+ if (g_engine.recorder.isRecordingInput())
return;
- m::clock::setBpm(std::atof(i) + (std::atof(f) / 10.0f));
+ g_engine.sequencer.setBpm(std::atof(i) + (std::atof(f) / 10.0f), g_engine.kernelAudio.getSampleRate());
}
/* -------------------------------------------------------------------------- */
{
/* Never change this stuff while recording audio. */
- if (m::recManager::isRecordingInput())
+ if (g_engine.recorder.isRecordingInput())
return;
- m::clock::setBpm(f);
+ g_engine.sequencer.setBpm(f, g_engine.kernelAudio.getSampleRate());
}
/* -------------------------------------------------------------------------- */
{
/* Never change this stuff while recording audio. */
- if (m::recManager::isRecordingInput())
+ if (g_engine.recorder.isRecordingInput())
return;
- m::clock::setBeats(beats, bars);
- m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop());
+ g_engine.sequencer.setBeats(beats, bars, g_engine.kernelAudio.getSampleRate());
+ g_engine.mixer.allocRecBuffer(g_engine.sequencer.getMaxFramesInLoop(g_engine.kernelAudio.getSampleRate()));
}
/* -------------------------------------------------------------------------- */
void quantize(int val)
{
- m::clock::setQuantize(val);
+ g_engine.sequencer.setQuantize(val, g_engine.kernelAudio.getSampleRate());
}
/* -------------------------------------------------------------------------- */
{
if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?"))
return;
- G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
- m::clock::setStatus(ClockStatus::STOPPED);
- m::mh::freeAllChannels();
- m::recorderHandler::clearAllActions();
+ g_ui.closeSubWindow(WID_SAMPLE_EDITOR);
+ g_engine.sequencer.setStatus(SeqStatus::STOPPED);
+ g_engine.synchronizer.sendMIDIstop();
+ g_engine.mixerHandler.freeAllChannels();
+ g_engine.actionRecorder.clearAllActions();
}
/* -------------------------------------------------------------------------- */
{
if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?"))
return;
- G_MainWin->delSubWindow(WID_ACTION_EDITOR);
- m::recorderHandler::clearAllActions();
+ g_ui.closeSubWindow(WID_ACTION_EDITOR);
+ g_engine.actionRecorder.clearAllActions();
}
/* -------------------------------------------------------------------------- */
void setInToOut(bool v)
{
- m::mh::setInToOut(v);
+ g_engine.mixerHandler.setInToOut(v);
}
/* -------------------------------------------------------------------------- */
void toggleRecOnSignal()
{
- if (!m::recManager::canEnableRecOnSignal())
- {
- m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL;
- return;
- }
- m::conf::conf.recTriggerMode = m::conf::conf.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL;
+ if (!g_engine.recorder.canEnableRecOnSignal())
+ g_engine.conf.data.recTriggerMode = RecTriggerMode::NORMAL;
+ else
+ g_engine.conf.data.recTriggerMode = g_engine.conf.data.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL;
+ g_engine.updateMixerModel();
}
/* -------------------------------------------------------------------------- */
void toggleFreeInputRec()
{
- if (!m::recManager::canEnableFreeInputRec())
- {
- m::conf::conf.inputRecMode = InputRecMode::RIGID;
- return;
- }
- m::conf::conf.inputRecMode = m::conf::conf.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE;
+ if (!g_engine.recorder.canEnableFreeInputRec())
+ g_engine.conf.data.inputRecMode = InputRecMode::RIGID;
+ else
+ g_engine.conf.data.inputRecMode = g_engine.conf.data.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE;
+ g_engine.updateMixerModel();
}
/* -------------------------------------------------------------------------- */
+#ifdef G_DEBUG_MODE
+
+void printDebugInfo()
+{
+ g_engine.model.debug();
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
void closeProject()
{
if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
return;
- m::init::reset();
- m::mixer::enable();
+ g_engine.mixer.disable();
+ g_engine.reset();
+ g_ui.reset();
+ g_engine.mixer.enable();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void quitGiada()
+{
+ m::init::closeMainWindow();
}
} // namespace giada::c::main
#include "core/types.h"
-namespace giada::m::channel
+namespace giada::m
{
-struct Data;
+class Channel;
}
+
namespace giada::m::model
{
-struct Clock;
-struct Clock;
-struct Mixer;
-struct Mixer;
+class Sequencer;
+class Mixer;
} // namespace giada::m::model
+
namespace giada::c::main
{
struct Timer
{
Timer() = default;
- Timer(const m::model::Clock& c);
+ Timer(const m::model::Sequencer& c);
float bpm;
int beats;
struct IO
{
IO() = default;
- IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m);
+ IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m);
float masterOutVol;
float masterInVol;
#endif
bool inToOut;
- float getMasterOutPeak();
- float getMasterInPeak();
+ Peak getMasterOutPeak();
+ Peak getMasterInPeak();
+ bool isKernelReady();
};
struct Sequencer
Frame recMaxLength;
};
+struct Transport
+{
+ bool isRunning;
+ bool isRecordingAction;
+ bool isRecordingInput;
+ bool isMetronomeOn;
+ RecTriggerMode recTriggerMode;
+ InputRecMode inputRecMode;
+};
+
+struct MainMenu
+{
+ bool hasAudioData;
+ bool hasActions;
+};
+
/* get*
Returns viewModel objects filled with data. */
Timer getTimer();
IO getIO();
Sequencer getSequencer();
+Transport getTransport();
+MainMenu getMainMenu();
/* setBpm (1)
Sets bpm value from string to float. */
void toggleRecOnSignal();
void toggleFreeInputRec();
+#ifdef G_DEBUG_MODE
+void printDebugInfo();
+#endif
/* closeProject
Resets Giada to init state. If resetGui also refresh all widgets. */
void closeProject();
+
+void quitGiada();
} // namespace giada::c::main
#endif
#include "core/plugins/plugin.h"
#include "core/conf.h"
#include "core/const.h"
+#include "core/engine.h"
+#include "core/kernelAudio.h"
#include "core/mixer.h"
#include "core/model/model.h"
#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
-#include "gui/dialogs/browser/browserDir.h"
#include "gui/dialogs/config.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/pluginList.h"
#include "gui/dialogs/pluginWindow.h"
-#include "gui/dialogs/warnings.h"
+#include "gui/ui.h"
#include "plugin.h"
#include "utils/gui.h"
#include <FL/Fl.H>
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::plugin
{
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Plugins::Plugins(const m::channel::Data& c)
+Plugins::Plugins(const m::Channel& c)
: channelId(c.id)
, plugins(c.plugins)
{
Plugins getPlugins(ID channelId)
{
- return Plugins(m::model::get().getChannel(channelId));
+ return Plugins(g_engine.model.get().getChannel(channelId));
}
Plugin getPlugin(m::Plugin& plugin, ID channelId)
return Param(plugin, index, channelId);
}
+std::vector<m::PluginManager::PluginInfo> getPluginsInfo()
+{
+ return g_engine.pluginManager.getPluginsInfo();
+}
+
/* -------------------------------------------------------------------------- */
void updateWindow(ID pluginId, bool gui)
{
- m::Plugin* p = m::model::find<m::Plugin>(pluginId);
+ m::Plugin* p = g_engine.model.findShared<m::Plugin>(pluginId);
assert(p != nullptr);
/* Get the parent window first: the plug-in list. Then, if it exists, get
the child window - the actual pluginWindow. */
- v::gdPluginList* parent = static_cast<v::gdPluginList*>(u::gui::getSubwindow(G_MainWin, WID_FX_LIST));
+ v::gdPluginList* parent = static_cast<v::gdPluginList*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_FX_LIST));
if (parent == nullptr)
return;
- v::gdPluginWindow* child = static_cast<v::gdPluginWindow*>(u::gui::getSubwindow(parent, pluginId + 1));
+ v::gdPluginWindow* child = static_cast<v::gdPluginWindow*>(g_ui.getSubwindow(*parent, pluginId + 1));
if (child == nullptr)
return;
void addPlugin(int pluginListIndex, ID channelId)
{
- if (pluginListIndex >= m::pluginManager::countAvailablePlugins())
+ if (pluginListIndex >= g_engine.pluginManager.countAvailablePlugins())
return;
- std::unique_ptr<m::Plugin> p = m::pluginManager::makePlugin(pluginListIndex);
- if (p != nullptr)
- m::pluginHost::addPlugin(std::move(p), channelId);
+ std::unique_ptr<m::Plugin> plugin = g_engine.pluginManager.makePlugin(pluginListIndex, g_engine.kernelAudio.getSampleRate(), g_engine.kernelAudio.getBufferSize(), g_engine.sequencer);
+ const m::Plugin* pluginPtr = plugin.get();
+ if (plugin != nullptr)
+ g_engine.pluginHost.addPlugin(std::move(plugin));
+
+ /* TODO - unfortunately JUCE wants mutable plugin objects due to the
+ presence of the non-const processBlock() method. Why not const_casting
+ only in the Plugin class? */
+ g_engine.model.get().getChannel(channelId).plugins.push_back(const_cast<m::Plugin*>(pluginPtr));
+ g_engine.model.swap(m::model::SwapType::HARD);
}
/* -------------------------------------------------------------------------- */
void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId)
{
- m::pluginHost::swapPlugin(p1, p2, channelId);
+ g_engine.pluginHost.swapPlugin(p1, p2, g_engine.model.get().getChannel(channelId).plugins);
+ g_engine.model.swap(m::model::SwapType::HARD);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void sortPlugins(m::PluginManager::SortMethod method)
+{
+ g_engine.pluginManager.sortPlugins(method);
}
/* -------------------------------------------------------------------------- */
void freePlugin(const m::Plugin& plugin, ID channelId)
{
- m::pluginHost::freePlugin(plugin, channelId);
+ u::vector::remove(g_engine.model.get().getChannel(channelId).plugins, &plugin);
+ g_engine.model.swap(m::model::SwapType::HARD);
+
+ g_engine.pluginHost.freePlugin(plugin);
}
/* -------------------------------------------------------------------------- */
void setProgram(ID pluginId, int programIndex)
{
- m::pluginHost::setPluginProgram(pluginId, programIndex);
+ g_engine.pluginHost.setPluginProgram(pluginId, programIndex);
updateWindow(pluginId, /*gui=*/true);
}
void toggleBypass(ID pluginId)
{
- m::pluginHost::toggleBypass(pluginId);
+ g_engine.pluginHost.toggleBypass(pluginId);
}
/* -------------------------------------------------------------------------- */
-void setPluginPathCb(void* data)
+void startDispatchLoop()
{
- v::gdBrowserDir* browser = (v::gdBrowserDir*)data;
-
- if (browser->getCurrentPath() == "")
- {
- v::gdAlert("Invalid path.");
- return;
- }
-
- if (!m::conf::conf.pluginPath.empty() && m::conf::conf.pluginPath.back() != ';')
- m::conf::conf.pluginPath += ";";
- m::conf::conf.pluginPath += browser->getCurrentPath();
-
- browser->do_callback();
+ g_ui.startJuceDispatchLoop();
+}
- v::gdConfig* configWin = static_cast<v::gdConfig*>(u::gui::getSubwindow(G_MainWin, WID_CONFIG));
- configWin->refreshVstPath();
+void stopDispatchLoop()
+{
+ g_ui.stopJuceDispatchLoop();
}
} // namespace giada::c::plugin
#ifdef WITH_VST
#include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
#include "core/types.h"
#include <string>
#include <vector>
{
class AudioProcessorEditor;
}
+
namespace giada::m
{
class Plugin;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
+class Channel;
+} // namespace giada::m
+
namespace giada::c::plugin
{
struct Program
std::vector<Program> programs;
std::vector<int> paramIndexes;
- private:
+private:
m::Plugin& m_plugin;
};
struct Plugins
{
Plugins() = default;
- Plugins(const m::channel::Data&);
+ Plugins(const m::Channel&);
ID channelId;
std::vector<m::Plugin*> plugins;
Plugin getPlugin(m::Plugin& plugin, ID channelId);
Param getParam(int index, const m::Plugin& plugin, ID channelId);
+std::vector<m::PluginManager::PluginInfo> getPluginsInfo();
+
/* updateWindow
Updates the editor-less plug-in window. This is useless if the plug-in has an
editor. */
void addPlugin(int pluginListIndex, ID channelId);
void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId);
+void sortPlugins(m::PluginManager::SortMethod);
void freePlugin(const m::Plugin& plugin, ID channelId);
void setProgram(ID pluginId, int programIndex);
void toggleBypass(ID pluginId);
-
-/* setPluginPathCb
-Callback attached to the DirBrowser for adding new Plug-in search paths in the
-configuration window. */
-
-void setPluginPathCb(void* data);
+void startDispatchLoop();
+void stopDispatchLoop();
} // namespace giada::c::plugin
#endif
*
* -------------------------------------------------------------------------- */
-#include "core/recorder.h"
-#include "core/action.h"
+#include "glue/recorder.h"
+#include "core/actions/actionRecorder.h"
#include "core/channels/channel.h"
-#include "core/clock.h"
#include "core/const.h"
+#include "core/engine.h"
#include "core/kernelMidi.h"
#include "core/mixer.h"
#include "core/model/model.h"
-#include "core/recorderHandler.h"
#include "gui/dialogs/warnings.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
-#include "recorder.h"
+#include "gui/ui.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/actions.h"
#include "utils/gui.h"
#include "utils/log.h"
#include <cassert>
+extern giada::m::Engine g_engine;
+extern giada::v::Ui g_ui;
+
namespace giada::c::recorder
{
void clearAllActions(ID channelId)
{
if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?"))
return;
- m::recorder::clearChannel(channelId);
+ g_engine.actionRecorder.clearChannel(channelId);
updateChannel(channelId, /*updateActionEditor=*/true);
}
{
if (!v::gdConfirmWin("Warning", "Clear all volume actions: are you sure?"))
return;
- m::recorder::clearActions(channelId, m::MidiEvent::ENVELOPE);
+ g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::ENVELOPE);
updateChannel(channelId, /*updateActionEditor=*/true);
}
{
if (!v::gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?"))
return;
- m::recorder::clearActions(channelId, m::MidiEvent::NOTE_ON);
- m::recorder::clearActions(channelId, m::MidiEvent::NOTE_OFF);
- m::recorder::clearActions(channelId, m::MidiEvent::NOTE_KILL);
+ g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_ON);
+ g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_OFF);
+ g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_KILL);
updateChannel(channelId, /*updateActionEditor=*/true);
}
void updateChannel(ID channelId, bool updateActionEditor)
{
/* TODO - move somewhere else in the core area */
- m::model::get().getChannel(channelId).hasActions = m::recorder::hasActions(channelId);
- m::model::swap(m::model::SwapType::HARD);
+ g_engine.model.get().getChannel(channelId).hasActions = g_engine.actionRecorder.hasActions(channelId);
+ g_engine.model.swap(m::model::SwapType::HARD);
if (updateActionEditor)
- u::gui::refreshActionEditor();
+ g_ui.refreshSubWindow(WID_ACTION_EDITOR);
}
} // namespace giada::c::recorder
#ifndef G_GLUE_RECORDER_H
#define G_GLUE_RECORDER_H
+#include "core/types.h"
+
namespace giada::c::recorder
{
void clearAllActions(ID channelId);
#include "gui/dialogs/sampleEditor.h"
#include "channel.h"
#include "core/const.h"
+#include "core/engine.h"
+#include "core/kernelAudio.h"
#include "core/mixerHandler.h"
#include "core/model/model.h"
+#include "core/sequencer.h"
#include "core/wave.h"
#include "core/waveManager.h"
#include "glue/events.h"
#include "gui/elems/sampleEditor/volumeTool.h"
#include "gui/elems/sampleEditor/waveTools.h"
#include "gui/elems/sampleEditor/waveform.h"
+#include "gui/ui.h"
#include "sampleEditor.h"
#include "utils/gui.h"
#include "utils/log.h"
#include <FL/Fl.H>
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
+extern giada::m::Engine g_engine;
namespace giada::c::sampleEditor
{
namespace
{
-m::channel::Data& getChannel_(ID channelId)
+m::Channel& getChannel_(ID channelId)
{
- return m::model::get().getChannel(channelId);
+ return g_engine.model.get().getChannel(channelId);
}
-m::samplePlayer::Data& getSamplePlayer_(ID channelId)
+m::SamplePlayer& getSamplePlayer_(ID channelId)
{
return getChannel_(channelId).samplePlayer.value();
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-Data::Data(const m::channel::Data& c)
+Data::Data(const m::Channel& c)
: channelId(c.id)
, name(c.name)
, volume(c.volume)
ChannelStatus Data::a_getPreviewStatus() const
{
- return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load();
+ return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->playStatus.load();
}
Frame Data::a_getPreviewTracker() const
{
- return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.load();
+ return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.load();
}
const m::Wave& Data::getWaveRef() const
return *m_channel->samplePlayer->getWave();
}
+Frame Data::getFramesInBar() const
+{
+ return g_engine.sequencer.getFramesInBar();
+}
+
+Frame Data::getFramesInLoop() const
+{
+ return g_engine.sequencer.getFramesInLoop();
+}
+
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
Data getData(ID channelId)
{
/* Prepare the preview channel first, then return Data object. */
- m::samplePlayer::loadWave(getChannel_(m::mixer::PREVIEW_CHANNEL_ID), &getWave_(channelId));
- m::model::swap(m::model::SwapType::SOFT);
+ m::Channel& previewChannel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID);
+ previewChannel.samplePlayer->loadWave(previewChannel, &getWave_(channelId));
+ g_engine.model.swap(m::model::SwapType::SOFT);
return Data(getChannel_(channelId));
}
void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f)
{
- v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR));
if (se == nullptr)
return;
if (!gui)
v::gdSampleEditor* getSampleEditorWindow()
{
- v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR));
assert(se != nullptr);
return se;
}
void setBeginEnd(ID channelId, Frame b, Frame e)
{
- m::channel::Data& c = getChannel_(channelId);
+ m::Channel& c = getChannel_(channelId);
b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1);
e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1);
else if (e < b)
e = b + 1;
- if (c.state->tracker.load() < b)
- c.state->tracker.store(b);
+ if (c.shared->tracker.load() < b)
+ c.shared->tracker.store(b);
getSamplePlayer_(channelId).begin = b;
getSamplePlayer_(channelId).end = e;
- m::model::swap(m::model::SwapType::SOFT);
+ g_engine.model.swap(m::model::SwapType::SOFT);
/* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */
getSampleEditorWindow()->rebuild();
void cut(ID channelId, Frame a, Frame b)
{
copy(channelId, a, b);
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::cut(getWave_(channelId), a, b);
resetBeginEnd_(channelId);
}
void copy(ID channelId, Frame a, Frame b)
{
- waveBuffer_ = m::waveManager::createFromWave(getWave_(channelId), a, b);
+ waveBuffer_ = g_engine.waveManager.createFromWave(getWave_(channelId), a, b);
}
/* -------------------------------------------------------------------------- */
/* Temporary disable wave reading in channel. From now on, the audio thread
won't be reading any wave, so editing it is safe. */
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
/* Paste copied data to destination wave. */
/* Pass the old wave that contains the pasted data to channel. */
- m::samplePlayer::setWave(getChannel_(channelId), &wave, 1.0f);
+ getChannel_(channelId).samplePlayer->setWave(&wave, 1.0f);
/* In the meantime, shift begin/end points to keep the previous position. */
void silence(ID channelId, int a, int b)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::silence(getWave_(channelId), a, b);
}
void fade(ID channelId, int a, int b, m::wfx::Fade type)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::fade(getWave_(channelId), a, b, type);
}
void smoothEdges(ID channelId, int a, int b)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::smooth(getWave_(channelId), a, b);
}
void reverse(ID channelId, Frame a, Frame b)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::reverse(getWave_(channelId), a, b);
}
void normalize(ID channelId, int a, int b)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::normalize(getWave_(channelId), a, b);
}
void trim(ID channelId, int a, int b)
{
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
m::wfx::trim(getWave_(channelId), a, b);
resetBeginEnd_(channelId);
}
void playPreview(bool loop)
{
setPreviewTracker(previewTracker_);
- channel::setSamplePlayerMode(m::mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC);
- events::pressChannel(m::mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN);
+ channel::setSamplePlayerMode(m::Mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC);
+ events::pressChannel(m::Mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN);
}
void stopPreview()
channel. */
setPreviewTracker(previewTracker_);
getSampleEditorWindow()->refresh();
- events::killChannel(m::mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
+ events::killChannel(m::Mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
}
void setPreviewTracker(Frame f)
{
- namespace mm = m::model;
-
- mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.store(f);
- mm::swap(mm::SwapType::SOFT);
+ g_engine.model.get().getChannel(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.store(f);
+ g_engine.model.swap(m::model::SwapType::SOFT);
previewTracker_ = f;
void cleanupPreview()
{
- namespace mm = m::model;
+ m::Channel& channel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID);
- m::samplePlayer::loadWave(mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID), nullptr);
- mm::swap(mm::SwapType::SOFT);
+ channel.samplePlayer->loadWave(channel, nullptr);
+ g_engine.model.swap(m::model::SwapType::SOFT);
}
/* -------------------------------------------------------------------------- */
void toNewChannel(ID channelId, Frame a, Frame b)
{
- ID columnId = G_MainWin->keyboard->getChannel(channelId)->getColumnId();
- m::mh::addAndLoadChannel(columnId, m::waveManager::createFromWave(getWave_(channelId), a, b));
+ ID columnId = g_ui.mainWindow->keyboard->getChannel(channelId)->getColumnId();
+ g_engine.mixerHandler.addAndLoadChannel(columnId, g_engine.waveManager.createFromWave(getWave_(channelId), a, b),
+ g_engine.kernelAudio.getBufferSize(), g_engine.channelManager);
}
/* -------------------------------------------------------------------------- */
void shift(ID channelId, Frame offset)
{
- namespace mm = m::model;
-
Frame shift = getSamplePlayer_(channelId).shift;
- mm::DataLock lock();
+ m::model::DataLock lock = g_engine.model.lockData();
+
m::wfx::shift(getWave_(channelId), offset - shift);
getSamplePlayer_(channelId).shift = offset;
namespace giada::m
{
class Wave;
-}
-namespace giada::m::channel
-{
-struct Data;
-}
+class Channel;
+} // namespace giada::m
+
namespace giada::v
{
class gdSampleEditor;
}
+
namespace giada::c::sampleEditor
{
struct Data
{
Data() = default;
- Data(const m::channel::Data&);
+ Data(const m::Channel&);
ChannelStatus a_getPreviewStatus() const;
Frame a_getPreviewTracker() const;
const m::Wave& getWaveRef() const; // TODO - getWaveData (or public ptr member to Wave::data)
+ Frame getFramesInBar() const;
+ Frame getFramesInLoop() const;
ID channelId;
std::string name;
std::string wavePath;
bool isLogical;
- private:
- const m::channel::Data* m_channel;
+private:
+ const m::Channel* m_channel;
};
/* onRefresh --- TODO - wrong name */
#include "core/model/storage.h"
#include "channel.h"
-#include "core/clock.h"
#include "core/conf.h"
+#include "core/engine.h"
#include "core/init.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/plugins/plugin.h"
#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
-#include "core/recorderHandler.h"
+#include "core/sequencer.h"
#include "core/wave.h"
#include "core/waveManager.h"
#include "gui/dialogs/browser/browserLoad.h"
#include "gui/elems/basics/progress.h"
#include "gui/elems/mainWindow/keyboard/column.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
-#include "gui/model.h"
+#include "gui/ui.h"
#include "main.h"
+#include "src/core/actions/actionRecorder.h"
#include "storage.h"
#include "utils/fs.h"
#include "utils/gui.h"
#include "utils/string.h"
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::m::Engine g_engine;
+extern giada::v::Ui g_ui;
-namespace giada
+namespace giada::c::storage
{
-namespace c
-{
-namespace storage
-{
-namespace
-{
-std::string makeWavePath_(const std::string& base, const m::Wave& w, int k)
-{
- return base + G_SLASH + w.getBasename(/*ext=*/false) + "-" + std::to_string(k) + w.getExtension();
-}
-
-bool isWavePathUnique_(const m::Wave& skip, const std::string& path)
-{
- for (const auto& w : m::model::getAll<m::model::WavePtrs>())
- if (w->id != skip.id && w->getPath() == path)
- return false;
- return true;
-}
-
-std::string makeUniqueWavePath_(const std::string& base, const m::Wave& w)
-{
- std::string path = base + G_SLASH + w.getBasename(/*ext=*/true);
- if (isWavePathUnique_(w, path))
- return path;
-
- // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp())
- int k = 0;
- path = makeWavePath_(base, w, k);
- while (!isWavePathUnique_(w, path))
- path = makeWavePath_(base, w, k++);
-
- return path;
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool savePatch_(const std::string& path, const std::string& name)
-{
- m::patch::init();
- m::patch::patch.name = name;
- m::model::store(m::patch::patch);
- v::model::store(m::patch::patch);
-
- if (!m::patch::write(path))
- return false;
-
- u::gui::updateMainWinLabel(name);
- m::conf::conf.patchPath = u::fs::getUpDir(u::fs::getUpDir(path));
- u::log::print("[savePatch] patch saved as %s\n", path);
-
- return true;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void saveWavesToProject_(const std::string& basePath)
-{
- for (const std::unique_ptr<m::Wave>& w : m::model::getAll<m::model::WavePtrs>())
- {
- w->setPath(makeUniqueWavePath_(basePath, *w));
- m::waveManager::save(*w, w->getPath()); // TODO - error checking
- }
-}
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
void loadProject(void* data)
{
- v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
- std::string fullPath = browser->getSelectedItem();
+ v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
- browser->showStatusBar();
-
- u::log::print("[loadProject] load from %s\n", fullPath);
+ const std::string projectPath = browser->getSelectedItem();
+ const std::string patchPath = projectPath + G_SLASH + u::fs::stripExt(u::fs::basename(projectPath)) + ".gptc";
- std::string fileToLoad = fullPath + G_SLASH + u::fs::stripExt(u::fs::basename(fullPath)) + ".gptc";
- std::string basePath = fullPath + G_SLASH;
-
- /* Read the patch from file. */
+ browser->showStatusBar();
- m::patch::init();
- int res = m::patch::read(fileToLoad, basePath);
- if (res != G_PATCH_OK)
+ if (int res = g_engine.load(projectPath, patchPath); res != G_PATCH_OK)
{
if (res == G_PATCH_UNREADABLE)
v::gdAlert("This patch is unreadable.");
return;
}
- /* Then reset the system (it disables mixer) and fill the model. */
-
- m::init::reset();
- v::model::load(m::patch::patch);
- m::model::load(m::patch::patch);
-
- /* Prepare the engine. Recorder has to recompute the actions positions if
- the current samplerate != patch samplerate. Clock needs to update frames
- in sequencer. */
-
- m::mh::updateSoloCount();
- m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate);
- m::clock::recomputeFrames();
- m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop());
+ /* Update UI. */
- /* Mixer is ready to go back online. */
-
- m::mixer::enable();
-
- /* Utilities and cosmetics. Save patchPath by taking the last dir of the
- broswer, in order to reuse it the next time. Also update UI. */
-
- m::conf::conf.patchPath = u::fs::dirname(fullPath);
- u::gui::updateMainWinLabel(m::patch::patch.name);
+ g_ui.load(g_engine.patch.data);
#ifdef WITH_VST
- if (m::pluginManager::hasMissingPlugins())
- v::gdAlert("Some plugins were not loaded successfully.\nCheck the plugin browser to know more.");
+ if (g_engine.pluginManager.hasMissingPlugins())
+ v::gdAlert("Some plug-ins were not loaded successfully.\nCheck the Plug-in Browser to know more.");
#endif
void saveProject(void* data)
{
- v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
- std::string name = u::fs::stripExt(browser->getName());
- std::string folderPath = browser->getCurrentPath();
- std::string fullPath = folderPath + G_SLASH + name + ".gprj";
- std::string gptcPath = fullPath + G_SLASH + name + ".gptc";
+ v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
- if (name == "")
+ const std::string projectName = u::fs::stripExt(browser->getName());
+ const std::string projectPath = browser->getCurrentPath() + G_SLASH + projectName + ".gprj";
+ const std::string patchPath = projectPath + G_SLASH + projectName + ".gptc";
+
+ if (projectName == "")
{
v::gdAlert("Please choose a project name.");
return;
}
- if (u::fs::dirExists(fullPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?"))
+ if (u::fs::dirExists(projectPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?"))
return;
- if (!u::fs::mkdir(fullPath))
+ g_ui.store(g_engine.patch.data);
+
+ if (!g_engine.store(projectName, projectPath, patchPath))
{
- u::log::print("[saveProject] Unable to make project directory!\n");
+ v::gdAlert("Unable to save the project!");
return;
}
- u::log::print("[saveProject] Project dir created: %s\n", fullPath);
-
- saveWavesToProject_(fullPath);
-
- if (savePatch_(gptcPath, name))
- browser->do_callback();
- else
- v::gdAlert("Unable to save the project!");
+ browser->do_callback();
}
/* -------------------------------------------------------------------------- */
if (res == G_RES_OK)
{
- m::conf::conf.samplePath = u::fs::dirname(fullPath);
+ g_engine.conf.data.samplePath = u::fs::dirname(fullPath);
browser->do_callback();
- G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open
+ g_ui.mainWindow->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open
}
}
if (u::fs::fileExists(filePath) && !v::gdConfirmWin("Warning", "File exists: overwrite?"))
return;
- ID waveId = m::model::get().getChannel(channelId).samplePlayer->getWaveId();
- m::Wave* wave = m::model::find<m::Wave>(waveId);
+ ID waveId = g_engine.model.get().getChannel(channelId).samplePlayer->getWaveId();
+ m::Wave* wave = g_engine.model.findShared<m::Wave>(waveId);
assert(wave != nullptr);
- if (!m::waveManager::save(*wave, filePath))
+ if (!g_engine.waveManager.save(*wave, filePath))
{
v::gdAlert("Unable to save this sample!");
return;
/* Update last used path in conf, so that it can be reused next time. */
- m::conf::conf.samplePath = u::fs::dirname(filePath);
+ g_engine.conf.data.samplePath = u::fs::dirname(filePath);
/* Update logical and edited states in Wave. */
- m::model::DataLock lock;
+ m::model::DataLock lock = g_engine.model.lockData();
wave->setLogical(false);
wave->setEdited(false);
browser->do_callback();
}
-} // namespace storage
-} // namespace c
-} // namespace giada
+} // namespace giada::c::storage
\ No newline at end of file
#ifndef G_GLUE_STORAGE_H
#define G_GLUE_STORAGE_H
-namespace giada
-{
-namespace c
-{
-namespace storage
+namespace giada::c::storage
{
void loadProject(void* data);
void saveProject(void* data);
void saveSample(void* data);
void loadSample(void* data);
-} // namespace storage
-} // namespace c
-} // namespace giada
+} // namespace giada::c::storage
#endif
*
* -------------------------------------------------------------------------- */
-#include "baseActionEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
+#include "gui/dialogs/actionEditor/baseActionEditor.h"
#include "core/conf.h"
#include "core/const.h"
-#include "core/midiEvent.h"
+#include "core/graphics.h"
#include "glue/channel.h"
+#include "gui/drawing.h"
#include "gui/elems/actionEditor/gridTool.h"
+#include "gui/elems/basics/button.h"
#include "gui/elems/basics/choice.h"
#include "gui/elems/basics/scrollPack.h"
+#include "src/core/actions/action.h"
#include "utils/gui.h"
#include "utils/string.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <cassert>
+#include <limits>
#include <string>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdBaseActionEditor::gdBaseActionEditor(ID channelId)
-: gdWindow(640, 284)
+gdBaseActionEditor::gdBaseActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdWindow(conf.actionEditorX, conf.actionEditorY, conf.actionEditorW, conf.actionEditorH)
, channelId(channelId)
-, ratio(G_DEFAULT_ZOOM_RATIO)
+, gridTool(0, 0, conf, framesInBeat)
+, zoomInBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm)
+, zoomOutBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm)
+, m_barTop(0, 0, Direction::HORIZONTAL)
+, m_splitScroll(0, 0, 0, 0)
+, m_conf(conf)
+, m_playhead(0)
+, m_ratio(conf.actionEditorZoom)
{
- using namespace giada::m;
+ end();
- if (conf::conf.actionEditorW)
- {
- resize(conf::conf.actionEditorX, conf::conf.actionEditorY,
- conf::conf.actionEditorW, conf::conf.actionEditorH);
- ratio = conf::conf.actionEditorZoom;
- }
+ m_barTop.position(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+
+ m_splitScroll.resize(
+ G_GUI_OUTER_MARGIN,
+ (G_GUI_OUTER_MARGIN * 2) + 20,
+ w() - G_GUI_OUTER_MARGIN * 2,
+ h() - (G_GUI_OUTER_MARGIN * 3) - 20);
+
+ zoomInBtn.callback(cb_zoomIn, this);
+ zoomInBtn.copy_tooltip("Zoom in");
+ zoomOutBtn.callback(cb_zoomOut, this);
+ zoomOutBtn.copy_tooltip("Zoom out");
+
+ add(m_barTop);
+ add(m_splitScroll);
}
/* -------------------------------------------------------------------------- */
{
using namespace giada::m;
- conf::conf.actionEditorX = x();
- conf::conf.actionEditorY = y();
- conf::conf.actionEditorW = w();
- conf::conf.actionEditorH = h();
- conf::conf.actionEditorZoom = ratio;
+ m_conf.actionEditorX = x();
+ m_conf.actionEditorY = y();
+ m_conf.actionEditorW = w();
+ m_conf.actionEditorH = h();
+ m_conf.actionEditorSplitH = m_splitScroll.getTopContentH();
+ m_conf.actionEditorZoom = m_ratio;
+}
+
+/* -------------------------------------------------------------------------- */
+
+int gdBaseActionEditor::getMouseOverContent() const
+{
+ return m_splitScroll.getScrollX() + (Fl::event_x() - G_GUI_OUTER_MARGIN);
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void gdBaseActionEditor::computeWidth()
+void gdBaseActionEditor::computeWidth(Frame framesInSeq, Frame framesInLoop)
{
- fullWidth = frameToPixel(m::clock::getFramesInSeq());
- loopWidth = frameToPixel(m::clock::getFramesInLoop());
+ fullWidth = frameToPixel(framesInSeq);
+ loopWidth = frameToPixel(framesInLoop);
}
/* -------------------------------------------------------------------------- */
Pixel gdBaseActionEditor::frameToPixel(Frame f) const
{
- return f / ratio;
+ return f / m_ratio;
}
Frame gdBaseActionEditor::pixelToFrame(Pixel p, bool snap) const
{
- return snap ? gridTool->getSnapFrame(p * ratio) : p * ratio;
+ return snap ? gridTool.getSnapFrame(p * m_ratio) : p * m_ratio;
}
/* -------------------------------------------------------------------------- */
void gdBaseActionEditor::zoomIn()
{
- float ratioPrev = ratio;
-
- ratio /= 2;
- if (ratio < MIN_RATIO)
- ratio = MIN_RATIO;
-
- if (ratioPrev != ratio)
- {
- rebuild();
- centerViewportIn();
- redraw();
- }
+ // Explicit type std::max<int> to fix MINMAX macro hell on Windows
+ zoomAbout([&r = m_ratio]() { return std::max<int>(r / RATIO_STEP, MIN_RATIO); });
}
/* -------------------------------------------------------------------------- */
void gdBaseActionEditor::zoomOut()
{
- float ratioPrev = ratio;
-
- ratio *= 2;
- if (ratio > MAX_RATIO)
- ratio = MAX_RATIO;
-
- if (ratioPrev != ratio)
- {
- rebuild();
- centerViewportOut();
- redraw();
- }
-}
-
-/* -------------------------------------------------------------------------- */
-
-void gdBaseActionEditor::centerViewportIn()
-{
- Pixel sx = Fl::event_x() + (viewport->xposition() * 2);
- viewport->scroll_to(sx, viewport->yposition());
-}
-
-void gdBaseActionEditor::centerViewportOut()
-{
- Pixel sx = -((Fl::event_x() + viewport->xposition()) / 2) + viewport->xposition();
- if (sx < 0)
- sx = 0;
- viewport->scroll_to(sx, viewport->yposition());
-}
-
-/* -------------------------------------------------------------------------- */
-
-int gdBaseActionEditor::getActionType() const
-{
- if (actionType->value() == 0)
- return m::MidiEvent::NOTE_ON;
- else if (actionType->value() == 1)
- return m::MidiEvent::NOTE_OFF;
- else if (actionType->value() == 2)
- return m::MidiEvent::NOTE_KILL;
-
- assert(false);
- return -1;
+ // Explicit type std::max<int> to fix MINMAX macro hell on Windows
+ zoomAbout([&r = m_ratio]() { return std::min<int>(r * RATIO_STEP, MAX_RATIO); });
}
/* -------------------------------------------------------------------------- */
set_non_modal();
size_range(640, 284);
- resizable(viewport);
show();
}
return Fl_Group::handle(e);
}
}
-} // namespace v
-} // namespace giada
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::draw()
+{
+ gdWindow::draw();
+
+ const geompp::Rect splitBounds = m_splitScroll.getBoundsNoScrollbar();
+ const geompp::Line playhead = splitBounds.getHeightAsLine().withX(m_playhead);
+
+ if (splitBounds.contains(playhead))
+ drawLine(playhead, G_COLOR_LIGHT_2);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::zoomAbout(std::function<float()> f)
+{
+ const float ratioPrev = m_ratio;
+ const int minWidth = w() - (G_GUI_OUTER_MARGIN * 2);
+
+ m_ratio = f();
+
+ /* Make sure the new content width doesn't underflow the window space (i.e.
+ the minimum width allowed). */
+
+ if (frameToPixel(m_data.framesInSeq) < minWidth)
+ {
+ m_ratio = m_data.framesInSeq / static_cast<float>(minWidth);
+ m_splitScroll.setScrollX(0);
+ }
+
+ /* 1. Store the current x-position, then the new x-position affected by the
+ zoom change. */
+
+ const int mpre = getMouseOverContent();
+ const int mnow = mpre / (m_ratio / ratioPrev);
+
+ /* 2. Rebuild everything and adjust scrolling given the change occurred in
+ the x-position. This effectively centers the view on the mouse cursor. */
+
+ rebuild();
+ m_splitScroll.setScrollX(m_splitScroll.getScrollX() + (mnow - mpre));
+ redraw();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void gdBaseActionEditor::refresh()
+{
+ m_playhead = m_data.isChannelPlaying() ? currentFrameToPixel() : 0;
+ redraw();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel gdBaseActionEditor::currentFrameToPixel() const
+{
+ return (frameToPixel(m_data.getCurrentFrame()) + m_splitScroll.x()) - m_splitScroll.getScrollX();
+}
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_BASE_ACTION_EDITOR_H
#define GD_BASE_ACTION_EDITOR_H
-#include "core/types.h"
+#include "core/conf.h"
#include "glue/actionEditor.h"
#include "gui/dialogs/window.h"
+#include "gui/elems/actionEditor/gridTool.h"
+#include "gui/elems/actionEditor/splitScroll.h"
+#include "gui/elems/basics/button.h"
+#include "gui/elems/basics/pack.h"
-class geButton;
-
-namespace giada
-{
-namespace m
+namespace giada::m
{
class Channel;
struct Action;
-} // namespace m
-namespace v
+} // namespace giada::m
+
+namespace giada::v
{
-class geChoice;
-class geGridTool;
-class geScrollPack;
class gdBaseActionEditor : public gdWindow
{
public:
virtual ~gdBaseActionEditor();
- int handle(int e) override;
+ int handle(int e) override;
+ void draw() override;
Pixel frameToPixel(Frame f) const;
Frame pixelToFrame(Pixel p, bool snap = true) const;
- int getActionType() const;
ID channelId;
- geChoice* actionType;
- geGridTool* gridTool;
- geButton* zoomInBtn;
- geButton* zoomOutBtn;
- geScrollPack* viewport; // widget container
+ geGridTool gridTool;
+ geButton zoomInBtn;
+ geButton zoomOutBtn;
- float ratio;
Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range
- protected:
- static constexpr Pixel RESIZER_BAR_H = 20;
- static constexpr Pixel MIN_WIDGET_H = 10;
- static constexpr float MIN_RATIO = 25.0f;
- static constexpr float MAX_RATIO = 40000.0f;
+protected:
+ static constexpr float MIN_RATIO = 25.0f;
+ static constexpr float MAX_RATIO = 40000.0f;
+ static constexpr float RATIO_STEP = 1.5f;
- gdBaseActionEditor(ID channelId);
+ gdBaseActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
+ /* getMouseOverContent
+ Returns mouse x-position relative to the viewport content. */
+
+ int getMouseOverContent() const;
+
+ static void cb_zoomIn(Fl_Widget* w, void* p);
+ static void cb_zoomOut(Fl_Widget* w, void* p);
void zoomIn();
void zoomOut();
- static void cb_zoomIn(Fl_Widget* /*w*/, void* p);
- static void cb_zoomOut(Fl_Widget* /*w*/, void* p);
/* computeWidth
Computes total width, in pixel. */
- void computeWidth();
+ void computeWidth(Frame framesInSeq, Frame framesInLoop);
- void centerViewportIn();
- void centerViewportOut();
+ /* prepareWindow
+ Initializes window (favicon, limits, ...). */
void prepareWindow();
+ gePack m_barTop;
+ geSplitScroll m_splitScroll;
+
c::actionEditor::Data m_data;
-};
-} // namespace v
-} // namespace giada
+ m::Conf::Data& m_conf;
+
+private:
+ void refresh() override;
+
+ /* zoomAbout
+ Zooms and centers the viewport around the mouse cursor. Wants a function to
+ apply to the current ratio. */
+ void zoomAbout(std::function<float()> f);
+
+ Pixel currentFrameToPixel() const;
+
+ Pixel m_playhead;
+ float m_ratio;
+};
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "midiActionEditor.h"
-#include "core/graphics.h"
+#include "core/conf.h"
#include "glue/actionEditor.h"
#include "glue/channel.h"
-#include "gui/elems/actionEditor/gridTool.h"
-#include "gui/elems/actionEditor/noteEditor.h"
-#include "gui/elems/actionEditor/pianoRoll.h"
-#include "gui/elems/actionEditor/velocityEditor.h"
#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/resizerBar.h"
-#include "gui/elems/basics/scrollPack.h"
-#include <string>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdMidiActionEditor::gdMidiActionEditor(ID channelId)
-: gdBaseActionEditor(channelId)
+gdMidiActionEditor::gdMidiActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdBaseActionEditor(channelId, conf, framesInBeat)
+, m_barPadding(0, 0, w() - 150, G_GUI_UNIT)
+, m_pianoRoll(0, 0, this)
+, m_velocityEditor(0, 0, this)
{
end();
- computeWidth();
-
- gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
- gridTool = new geGridTool(0, 0);
- geBox* b1 = new geBox(0, 0, w() - 150, G_GUI_UNIT); // padding actionType - zoomButtons
- zoomInBtn = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
- zoomOutBtn = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm);
- upperArea->add(gridTool);
- upperArea->add(b1);
- upperArea->add(zoomInBtn);
- upperArea->add(zoomOutBtn);
- upperArea->resizable(b1);
-
- /* Main viewport: contains all widgets. */
+ m_barTop.add(&gridTool);
+ m_barTop.add(&m_barPadding);
+ m_barTop.add(&zoomInBtn);
+ m_barTop.add(&zoomOutBtn);
+ m_barTop.resizable(m_barPadding);
- viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
- upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
- m_ne = new geNoteEditor(0, 0, this);
- m_ner = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
- m_ve = new geVelocityEditor(0, 0, this);
- m_ver = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
- viewport->add(m_ne);
- viewport->add(m_ner);
- viewport->add(m_ve);
- viewport->add(m_ver);
+ m_splitScroll.addWidgets(m_pianoRoll, m_velocityEditor, conf.actionEditorSplitH);
- zoomInBtn->callback(cb_zoomIn, (void*)this);
- zoomInBtn->copy_tooltip("Zoom in");
- zoomOutBtn->callback(cb_zoomOut, (void*)this);
- zoomOutBtn->copy_tooltip("Zoom out");
+ if (conf.actionEditorPianoRollY != -1)
+ m_splitScroll.setScrollY(conf.actionEditorPianoRollY);
- add(upperArea);
- add(viewport);
- resizable(upperArea);
+ resizable(m_splitScroll); // Make it resizable only once filled with widgets
prepareWindow();
rebuild();
/* -------------------------------------------------------------------------- */
+gdMidiActionEditor::~gdMidiActionEditor()
+{
+ m_conf.actionEditorPianoRollY = m_splitScroll.getScrollY();
+ m_barTop.remove(gridTool);
+ m_barTop.remove(zoomInBtn);
+ m_barTop.remove(zoomOutBtn);
+}
+
+/* -------------------------------------------------------------------------- */
+
void gdMidiActionEditor::rebuild()
{
m_data = c::actionEditor::getData(channelId);
- computeWidth();
- m_ne->rebuild(m_data);
- m_ner->size(m_ne->w(), m_ner->h());
- m_ve->rebuild(m_data);
- m_ver->size(m_ve->w(), m_ver->h());
+ computeWidth(m_data.framesInSeq, m_data.framesInLoop);
+
+ m_pianoRoll.rebuild(m_data);
+ m_velocityEditor.rebuild(m_data);
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#define GD_MIDI_ACTION_EDITOR_H
#include "baseActionEditor.h"
+#include "gui/elems/actionEditor/pianoRoll.h"
+#include "gui/elems/actionEditor/velocityEditor.h"
+#include "gui/elems/basics/box.h"
-class geResizerBar;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
-class geNoteEditor;
-class geVelocityEditor;
-
class gdMidiActionEditor : public gdBaseActionEditor
{
public:
- gdMidiActionEditor(ID channelId);
+ gdMidiActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
+ ~gdMidiActionEditor();
void rebuild() override;
- private:
- geNoteEditor* m_ne;
- geResizerBar* m_ner;
-
- geVelocityEditor* m_ve;
- geResizerBar* m_ver;
+private:
+ geBox m_barPadding;
+ gePianoRoll m_pianoRoll;
+ geVelocityEditor m_velocityEditor;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* -------------------------------------------------------------------------- */
-#include "gui/elems/actionEditor/sampleActionEditor.h"
+#include "sampleActionEditor.h"
+#include "core/conf.h"
#include "core/const.h"
-#include "core/graphics.h"
#include "core/midiEvent.h"
#include "core/model/model.h"
#include "glue/actionEditor.h"
#include "glue/channel.h"
-#include "gui/elems/actionEditor/envelopeEditor.h"
-#include "gui/elems/actionEditor/gridTool.h"
#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/choice.h"
-#include "gui/elems/basics/pack.h"
-#include "gui/elems/basics/resizerBar.h"
-#include "gui/elems/basics/scrollPack.h"
-#include "sampleActionEditor.h"
#include <string>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdSampleActionEditor::gdSampleActionEditor(ID channelId)
-: gdBaseActionEditor(channelId)
+gdSampleActionEditor::gdSampleActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat)
+: gdBaseActionEditor(channelId, conf, framesInBeat)
+, m_barPadding(0, 0, w() - 232, G_GUI_UNIT)
+, m_sampleActionEditor(0, 0, this)
+, m_envelopeEditor(0, 0, "Volume", this)
+, m_actionType(0, 0, 80, G_GUI_UNIT)
{
end();
- computeWidth();
-
- /* Container with zoom buttons and the action type selector. Scheme of the
- resizable boxes: |[--b1--][actionType][--b2--][+][-]| */
-
- gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
- actionType = new geChoice(0, 0, 80, 20);
- gridTool = new geGridTool(0, 0);
- geBox* b1 = new geBox(0, 0, w() - 232, 20); // padding actionType - zoomButtons
- zoomInBtn = new geButton(0, 0, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
- zoomOutBtn = new geButton(0, 0, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
- upperArea->add(actionType);
- upperArea->add(gridTool);
- upperArea->add(b1);
- upperArea->add(zoomInBtn);
- upperArea->add(zoomOutBtn);
- upperArea->resizable(b1);
-
- actionType->add("Key press");
- actionType->add("Key release");
- actionType->add("Kill chan");
- actionType->value(0);
- actionType->copy_tooltip("Action type to add");
+ m_barTop.add(&m_actionType);
+ m_barTop.add(&gridTool);
+ m_barTop.add(&m_barPadding);
+ m_barTop.add(&zoomInBtn);
+ m_barTop.add(&zoomOutBtn);
+ m_barTop.resizable(m_barPadding);
+
+ m_actionType.add("Key press");
+ m_actionType.add("Key release");
+ m_actionType.add("Kill chan");
+ m_actionType.value(0);
+ m_actionType.copy_tooltip("Action type to add");
if (!canChangeActionType())
- actionType->deactivate();
-
- zoomInBtn->callback(cb_zoomIn, (void*)this);
- zoomInBtn->copy_tooltip("Zoom in");
- zoomOutBtn->callback(cb_zoomOut, (void*)this);
- zoomOutBtn->copy_tooltip("Zoom out");
-
- /* Main viewport: contains all widgets. */
-
- viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
- upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
- m_ae = new geSampleActionEditor(0, 0, this);
- m_aer = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
- m_ee = new geEnvelopeEditor(0, 0, "volume", this);
- m_eer = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
- viewport->add(m_ae);
- viewport->add(m_aer);
- viewport->add(m_ee);
- viewport->add(m_eer);
-
- add(upperArea);
- add(viewport);
- resizable(upperArea);
+ m_actionType.deactivate();
+
+ m_splitScroll.addWidgets(m_sampleActionEditor, m_envelopeEditor, conf.actionEditorSplitH);
+
+ resizable(m_splitScroll); // Make it resizable only once filled with widgets
prepareWindow();
rebuild();
/* -------------------------------------------------------------------------- */
+gdSampleActionEditor::~gdSampleActionEditor()
+{
+ m_barTop.remove(m_actionType);
+ m_barTop.remove(gridTool);
+ m_barTop.remove(zoomInBtn);
+ m_barTop.remove(zoomOutBtn);
+}
+
+/* -------------------------------------------------------------------------- */
+
bool gdSampleActionEditor::canChangeActionType()
{
return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS &&
{
m_data = c::actionEditor::getData(channelId);
- canChangeActionType() ? actionType->activate() : actionType->deactivate();
- computeWidth();
+ canChangeActionType() ? m_actionType.activate() : m_actionType.deactivate();
+ computeWidth(m_data.framesInSeq, m_data.framesInLoop);
+
+ m_sampleActionEditor.rebuild(m_data);
+ m_envelopeEditor.rebuild(m_data);
+}
- m_ae->rebuild(m_data);
- m_aer->size(m_ae->w(), m_aer->h());
- m_ee->rebuild(m_data);
- m_eer->size(m_ee->w(), m_eer->h());
+/* -------------------------------------------------------------------------- */
+
+int gdSampleActionEditor::getActionType() const
+{
+ if (m_actionType.value() == 0)
+ return m::MidiEvent::NOTE_ON;
+ else if (m_actionType.value() == 1)
+ return m::MidiEvent::NOTE_OFF;
+ else if (m_actionType.value() == 2)
+ return m::MidiEvent::NOTE_KILL;
+
+ assert(false);
+ return -1;
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#define GD_SAMPLE_ACTION_EDITOR_H
#include "baseActionEditor.h"
+#include "gui/elems/actionEditor/envelopeEditor.h"
+#include "gui/elems/actionEditor/sampleActionEditor.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/choice.h"
-class geResizerBar;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
-class geSampleActionEditor;
-class geEnvelopeEditor;
class gdSampleActionEditor : public gdBaseActionEditor
{
public:
- gdSampleActionEditor(ID channelId);
+ gdSampleActionEditor(ID channelId, m::Conf::Data&, Frame framesInBeat);
+ ~gdSampleActionEditor();
void rebuild() override;
- private:
- bool canChangeActionType();
+ int getActionType() const;
- geSampleActionEditor* m_ae;
- geResizerBar* m_aer;
+private:
+ bool canChangeActionType();
- geEnvelopeEditor* m_ee;
- geResizerBar* m_eer;
+ geBox m_barPadding;
+ geSampleActionEditor m_sampleActionEditor;
+ geEnvelopeEditor m_envelopeEditor;
+ geChoice m_actionType;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "beatsInput.h"
-#include "core/clock.h"
-#include "core/conf.h"
#include "core/const.h"
-#include "core/mixer.h"
#include "glue/main.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/check.h"
extern giada::v::gdMainWindow* mainWin;
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdBeatsInput::gdBeatsInput()
+gdBeatsInput::gdBeatsInput(int beats, int bars)
: gdWindow(u::gui::centerWindowX(180), u::gui::centerWindowY(36), 180, 36, "Beats")
{
set_modal();
- beats = new geInput(8, 8, 43, G_GUI_UNIT);
- bars = new geInput(beats->x() + beats->w() + 4, 8, 43, G_GUI_UNIT);
- ok = new geButton(bars->x() + bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok");
+ m_beats = new geInput(8, 8, 43, G_GUI_UNIT);
+ m_bars = new geInput(m_beats->x() + m_beats->w() + 4, 8, 43, G_GUI_UNIT);
+ m_ok = new geButton(m_bars->x() + m_bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok");
end();
- beats->maximum_size(2);
- beats->value(std::to_string(m::clock::getBeats()).c_str());
- beats->type(FL_INT_INPUT);
+ m_beats->maximum_size(2);
+ m_beats->value(std::to_string(beats).c_str());
+ m_beats->type(FL_INT_INPUT);
- bars->maximum_size(2);
- bars->value(std::to_string(m::clock::getBars()).c_str());
- bars->type(FL_INT_INPUT);
+ m_bars->maximum_size(2);
+ m_bars->value(std::to_string(bars).c_str());
+ m_bars->type(FL_INT_INPUT);
- ok->shortcut(FL_Enter);
- ok->callback(cb_update, (void*)this);
+ m_ok->shortcut(FL_Enter);
+ m_ok->callback(cb_update, (void*)this);
u::gui::setFavicon(this);
setId(WID_BEATS);
void gdBeatsInput::cb_update()
{
- if (!strcmp(beats->value(), "") || !strcmp(bars->value(), ""))
+ if (!strcmp(m_beats->value(), "") || !strcmp(m_bars->value(), ""))
return;
- c::main::setBeats(atoi(beats->value()), atoi(bars->value()));
+ c::main::setBeats(atoi(m_beats->value()), atoi(m_bars->value()));
do_callback();
}
-
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
class geButton;
class geCheck;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class gdBeatsInput : public gdWindow
{
public:
- gdBeatsInput();
+ gdBeatsInput(int beats, int bars);
- private:
+private:
static void cb_update(Fl_Widget* /*w*/, void* p);
void cb_update();
- geInput* beats;
- geInput* bars;
- geButton* ok;
+ geInput* m_beats;
+ geInput* m_bars;
+ geButton* m_ok;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "bpmInput.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/mixer.h"
*
* -------------------------------------------------------------------------- */
-#include "browserBase.h"
+#include "gui/dialogs/browser/browserBase.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/graphics.h"
#include "utils/fs.h"
#include "utils/gui.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path,
- std::function<void(void*)> callback, ID channelId)
-: gdWindow(m::conf::conf.browserX, m::conf::conf.browserY, m::conf::conf.browserW,
- m::conf::conf.browserH, title.c_str())
+ std::function<void(void*)> callback, ID channelId, m::Conf::Data& c)
+: gdWindow(c.browserX, c.browserY, c.browserW,
+ c.browserH, title.c_str())
, m_callback(callback)
+, m_conf(c)
, m_channelId(channelId)
{
set_non_modal();
browser = new geBrowser(8, groupTop->y() + groupTop->h() + 8, w() - 16, h() - 101);
browser->loadDir(path);
- if (path == m::conf::conf.browserLastPath)
- browser->preselect(m::conf::conf.browserPosition, m::conf::conf.browserLastValue);
+ if (path == m_conf.browserLastPath)
+ browser->preselect(m_conf.browserPosition, m_conf.browserLastValue);
Fl_Group* groupButtons = new Fl_Group(8, browser->y() + browser->h() + 8, w() - 16, 20);
ok = new geButton(w() - 88, groupButtons->y(), 80, 20);
gdBrowserBase::~gdBrowserBase()
{
- m::conf::conf.browserX = x();
- m::conf::conf.browserY = y();
- m::conf::conf.browserW = w();
- m::conf::conf.browserH = h();
- m::conf::conf.browserPosition = browser->position();
- m::conf::conf.browserLastPath = browser->getCurrentDir();
- m::conf::conf.browserLastValue = browser->value();
+ m_conf.browserX = x();
+ m_conf.browserY = y();
+ m_conf.browserW = w();
+ m_conf.browserH = h();
+ m_conf.browserPosition = browser->position();
+ m_conf.browserLastPath = browser->getCurrentDir();
+ m_conf.browserLastValue = browser->value();
}
/* -------------------------------------------------------------------------- */
{
m_callback((void*)this);
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_BROWSER_BASE_H
#define GD_BROWSER_BASE_H
+#include "core/conf.h"
#include "core/types.h"
#include "gui/dialogs/window.h"
#include <functional>
class geInput;
class geProgress;
-namespace giada
-{
-namespace m
+namespace giada::m
{
class Channel;
}
-namespace v
+
+namespace giada::v
{
class geBrowser;
class gdBrowserBase : public gdWindow
void showStatusBar();
void hideStatusBar();
- protected:
+protected:
gdBrowserBase(const std::string& title, const std::string& path,
- std::function<void(void*)> f, ID channelId);
+ std::function<void(void*)> f, ID channelId, m::Conf::Data&);
static void cb_up(Fl_Widget* /*w*/, void* p);
static void cb_close(Fl_Widget* /*w*/, void* p);
std::function<void(void*)> m_callback;
- ID m_channelId;
+ m::Conf::Data& m_conf;
+ ID m_channelId;
Fl_Group* groupTop;
geCheck* hiddenFiles;
geButton* updir;
geProgress* status;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "gui/elems/browser.h"
#include "utils/fs.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
gdBrowserDir::gdBrowserDir(const std::string& title, const std::string& path,
- std::function<void(void*)> cb)
-: gdBrowserBase(title, path, cb, 0)
+ std::function<void(void*)> cb, m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, 0, conf)
{
where->size(groupTop->w() - updir->w() - 8, 20);
browser->loadDir(path);
where->value(browser->getCurrentDir().c_str());
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_BROWSER_DIR_H
#define GD_BROWSER_DIR_H
+#include "core/conf.h"
#include "browserBase.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
class gdBrowserDir : public gdBrowserBase
{
public:
gdBrowserDir(const std::string& title, const std::string& path,
- std::function<void(void*)> cb);
+ std::function<void(void*)> cb, m::Conf::Data&);
- private:
+private:
static void cb_load(Fl_Widget* /*w*/, void* p);
static void cb_down(Fl_Widget* /*w*/, void* p);
void cb_load();
void cb_down();
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "gui/elems/browser.h"
#include "utils/fs.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
gdBrowserLoad::gdBrowserLoad(const std::string& title, const std::string& path,
- std::function<void(void*)> cb, ID channelId)
-: gdBrowserBase(title, path, cb, channelId)
+ std::function<void(void*)> cb, ID channelId, m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, channelId, conf)
{
where->size(groupTop->w() - updir->w() - 8, 20);
browser->loadDir(path);
where->value(browser->getCurrentDir().c_str());
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#define GD_BROWSER_LOAD_H
#include "browserBase.h"
+#include "core/conf.h"
-namespace giada
-{
-namespace m
+namespace giada::m
{
class Channel;
}
-namespace v
+
+namespace giada::v
{
class gdBrowserLoad : public gdBrowserBase
{
public:
gdBrowserLoad(const std::string& title, const std::string& path,
- std::function<void(void*)> cb, ID channelId);
+ std::function<void(void*)> cb, ID channelId, m::Conf::Data&);
- private:
+private:
static void cb_load(Fl_Widget* /*w*/, void* p);
static void cb_down(Fl_Widget* /*w*/, void* p);
void cb_load();
void cb_down();
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "gui/elems/browser.h"
#include "utils/fs.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
gdBrowserSave::gdBrowserSave(const std::string& title, const std::string& path,
- const std::string& name_, std::function<void(void*)> cb, ID channelId)
-: gdBrowserBase(title, path, cb, channelId)
+ const std::string& name_, std::function<void(void*)> cb, ID channelId,
+ m::Conf::Data& conf)
+: gdBrowserBase(title, path, cb, channelId, conf)
{
where->size(groupTop->w() - 236, 20);
{
fireCallback();
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_BROWSER_SAVE_H
#define GD_BROWSER_SAVE_H
+#include "core/conf.h"
#include "browserBase.h"
class geInput;
-namespace giada
-{
-namespace m
+namespace giada::m
{
class Channel;
}
-namespace v
+
+namespace giada::v
{
class gdBrowserSave : public gdBrowserBase
{
public:
gdBrowserSave(const std::string& title, const std::string& path,
const std::string& name, std::function<void(void*)> cb,
- ID channelId);
+ ID channelId, m::Conf::Data&);
std::string getName() const;
- private:
+private:
geInput* name;
static void cb_down(Fl_Widget* /*w*/, void* p);
void cb_down();
void cb_save();
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "utils/gui.h"
#include <FL/Fl_Tabs.H>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdConfig::gdConfig(int w, int h)
+gdConfig::gdConfig(int w, int h, m::Conf::Data& conf)
: gdWindow(u::gui::centerWindowX(w), u::gui::centerWindowY(h), w, h, "Configuration")
{
Fl_Tabs* tabs = new Fl_Tabs(8, 8, w - 16, h - 44);
tabAudio = new geTabAudio(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
tabMidi = new geTabMidi(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
- tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
- tabMisc = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
+ tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40, conf);
+ tabMisc = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20);
#ifdef WITH_VST
tabPlugins = new geTabPlugins(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40);
#endif
{
do_callback();
}
-
-/* -------------------------------------------------------------------------- */
-
-#ifdef WITH_VST
-
-void gdConfig::refreshVstPath()
-{
- tabPlugins->refreshVstPath();
-}
-
-#endif
-
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
#ifndef GD_CONFIG_H
#define GD_CONFIG_H
+#include "core/conf.h"
#include "window.h"
class geButton;
class geInput;
class geBox;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChoice;
class geTabAudio;
class gdConfig : public gdWindow
{
public:
- gdConfig(int w, int h);
-
-#ifdef WITH_VST
- void refreshVstPath();
-#endif
+ gdConfig(int w, int h, m::Conf::Data&);
geTabAudio* tabAudio;
geTabBehaviors* tabBehaviors;
geButton* save;
geButton* cancel;
- private:
+private:
static void cb_save_config(Fl_Widget* /*w*/, void* p);
static void cb_cancel(Fl_Widget* /*w*/, void* p);
void cb_save_config();
void cb_cancel();
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
class geBox;
class geButton;
-namespace giada
-{
-namespace c
-{
-namespace channel
+namespace giada::c::channel
{
struct Data;
}
-} // namespace c
-namespace v
+
+namespace giada::v
{
class gdKeyGrabber : public gdWindow
{
int handle(int e) override;
void rebuild() override;
- private:
+private:
static void cb_clear(Fl_Widget* /*w*/, void* p);
static void cb_cancel(Fl_Widget* /*w*/, void* p);
void cb_clear();
geButton* m_clear;
geButton* m_cancel;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "mainWindow.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/init.h"
namespace giada::v
{
-gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv)
+gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv, m::Conf::Data& c)
: gdWindow(W, H, title)
+, m_conf(c)
{
+
Fl::visible_focus(0);
Fl::background(25, 25, 25); // TODO use G_COLOR_GREY_1
Fl_Tooltip::color(G_COLOR_GREY_1);
Fl_Tooltip::textcolor(G_COLOR_LIGHT_2);
Fl_Tooltip::size(G_GUI_FONT_SIZE_BASE);
- Fl_Tooltip::enable(m::conf::conf.showTooltips);
+ Fl_Tooltip::enable(m_conf.showTooltips);
size_range(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT);
gdMainWindow::~gdMainWindow()
{
- m::conf::conf.mainWindowX = x();
- m::conf::conf.mainWindowY = y();
- m::conf::conf.mainWindowW = w();
- m::conf::conf.mainWindowH = h();
+ m_conf.mainWindowX = x();
+ m_conf.mainWindowY = y();
+ m_conf.mainWindowW = w();
+ m_conf.mainWindowH = h();
}
/* -------------------------------------------------------------------------- */
#define GD_MAINWINDOW_H
#include "window.h"
+#include "core/conf.h"
namespace giada::v
{
class gdMainWindow : public gdWindow
{
public:
- gdMainWindow(int w, int h, const char* title, int argc, char** argv);
+ gdMainWindow(int w, int h, const char* title, int argc, char** argv, m::Conf::Data&);
~gdMainWindow();
void refresh() override;
geMainIO* mainIO;
geMainTimer* mainTimer;
geMainTransport* mainTransport;
+
+private:
+ m::Conf::Data& m_conf;
};
} // namespace giada::v
#include "core/conf.h"
#include "glue/io.h"
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title)
+gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title,
+ m::Conf::Data& c)
: gdWindow(x, y, w, h, title)
+, m_conf(c)
{
}
{
c::io::stopMidiLearn();
- m::conf::conf.midiInputX = x();
- m::conf::conf.midiInputY = y();
- m::conf::conf.midiInputW = w();
- m::conf::conf.midiInputH = h();
+ m_conf.midiInputX = x();
+ m_conf.midiInputY = y();
+ m_conf.midiInputW = w();
+ m_conf.midiInputH = h();
}
/* -------------------------------------------------------------------------- */
{
do_callback();
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_MIDI_INPUT_BASE_H
#define GD_MIDI_INPUT_BASE_H
+#include "core/conf.h"
#include "gui/dialogs/window.h"
#include "gui/elems/midiIO/midiLearner.h"
class geButton;
class geCheck;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChoice;
class gdMidiInputBase : public gdWindow
public:
virtual ~gdMidiInputBase();
- protected:
- gdMidiInputBase(int x, int y, int w, int h, const char* title = "");
+protected:
+ gdMidiInputBase(int x, int y, int w, int h, const char* title, m::Conf::Data&);
static void cb_close(Fl_Widget* /*w*/, void* p);
void cb_close();
+ m::Conf::Data& m_conf;
+
geButton* m_ok;
geCheck* m_enable;
geChoice* m_channel;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "midiInputChannel.h"
#include "utils/string.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
geChannelLearnerPack::geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& channel)
: geMidiLearnerPack(x, y, "Channel")
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-gdMidiInputChannel::gdMidiInputChannel(ID channelId)
-: gdMidiInputBase(m::conf::conf.midiInputX,
- m::conf::conf.midiInputY,
- m::conf::conf.midiInputW,
- m::conf::conf.midiInputH)
+gdMidiInputChannel::gdMidiInputChannel(ID channelId, m::Conf::Data& c)
+: gdMidiInputBase(c.midiInputX, c.midiInputY, c.midiInputW, c.midiInputH, "", c)
, m_channelId(channelId)
, m_data(c::io::channel_getInputData(channelId))
{
c::io::channel_setMidiInputFilter(m_data.channelId,
m_channel->value() == 0 ? -1 : m_channel->value() - 1);
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_MIDI_INPUT_CHANNEL_H
#define GD_MIDI_INPUT_CHANNEL_H
+#include "core/conf.h"
#include "glue/io.h"
#include "gui/elems/midiIO/midiLearnerPack.h"
#include "midiInputBase.h"
class geCheck;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChoice;
class geScrollPack;
class gdMidiInputChannel : public gdMidiInputBase
{
public:
- gdMidiInputChannel(ID channelId);
+ gdMidiInputChannel(ID channelId, m::Conf::Data&);
void rebuild() override;
- private:
+private:
static void cb_enable(Fl_Widget* /*w*/, void* p);
static void cb_setChannel(Fl_Widget* /*w*/, void* p);
static void cb_veloAsVol(Fl_Widget* /*w*/, void* p);
geScrollPack* m_container;
geCheck* m_veloAsVol;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "utils/gui.h"
#include <FL/Fl_Pack.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geMasterLearnerPack::geMasterLearnerPack(int x, int y)
: geMidiLearnerPack(x, y)
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-gdMidiInputMaster::gdMidiInputMaster()
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+gdMidiInputMaster::gdMidiInputMaster(m::Conf::Data& c)
+: gdMidiInputBase(c.midiInputX, c.midiInputY, 300, 284, "MIDI Input Setup (global)", c)
{
end();
{
c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1);
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef GD_MIDI_INPUT_MASTER_H
#define GD_MIDI_INPUT_MASTER_H
+#include "core/conf.h"
#include "glue/io.h"
#include "gui/elems/midiIO/midiLearnerPack.h"
#include "midiInputBase.h"
class geCheck;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChoice;
class geMasterLearnerPack : public geMidiLearnerPack
class gdMidiInputMaster : public gdMidiInputBase
{
public:
- gdMidiInputMaster();
+ gdMidiInputMaster(m::Conf::Data&);
void rebuild() override;
- private:
+private:
static void cb_enable(Fl_Widget* /*w*/, void* p);
static void cb_setChannel(Fl_Widget* /*w*/, void* p);
void cb_enable();
geMasterLearnerPack* m_learners;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "pluginChooser.h"
#include "core/conf.h"
-#include "core/plugins/pluginHost.h"
-#include "core/plugins/pluginManager.h"
#include "glue/plugin.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/plugin/pluginBrowser.h"
#include "utils/gui.h"
-namespace giada
+namespace giada::v
{
-namespace v
-{
-gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
+gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId, m::Conf::Data& c)
: gdWindow(X, Y, W, H, "Available plugins")
+, m_conf(c)
, m_channelId(channelId)
{
/* top area */
sortMethod->add("Category");
sortMethod->add("Manufacturer");
sortMethod->callback(cb_sort, (void*)this);
- sortMethod->value(m::conf::conf.pluginSortMethod);
+ sortMethod->value(m_conf.pluginSortMethod);
addBtn->callback(cb_add, (void*)this);
addBtn->shortcut(FL_Enter);
gdPluginChooser::~gdPluginChooser()
{
- m::conf::conf.pluginChooserX = x();
- m::conf::conf.pluginChooserY = y();
- m::conf::conf.pluginChooserW = w();
- m::conf::conf.pluginChooserH = h();
- m::conf::conf.pluginSortMethod = sortMethod->value();
+ m_conf.pluginChooserX = x();
+ m_conf.pluginChooserY = y();
+ m_conf.pluginChooserW = w();
+ m_conf.pluginChooserH = h();
+ m_conf.pluginSortMethod = sortMethod->value();
}
/* -------------------------------------------------------------------------- */
void gdPluginChooser::cb_sort()
{
- m::pluginManager::sortPlugins(static_cast<m::pluginManager::SortMethod>(sortMethod->value()));
+ c::plugin::sortPlugins(static_cast<m::PluginManager::SortMethod>(sortMethod->value()));
browser->refresh();
}
c::plugin::addPlugin(pluginIndex, m_channelId);
do_callback();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif // #ifdef WITH_VST
#ifndef GD_PLUGIN_CHOOSER_H
#define GD_PLUGIN_CHOOSER_H
+#include "core/conf.h"
#include "core/types.h"
#include "window.h"
#include <FL/Fl.H>
class geButton;
class geButton;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChoice;
class gePluginBrowser;
class gdPluginChooser : public gdWindow
{
public:
- gdPluginChooser(int x, int y, int w, int h, ID channelId);
+ gdPluginChooser(int x, int y, int w, int h, ID channelId, m::Conf::Data&);
~gdPluginChooser();
- private:
+private:
static void cb_close(Fl_Widget* /*w*/, void* p);
static void cb_add(Fl_Widget* /*w*/, void* p);
static void cb_sort(Fl_Widget* /*w*/, void* p);
void cb_add();
void cb_sort();
+ m::Conf::Data& m_conf;
+
geChoice* sortMethod;
geButton* addBtn;
geButton* cancelBtn;
ID m_channelId;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#ifdef WITH_VST
-#include "pluginList.h"
+#include "gui/dialogs/pluginList.h"
#include "core/conf.h"
#include "core/const.h"
+#include "glue/layout.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/liquidScroll.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/mainIO.h"
#include "gui/elems/plugin/pluginElement.h"
-#include "mainWindow.h"
-#include "pluginChooser.h"
#include "utils/gui.h"
#include "utils/string.h"
#include <cassert>
#include <string>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
-gdPluginList::gdPluginList(ID channelId)
-: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204)
+gdPluginList::gdPluginList(ID channelId, m::Conf::Data& c)
+: gdWindow(c.pluginListX, c.pluginListY, 468, 204)
+, m_conf(c)
, m_channelId(channelId)
{
end();
list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN,
- w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2));
+ w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2),
+ Direction::VERTICAL);
list->end();
add(list);
resizable(list);
gdPluginList::~gdPluginList()
{
- m::conf::conf.pluginListX = x();
- m::conf::conf.pluginListY = y();
+ m_conf.pluginListX = x();
+ m_conf.pluginListY = y();
}
/* -------------------------------------------------------------------------- */
{
m_plugins = c::plugin::getPlugins(m_channelId);
- if (m_plugins.channelId == m::mixer::MASTER_OUT_CHANNEL_ID)
+ if (m_plugins.channelId == m::Mixer::MASTER_OUT_CHANNEL_ID)
label("Master Out Plug-ins");
- else if (m_plugins.channelId == m::mixer::MASTER_IN_CHANNEL_ID)
+ else if (m_plugins.channelId == m::Mixer::MASTER_IN_CHANNEL_ID)
label("Master In Plug-ins");
else
{
void gdPluginList::cb_addPlugin()
{
- int wx = m::conf::conf.pluginChooserX;
- int wy = m::conf::conf.pluginChooserY;
- int ww = m::conf::conf.pluginChooserW;
- int wh = m::conf::conf.pluginChooserH;
- u::gui::openSubWindow(G_MainWin, new v::gdPluginChooser(wx, wy, ww, wh, m_plugins.channelId), WID_FX_CHOOSER);
+ c::layout::openPluginChooser(m_plugins.channelId);
}
/* -------------------------------------------------------------------------- */
prev = 0;
return *static_cast<gePluginElement*>(list->child(prev));
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif // #ifdef WITH_VST
#ifndef GD_PLUGINLIST_H
#define GD_PLUGINLIST_H
+#include "core/conf.h"
#include "glue/plugin.h"
#include "window.h"
-class geLiquidScroll;
class geButton;
-namespace giada
-{
-namespace v
+namespace giada::v
{
+class geLiquidScroll;
class gePluginElement;
class gdPluginList : public gdWindow
{
public:
- gdPluginList(ID channelId);
+ gdPluginList(ID channelId, m::Conf::Data&);
~gdPluginList();
void rebuild() override;
const gePluginElement& getNextElement(const gePluginElement& curr) const;
const gePluginElement& getPrevElement(const gePluginElement& curr) const;
- private:
+private:
static void cb_addPlugin(Fl_Widget* /*w*/, void* p);
void cb_addPlugin();
+ m::Conf::Data& m_conf;
+
geButton* addPlugin;
geLiquidScroll* list;
ID m_channelId;
c::plugin::Plugins m_plugins;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
set_non_modal();
m_list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN,
- w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2));
+ w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2),
+ Direction::VERTICAL);
m_list->type(Fl_Scroll::VERTICAL_ALWAYS);
m_list->begin();
class geBox;
class geSlider;
-class geLiquidScroll;
namespace giada::c::plugin
{
}
namespace giada::v
{
+class geLiquidScroll;
class gdPluginWindow : public gdWindow
{
public:
void updateParameters(bool changeSlider = false);
- private:
+private:
const c::plugin::Plugin& m_plugin;
geLiquidScroll* m_list;
#ifdef WITH_VST
-#include "pluginWindowGUI.h"
+#include "gui/dialogs/pluginWindowGUI.h"
#include "core/const.h"
#include "glue/plugin.h"
#include "utils/gui.h"
#import "utils/cocoa.h" // objective-c
#endif
-namespace giada
-{
-namespace v
+namespace giada::v
{
gdPluginWindowGUI::gdPluginWindowGUI(c::plugin::Plugin& p)
#ifdef G_OS_MAC
: gdWindow(320, 200)
#endif
, m_plugin(p)
-, m_ui(nullptr)
{
- show();
-
-#if defined(G_OS_LINUX) || defined(G_OS_MAC) || defined(G_OS_FREEBSD)
-
- /* Fl_Window::show() is not guaranteed to show and draw the window on all
- platforms immediately. Instead this is done in the background; particularly on
- X11 it will take a few messages (client server roundtrips) to display the
- window. Usually this small delay doesn't matter, but in some cases you may
- want to have the window instantiated and displayed synchronously. Currently
- (as of FLTK 1.3.4) this method has an effect on X11 and Mac OS.
-
- http://www.fltk.org/doc-1.3/classFl__Window.html#aafbec14ca8ff8abdaff77a35ebb23dd8 */
+ /* Make sure to wait_for_expose() before opening the editor: the window must
+ be exposed and visible first. Don't fuck with multithreading! */
+ copy_label(m_plugin.name.c_str());
+ show();
wait_for_expose();
+ openEditor();
Fl::flush();
-
-#endif
-
- u::log::print("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n",
- (void*)this, (void*)fl_xid(this));
-
-#ifdef G_OS_MAC
-
- void* cocoaWindow = (void*)fl_xid(this);
- openEditor(cocoa_getViewFromWindow(cocoaWindow));
-
-#else
-
- openEditor((void*)fl_xid(this));
-
- int pluginW = m_ui->getWidth();
- int pluginH = m_ui->getHeight();
-
- resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
-
- m_plugin.setResizeCallback([this](int w, int h) {
- resize(x(), y(), w, h);
- });
-
-#endif
-
-#ifdef G_OS_LINUX
- Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this);
-#endif
-
- copy_label(m_plugin.name.c_str());
}
/* -------------------------------------------------------------------------- */
gdPluginWindowGUI::~gdPluginWindowGUI()
{
- cb_close();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void gdPluginWindowGUI::cb_close(Fl_Widget* /*v*/, void* p) { ((gdPluginWindowGUI*)p)->cb_close(); }
-void gdPluginWindowGUI::cb_refresh(void* data) { ((gdPluginWindowGUI*)data)->cb_refresh(); }
-
-/* -------------------------------------------------------------------------- */
-
-void gdPluginWindowGUI::cb_close()
-{
-#ifdef G_OS_LINUX
- Fl::remove_timeout(cb_refresh);
-#endif
+ c::plugin::stopDispatchLoop();
closeEditor();
u::log::print("[gdPluginWindowGUI::__cb_close] GUI closed, this=%p\n", (void*)this);
}
/* -------------------------------------------------------------------------- */
-void gdPluginWindowGUI::cb_refresh()
+void gdPluginWindowGUI::openEditor()
{
- m::pluginHost::runDispatchLoop();
- Fl::repeat_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this);
-}
-
-/* -------------------------------------------------------------------------- */
+ u::log::print("[gdPluginWindowGUI] Opening editor, this=%p, xid=%p\n",
+ this, reinterpret_cast<void*>(fl_xid(this)));
-void gdPluginWindowGUI::openEditor(void* parent)
-{
- m_ui = m_plugin.createEditor();
- if (m_ui == nullptr)
+ m_editor.reset(m_plugin.createEditor());
+ if (m_editor == nullptr)
{
u::log::print("[gdPluginWindowGUI::openEditor] unable to create editor!\n");
return;
}
- m_ui->setOpaque(true);
- m_ui->addToDesktop(0, parent);
+ m_editor->setOpaque(true);
+
+#ifdef G_OS_MAC
+
+ void* cocoaWindow = (void*)fl_xid(this);
+ m_editor->addToDesktop(0, cocoa_getViewFromWindow(cocoaWindow));
+
+#else
+
+ m_editor->addToDesktop(0, reinterpret_cast<void*>(fl_xid(this)));
+
+#endif
+
+ const int pluginW = m_editor->getWidth();
+ const int pluginH = m_editor->getHeight();
+
+ resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
+
+ m_plugin.setResizeCallback([this](int w, int h) {
+ resize(x(), y(), w, h);
+ });
+
+ c::plugin::startDispatchLoop();
}
/* -------------------------------------------------------------------------- */
void gdPluginWindowGUI::closeEditor()
{
- delete m_ui;
- m_ui = nullptr;
+ m_editor.reset();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif // #ifdef WITH_VST
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
-namespace giada
-{
-namespace c
-{
-namespace plugin
+namespace giada::c::plugin
{
struct Plugin;
}
-} // namespace c
-namespace v
+
+namespace giada::v
{
class gdPluginWindowGUI : public gdWindow
{
gdPluginWindowGUI(c::plugin::Plugin&);
~gdPluginWindowGUI();
- private:
- static void cb_close(Fl_Widget* /*w*/, void* p);
- static void cb_refresh(void* data);
- void cb_close();
- void cb_refresh();
-
- void openEditor(void* parent);
+ void openEditor();
void closeEditor();
- c::plugin::Plugin& m_plugin;
-
- juce::AudioProcessorEditor* m_ui;
+private:
+ c::plugin::Plugin& m_plugin;
+ std::unique_ptr<juce::AudioProcessorEditor> m_editor;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
-#endif // include guard
+#endif
#endif // #ifdef WITH_VST
#include <cassert>
#include <cmath>
+#ifdef G_OS_WINDOWS
+#undef IN
+#undef OUT
+#endif
+
namespace giada::v
{
-gdSampleEditor::gdSampleEditor(ID channelId)
-: gdWindow(m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY,
- m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH)
+gdSampleEditor::gdSampleEditor(ID channelId, m::Conf::Data& c)
+: gdWindow(c.sampleEditorX, c.sampleEditorY, c.sampleEditorW, c.sampleEditorH)
, m_channelId(channelId)
+, m_conf(c)
{
end();
gePack* upperBar = createUpperBar();
waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y() + upperBar->h() + G_GUI_OUTER_MARGIN,
- w() - 16, h() - 168);
+ w() - 16, h() - 168, m_conf.sampleEditorGridOn, m_conf.sampleEditorGridVal);
gePack* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y() + waveTools->h() + G_GUI_OUTER_MARGIN,
h() - waveTools->h() - upperBar->h() - 32);
gdSampleEditor::~gdSampleEditor()
{
- m::conf::conf.sampleEditorX = x();
- m::conf::conf.sampleEditorY = y();
- m::conf::conf.sampleEditorW = w();
- m::conf::conf.sampleEditorH = h();
- m::conf::conf.sampleEditorGridVal = atoi(grid->text());
- m::conf::conf.sampleEditorGridOn = snap->value();
+ m_conf.sampleEditorX = x();
+ m_conf.sampleEditorY = y();
+ m_conf.sampleEditorW = w();
+ m_conf.sampleEditorH = h();
+ m_conf.sampleEditorGridVal = atoi(grid->text());
+ m_conf.sampleEditorGridOn = snap->value();
c::sampleEditor::stopPreview();
c::sampleEditor::cleanupPreview();
grid->add("64");
grid->copy_tooltip("Grid frequency");
- if (m::conf::conf.sampleEditorGridVal == 0)
+ if (m_conf.sampleEditorGridVal == 0)
grid->value(0);
else
- grid->value(grid->find_item(u::string::iToString(m::conf::conf.sampleEditorGridVal).c_str()));
+ grid->value(grid->find_item(u::string::iToString(m_conf.sampleEditorGridVal).c_str()));
grid->callback(cb_changeGrid, (void*)this);
- snap->value(m::conf::conf.sampleEditorGridOn);
+ snap->value(m_conf.sampleEditorGridOn);
snap->copy_tooltip("Snap to grid");
snap->callback(cb_enableSnap, (void*)this);
#define GD_EDITOR_H
#include "core/types.h"
+#include "core/conf.h"
#include "glue/sampleEditor.h"
#include "window.h"
{
class Wave;
}
+
namespace giada::v
{
class geChoice;
friend class geWaveform;
public:
- gdSampleEditor(ID channelId);
+ gdSampleEditor(ID channelId, m::Conf::Data&);
~gdSampleEditor();
void rebuild() override;
geCheck* loop;
geBox* info;
- private:
+private:
gePack* createUpperBar();
gePack* createBottomBar(int x, int y, int h);
geGroup* createPreviewBox(int x, int y, int h);
ID m_channelId;
c::sampleEditor::Data m_data;
+ m::Conf::Data& m_conf;
};
} // namespace giada::v
#include "window.h"
#include "utils/log.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
void cb_window_closer(Fl_Widget* /*v*/, void* p)
{
return subWindows.at(j);
return nullptr;
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
#include <FL/Fl_Double_Window.H>
#include <vector>
-namespace giada
-{
-namespace v
+namespace giada::v
{
/* cb_window_closer
Callback for closing windows. Deletes the widget (delete). */
gdWindow* getParent();
gdWindow* getChild(int id);
- protected:
+protected:
std::vector<gdWindow*> subWindows;
int id;
gdWindow* parent;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "gui/dialogs/mainWindow.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/ui.h"
#include <FL/Fl.H>
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
-namespace giada
+namespace giada::v
{
-namespace v
+Dispatcher::Dispatcher()
+: m_backspace(false)
+, m_end(false)
+, m_enter(false)
+, m_space(false)
+, m_esc(false)
+, m_key(false)
{
-namespace dispatcher
-{
-namespace
-{
-bool backspace_ = false;
-bool end_ = false;
-bool enter_ = false;
-bool space_ = false;
-bool esc_ = false;
-bool key_ = false;
-
-std::function<void()> signalCb_ = nullptr;
+}
/* -------------------------------------------------------------------------- */
-void perform_(ID channelId, int event)
+void Dispatcher::perform(ID channelId, int event) const
{
if (event == FL_KEYDOWN)
{
/* Walk channels array, trying to match button's bound key with the event. If
found, trigger the key-press/key-release function. */
-void dispatchChannels_(int event)
+void Dispatcher::dispatchChannels(int event) const
{
- G_MainWin->keyboard->forEachChannel([=](geChannel& c) {
+ g_ui.mainWindow->keyboard->forEachChannel([=](geChannel& c) {
if (c.handleKey(event))
- perform_(c.getData().id, event);
+ perform(c.getData().id, event);
});
}
/* -------------------------------------------------------------------------- */
-void triggerSignalCb_()
+void Dispatcher::dispatchKey(int event)
{
- if (signalCb_ == nullptr)
- return;
- signalCb_();
- signalCb_ = nullptr;
-}
-} // namespace
+ assert(onEventOccured != nullptr);
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void dispatchKey(int event)
-{
/* These events come from the keyboard, not from a direct interaction on the
UI with the mouse/touch. */
if (event == FL_KEYDOWN)
{
- if (Fl::event_key() == FL_BackSpace && !backspace_)
+ if (Fl::event_key() == FL_BackSpace && !m_backspace)
{
- backspace_ = true;
+ m_backspace = true;
c::events::rewindSequencer(Thread::MAIN);
}
- else if (Fl::event_key() == FL_End && !end_)
+ else if (Fl::event_key() == FL_End && !m_end)
{
- end_ = true;
+ m_end = true;
c::events::toggleInputRecording();
}
- else if (Fl::event_key() == FL_Enter && !enter_)
+ else if (Fl::event_key() == FL_Enter && !m_enter)
{
- enter_ = true;
+ m_enter = true;
c::events::toggleActionRecording();
}
- else if (Fl::event_key() == ' ' && !space_)
+ else if (Fl::event_key() == ' ' && !m_space)
{
- space_ = true;
+ m_space = true;
c::events::toggleSequencer(Thread::MAIN);
}
- else if (Fl::event_key() == FL_Escape && !esc_)
+ else if (Fl::event_key() == FL_Escape && !m_esc)
{
- esc_ = true;
+ m_esc = true;
m::init::closeMainWindow();
}
- else if (!key_)
+ else if (!m_key)
{
- key_ = true;
- triggerSignalCb_();
- dispatchChannels_(event);
+ m_key = true;
+ onEventOccured();
+ dispatchChannels(event);
}
}
else if (event == FL_KEYUP)
{
if (Fl::event_key() == FL_BackSpace)
- backspace_ = false;
+ m_backspace = false;
else if (Fl::event_key() == FL_End)
- end_ = false;
+ m_end = false;
else if (Fl::event_key() == ' ')
- space_ = false;
+ m_space = false;
else if (Fl::event_key() == FL_Enter)
- enter_ = false;
+ m_enter = false;
else if (Fl::event_key() == FL_Escape)
- esc_ = false;
+ m_esc = false;
else
{
- key_ = false;
- dispatchChannels_(event);
+ m_key = false;
+ dispatchChannels(event);
}
}
}
/* -------------------------------------------------------------------------- */
-void dispatchTouch(const geChannel& gch, bool status)
+void Dispatcher::dispatchTouch(const geChannel& gch, bool status)
{
- triggerSignalCb_();
- perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
-}
+ assert(onEventOccured != nullptr);
-/* -------------------------------------------------------------------------- */
-
-void setSignalCallback(std::function<void()> f)
-{
- signalCb_ = f;
+ onEventOccured();
+ perform(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
}
-
-} // namespace dispatcher
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#ifndef G_V_DISPATCHER_H
#define G_V_DISPATCHER_H
+#include "core/types.h"
#include <functional>
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geChannel;
-
-namespace dispatcher
+class Dispatcher final
{
-/* dispatchKey
-Processes a key pressed on the physical keyboard. */
+public:
+ Dispatcher();
+
+ /* dispatchKey
+ Processes a key pressed on the physical keyboard. */
+
+ void dispatchKey(int event);
+
+ /* dispatchTouch
+ Processes a mouse click/touch event. */
+
+ void dispatchTouch(const geChannel& gch, bool status);
+
+ /* onEventOccured
+ Callback fired when a key has been pressed or a mouse button clicked. */
+
+ std::function<void()> onEventOccured;
-void dispatchKey(int event);
+private:
+ void perform(ID channelId, int event) const;
-/* dispatchTouch
-Processes a mouse click/touch event. */
+ /* dispatchChannels
+ Walks channels array, trying to match button's bound key with the event. If
+ found, trigger the key-press/key-release function. */
-void dispatchTouch(const geChannel& gch, bool status);
+ void dispatchChannels(int event) const;
-void setSignalCallback(std::function<void()> f);
-} // namespace dispatcher
-} // namespace v
-} // namespace giada
+ bool m_backspace;
+ bool m_end;
+ bool m_enter;
+ bool m_space;
+ bool m_esc;
+ bool m_key;
+};
+} // namespace giada::v
#endif
\ No newline at end of file
#ifndef GE_BASE_ACTION_H
#define GE_BASE_ACTION_H
-#include "core/recorder.h"
#include "core/types.h"
+#include "src/core/actions/actions.h"
#include <FL/Fl_Box.H>
namespace giada::m
m::Action a1;
m::Action a2;
- protected:
+protected:
bool m_resizable;
};
} // namespace giada::v
#include "gui/dialogs/actionEditor/baseActionEditor.h"
#include "baseAction.h"
#include "baseActionEditor.h"
-#include "core/clock.h"
#include "core/const.h"
+#include "core/sequencer.h"
#include "gridTool.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h,
gdBaseActionEditor* base)
/* Draw grid, beats and bars. A grid set to 1 has a cell size == beat, so
painting it is useless. */
- if (m_base->gridTool->getValue() > 1)
+ if (m_base->gridTool.getValue() > 1)
{
fl_color(G_COLOR_GREY_3);
- drawVerticals(m_base->gridTool->getCellSize());
+ drawVerticals(m_base->gridTool.getCellSize());
}
fl_color(G_COLOR_GREY_4);
- drawVerticals(m::clock::getFramesInBeat());
+ drawVerticals(m_data->framesInBeat);
fl_color(G_COLOR_LIGHT_1);
- drawVerticals(m::clock::getFramesInBar());
+ drawVerticals(m_data->framesInBar);
/* Cover unused area. Avoid drawing cover if width == 0 (i.e. beats are 32). */
{
/* Start drawing from steps, not from 0. The zero-th element is always
graphically useless. */
- for (Frame i = steps; i < m::clock::getFramesInLoop(); i += steps)
+ for (Frame i = steps; i < m_data->framesInLoop; i += steps)
{
Pixel p = m_base->frameToPixel(i) + x();
fl_line(p, y() + 1, p, y() + h() - 2);
m_action = nullptr;
return ret;
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
{
struct Data;
}
+
namespace giada::v
{
class gdBaseActionEditor;
geBaseAction* getActionAtCursor() const;
- protected:
+protected:
geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*);
c::actionEditor::Data* m_data;
virtual void onResizeAction() = 0;
virtual void onRefreshAction() = 0;
- private:
+private:
/* drawVerticals
Draws generic vertical lines (beats, bars, grid lines...). */
* -------------------------------------------------------------------------- */
#include "envelopeEditor.h"
-#include "core/action.h"
#include "core/conf.h"
#include "core/const.h"
-#include "core/recorder.h"
#include "envelopePoint.h"
#include "glue/actionEditor.h"
#include "glue/channel.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
#include "utils/log.h"
#include "utils/math.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <cassert>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b)
+: geBaseActionEditor(x, y, 200, 40, b)
{
copy_label(l);
}
/* -------------------------------------------------------------------------- */
-geEnvelopeEditor::~geEnvelopeEditor()
-{
- m::conf::conf.envelopeEditorH = h();
-}
-
-/* -------------------------------------------------------------------------- */
-
void geEnvelopeEditor::draw()
{
baseDraw();
m_base->rebuild();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#include "baseActionEditor.h"
-namespace giada
+namespace giada::v
{
-namespace m
-{
-class SampleChannel;
-}
-namespace v
-{
-class geEnvelopePoint;
class geEnvelopeEditor : public geBaseActionEditor
{
public:
geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*);
- ~geEnvelopeEditor();
-
void draw() override;
void rebuild(c::actionEditor::Data& d) override;
- private:
+private:
void onAddAction() override;
void onDeleteAction() override;
void onMoveAction() override;
bool isFirstPoint() const;
bool isLastPoint() const;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include "core/const.h"
#include <FL/fl_draw.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, m::Action a)
: geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, {})
{
fl_rectf(x(), y(), w(), h(), hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1);
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
#define GE_ENVELOPE_POINT_H
#include "baseAction.h"
-#include "core/recorder.h"
+#include "src/core/actions/actions.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geEnvelopePoint : public geBaseAction
{
void draw() override;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* --------------------------------------------------------------------------- */
-#include "gridTool.h"
-#include "core/clock.h"
+#include "gui/elems/actionEditor/gridTool.h"
#include "core/conf.h"
#include "gui/elems/basics/check.h"
#include "gui/elems/basics/choice.h"
namespace giada::v
{
-geGridTool::geGridTool(Pixel x, Pixel y)
+geGridTool::geGridTool(Pixel x, Pixel y, m::Conf::Data& c, Frame framesInBeat)
: Fl_Group(x, y, 80, 20)
+, m_conf(c)
+, m_framesInBeat(framesInBeat)
{
gridType = new geChoice(x, y, 40, 20);
gridType->add("1");
active = new geCheck(gridType->x() + gridType->w() + 4, y, 20, 20);
- gridType->value(m::conf::conf.actionEditorGridVal);
- active->value(m::conf::conf.actionEditorGridOn);
+ gridType->value(m_conf.actionEditorGridVal);
+ active->value(m_conf.actionEditorGridOn);
end();
geGridTool::~geGridTool()
{
- m::conf::conf.actionEditorGridVal = gridType->value();
- m::conf::conf.actionEditorGridOn = active->value();
+ m_conf.actionEditorGridVal = gridType->value();
+ m_conf.actionEditorGridOn = active->value();
}
/* -------------------------------------------------------------------------- */
Frame geGridTool::getCellSize() const
{
- return m::clock::getFramesInBeat() / getValue();
+ return m_framesInBeat / getValue();
}
} // namespace giada::v
\ No newline at end of file
#ifndef GE_GRID_TOOL_H
#define GE_GRID_TOOL_H
+#include "core/conf.h"
#include "core/types.h"
#include <FL/Fl_Group.H>
class geGridTool : public Fl_Group
{
public:
- geGridTool(Pixel x, Pixel y);
+ geGridTool(Pixel x, Pixel y, m::Conf::Data&, Frame framesInBeat);
~geGridTool();
int getValue() const;
Frame getCellSize() const;
- private:
+private:
+ m::Conf::Data& m_conf;
+ Frame m_framesInBeat;
+
geChoice* gridType;
geCheck* active;
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "noteEditor.h"
-#include "core/conf.h"
-#include "core/const.h"
-#include "gui/dialogs/actionEditor/midiActionEditor.h"
-#include "pianoRoll.h"
-#include <FL/Fl.H>
-
-namespace giada
-{
-namespace v
-{
-geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base)
-: geScroll(x, y, 200, 422)
-, m_base(base)
-{
- end();
-
- type(Fl_Scroll::VERTICAL_ALWAYS);
- size(m_base->fullWidth, m::conf::conf.pianoRollH);
-
- pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, base);
- add(pianoRoll);
-}
-
-/* -------------------------------------------------------------------------- */
-
-geNoteEditor::~geNoteEditor()
-{
- m::conf::conf.pianoRollH = h();
- m::conf::conf.pianoRollY = pianoRoll->y();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geNoteEditor::scroll()
-{
- Pixel ey = Fl::event_y() - pianoRoll->pick;
-
- Pixel y1 = y();
- Pixel y2 = (y() + h()) - pianoRoll->h();
-
- if (ey > y1)
- ey = y1;
- else if (ey < y2)
- ey = y2;
-
- pianoRoll->position(x(), ey);
-
- redraw();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geNoteEditor::rebuild(c::actionEditor::Data& d)
-{
- size(m_base->fullWidth, h());
- pianoRoll->rebuild(d);
-}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef GE_NOTE_EDITOR_H
-#define GE_NOTE_EDITOR_H
-
-#include "core/types.h"
-#include "gui/elems/basics/scroll.h"
-
-namespace giada::c::actionEditor
-{
-struct Data;
-}
-namespace giada::v
-{
-class gdMidiActionEditor;
-class gePianoRoll;
-class geNoteEditor : public geScroll
-{
-public:
- geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base);
- ~geNoteEditor();
-
- void rebuild(c::actionEditor::Data& d);
- void scroll();
-
- gePianoRoll* pianoRoll;
-
- private:
- gdMidiActionEditor* m_base;
-};
-} // namespace giada::v
-
-#endif
* -------------------------------------------------------------------------- */
#include "pianoItem.h"
-#include "core/action.h"
#include "core/const.h"
#include "core/midiEvent.h"
+#include "src/core/actions/action.h"
#include "utils/math.h"
#include <FL/fl_draw.H>
#include "baseAction.h"
-namespace giada
-{
-namespace m
+namespace giada::m
{
struct Action;
}
-namespace v
-{
-class gdActionEditor;
+namespace giada::v
+{
class gePianoItem : public geBaseAction
{
public:
bool isResizable() const;
- private:
+private:
bool m_ringLoop;
bool m_orphaned;
Pixel calcVelocityH() const;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "pianoRoll.h"
-#include "core/action.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/midiEvent.h"
#include "glue/actionEditor.h"
#include "glue/channel.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
-#include "noteEditor.h"
-#include "pianoItem.h"
+#include "gui/elems/actionEditor/pianoItem.h"
+#include "src/core/actions/action.h"
#include "utils/log.h"
#include "utils/math.h"
#include "utils/string.h"
#include <FL/Fl.H>
#include <cassert>
-namespace giada
+namespace giada::v
{
-namespace v
+gePianoRoll::gePianoRoll(Pixel X, Pixel Y, gdBaseActionEditor* b)
+: geBaseActionEditor(X, Y, 200, CELL_H * MAX_KEYS, b)
+, m_pick(0)
{
-gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, gdBaseActionEditor* b)
-: geBaseActionEditor(X, Y, W, 40, b)
-, pick(0)
-{
- position(x(), m::conf::conf.pianoRollY == -1 ? y() - (h() / 2) : m::conf::conf.pianoRollY);
}
/* -------------------------------------------------------------------------- */
-void gePianoRoll::drawSurface1()
+void gePianoRoll::drawSurfaceY()
{
- surface1 = fl_create_offscreen(CELL_W, h());
- fl_begin_offscreen(surface1);
+ surfaceY = fl_create_offscreen(CELL_W, h());
+ fl_begin_offscreen(surfaceY);
/* Warning: only w() and h() come from this widget, x and y coordinates are
absolute, since we are writing in a memory chunk. */
for (int i = 1; i <= MAX_KEYS + 1; i++)
{
-
/* print key note label. C C# D D# E F F# G G# A A# B */
std::string note = u::string::iToString(octave);
/* -------------------------------------------------------------------------- */
-void gePianoRoll::drawSurface2()
+void gePianoRoll::drawSurfaceX()
{
- surface2 = fl_create_offscreen(CELL_W, h());
+ surfaceX = fl_create_offscreen(CELL_W, h());
- fl_begin_offscreen(surface2);
+ fl_begin_offscreen(surfaceX);
fl_rectf(0, 0, CELL_W, h(), G_COLOR_GREY_1);
fl_color(G_COLOR_GREY_3);
fl_line_style(FL_DASH, 0, nullptr);
{
switch (i % KEYS)
{
- case (int)Notes::G:
- case (int)Notes::E:
- case (int)Notes::D:
- case (int)Notes::B:
- case (int)Notes::A:
+ case static_cast<int>(Notes::G):
+ case static_cast<int>(Notes::E):
+ case static_cast<int>(Notes::D):
+ case static_cast<int>(Notes::B):
+ case static_cast<int>(Notes::A):
fl_rectf(0, i * CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2);
break;
}
void gePianoRoll::draw()
{
- fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0);
+ fl_copy_offscreen(x(), y(), CELL_W, h(), surfaceY, 0, 0);
// TODO - is this APPLE thing still useful?
#if defined(__APPLE__)
for (Pixel i = 36; i < m_base->fullWidth; i += 36) /// TODO: i < m_base->loopWidth is faster
- fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 1, 0);
+ fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 1, 0);
#else
for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W)
- fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 0, 0);
+ fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 0, 0);
#endif
baseDraw(false);
int gePianoRoll::handle(int e)
{
- if (e == FL_PUSH && Fl::event_button3())
- {
- pick = Fl::event_y() - y();
+ if (!Fl::event_button3())
return geBaseActionEditor::handle(e);
+
+ switch (e)
+ {
+ case FL_PUSH:
+ {
+ m_pick = Fl::event_y() - y();
+ break;
}
- if (e == FL_DRAG && Fl::event_button3())
+ case FL_DRAG:
{
- static_cast<geNoteEditor*>(parent())->scroll();
- return 1;
+ const int pos = Fl::event_y() - m_pick;
+ const int min = parent()->y();
+ const int max = -h() + (parent()->h() + parent()->y());
+ position(x(), std::clamp(pos, max, min));
+ break;
+ }
+ default:
+ break;
}
+
return geBaseActionEditor::handle(e);
}
add(new gePianoItem(px, py, pw, ph, a1, a2));
}
- drawSurface1();
- drawSurface2();
+ drawSurfaceY();
+ drawSurfaceX();
redraw();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
namespace giada::m
{
-class MidiChannel;
-class Action;
-} // namespace giada::m
+struct Action;
+}
+
namespace giada::v
{
class gePianoRoll : public geBaseActionEditor
static const Pixel CELL_H = 20;
static const Pixel CELL_W = 40;
- gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*);
+ gePianoRoll(Pixel x, Pixel y, gdBaseActionEditor* b);
void draw() override;
int handle(int e) override;
void rebuild(c::actionEditor::Data& d) override;
- Pixel pick;
-
- private:
+private:
enum class Notes
{
G = 1,
GS = 0
};
- Fl_Offscreen surface1; // notes, no repeat
- Fl_Offscreen surface2; // lines, x-repeat
-
void onAddAction() override;
void onDeleteAction() override;
void onMoveAction() override;
containing the rest of the piano roll. The latter will then be tiled during
the ::draw() call. */
- void drawSurface1();
- void drawSurface2();
+ void drawSurfaceY();
+ void drawSurfaceX();
Pixel snapToY(Pixel p) const;
int yToNote(Pixel y) const;
Pixel noteToY(int n) const;
Pixel getPianoItemW(Pixel x, const m::Action& a1, const m::Action& a2) const;
+
+ Fl_Offscreen surfaceY; // vertical notes, no x-repeat
+ Fl_Offscreen surfaceX; // lines, x-repeat
+
+ /* m_pick
+ Y-coordinate of the click event when the user clicks on an empty area of the
+ piano roll. Used for right mouse button scrolling. */
+
+ Pixel m_pick;
};
} // namespace giada::v
* -------------------------------------------------------------------------- */
#include "sampleAction.h"
-#include "core/action.h"
#include "core/const.h"
+#include "src/core/actions/action.h"
#include <FL/fl_draw.H>
namespace giada
#define GE_SAMPLE_ACTION_H
#include "baseAction.h"
-#include "core/recorder.h"
+#include "src/core/actions/actions.h"
-namespace giada
-{
-namespace m
-{
-class SampleChannel;
-}
-namespace v
+namespace giada::v
{
class geSampleAction : public geBaseAction
{
void draw() override;
- private:
+private:
bool m_singlePress;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "sampleActionEditor.h"
-#include "core/action.h"
-#include "core/conf.h"
#include "core/const.h"
-#include "core/recorder.h"
#include "glue/actionEditor.h"
#include "glue/channel.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "gui/dialogs/actionEditor/sampleActionEditor.h"
#include "sampleAction.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
#include "utils/log.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <cassert>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-geSampleActionEditor::~geSampleActionEditor()
+: geBaseActionEditor(x, y, 200, 40, b)
{
- m::conf::conf.sampleActionEditorH = h();
}
/* -------------------------------------------------------------------------- */
void geSampleActionEditor::draw()
{
+ /* Force height to match its parent's height. This widget belongs to a
+ geScroll container (see geSplitScroll class in baseActionEditor.h) but
+ there's nothing to scroll here actually. */
+
+ size(w(), parent()->h());
+
/* Draw basic boundaries (+ beat bars) and hide the unused area. Then draw
children (the actions). */
void geSampleActionEditor::onAddAction()
{
Frame f = m_base->pixelToFrame(Fl::event_x() - x());
- c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f);
+ c::actionEditor::recordSampleAction(m_data->channelId, static_cast<gdSampleActionEditor*>(m_base)->getActionType(), f);
}
/* -------------------------------------------------------------------------- */
return m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS &&
a.event.getStatus() == m::MidiEvent::NOTE_OFF;
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#include "baseActionEditor.h"
-namespace giada
+namespace giada::m
{
-namespace m
-{
-class SampleChannel;
struct Action;
-} // namespace m
-namespace v
+}
+
+namespace giada::v
{
class geSampleAction;
-
class geSampleActionEditor : public geBaseActionEditor
{
public:
geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*);
- ~geSampleActionEditor();
void draw() override;
void rebuild(c::actionEditor::Data& d) override;
- private:
+private:
void onAddAction() override;
void onDeleteAction() override;
void onMoveAction() override;
bool isNoteOffSinglePress(const m::Action& a);
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+*
+* Giada - Your Hardcore Loopmachine
+*
+* ------------------------------------------------------------------------------
+*
+* Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+*
+* This file is part of Giada - Your Hardcore Loopmachine.
+*
+* Giada - Your Hardcore Loopmachine is free software: you can
+* redistribute it and/or modify it under the terms of the GNU General
+* Public License as published by the Free Software Foundation, either
+* version 3 of the License, or (at your option) any later version.
+*
+* Giada - Your Hardcore Loopmachine is distributed in the hope that it
+* will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+* See the GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Giada - Your Hardcore Loopmachine. If not, see
+* <http://www.gnu.org/licenses/>.
+*
+* --------------------------------------------------------------------------- */
+
+#include "splitScroll.h"
+
+namespace giada::v
+{
+geSplitScroll::geSplitScroll(Pixel x, Pixel y, Pixel w, Pixel h)
+: geSplit(x, y, w, h)
+, m_a(0, 0, 0, 0, Fl_Scroll::VERTICAL_ALWAYS)
+, m_b(0, 0, 0, 0, Direction::HORIZONTAL)
+{
+ m_b.onScrollH = [&a = m_a](Pixel x) {
+ a.scroll_to(x, a.yposition());
+ };
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplitScroll::addWidgets(Fl_Widget& wa, Fl_Widget& wb, Pixel topContentH)
+{
+ m_a.add(&wa);
+ m_b.addWidget(&wb);
+
+ init(m_a, m_b);
+
+ if (topContentH != -1)
+ resizePanel(geSplit::Panel::A, topContentH);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getScrollX() const
+{
+ return m_b.xposition();
+}
+
+Pixel geSplitScroll::getScrollY() const
+{
+ return m_a.yposition();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getContentWidth() const
+{
+ if (m_a.countChildren() == 0)
+ return 0;
+ return m_a.child(0)->w();
+}
+
+/* -------------------------------------------------------------------------- */
+
+Pixel geSplitScroll::getTopContentH() const
+{
+ return m_a.h();
+}
+
+/* -------------------------------------------------------------------------- */
+
+geompp::Rect<Pixel> geSplitScroll::getBoundsNoScrollbar() const
+{
+ return {
+ x(), y(),
+ w() - m_a.scrollbar.w() - G_GUI_OUTER_MARGIN,
+ h() - m_b.hscrollbar.h() - G_GUI_OUTER_MARGIN};
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplitScroll::setScrollX(Pixel p)
+{
+ p = std::max(0, p);
+ m_a.scroll_to(p, m_a.yposition());
+ m_b.scroll_to(p, m_b.yposition());
+}
+
+void geSplitScroll::setScrollY(Pixel p)
+{
+ m_a.scroll_to(m_a.xposition(), p);
+}
+} // namespace giada::v
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_SPLITSCROLL_H
+#define GE_SPLITSCROLL_H
+
+#include "core/types.h"
+#include "deps/geompp/src/rect.hpp"
+#include "gui/elems/basics/liquidScroll.h"
+#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/split.h"
+
+namespace giada::v
+{
+class geSplitScroll : public geSplit
+{
+public:
+ geSplitScroll(Pixel x, Pixel y, Pixel w, Pixel h);
+
+ Pixel getScrollX() const;
+ Pixel getScrollY() const;
+ Pixel getContentWidth() const;
+ Pixel getTopContentH() const;
+ geompp::Rect<Pixel> getBoundsNoScrollbar() const;
+
+ void addWidgets(Fl_Widget& a, Fl_Widget& b, Pixel topContentH = -1);
+ void setScrollX(Pixel p);
+ void setScrollY(Pixel p);
+
+private:
+ geScroll m_a;
+ geLiquidScroll m_b;
+};
+} // namespace giada::v
+
+#endif
* -------------------------------------------------------------------------- */
#include "velocityEditor.h"
-#include "core/action.h"
-#include "core/clock.h"
#include "core/conf.h"
#include "core/const.h"
#include "envelopePoint.h"
#include "glue/actionEditor.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
+#include "src/core/actions/action.h"
#include "utils/log.h"
#include "utils/math.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <cassert>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
-: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b)
-{
-}
-
-/* -------------------------------------------------------------------------- */
-
-geVelocityEditor::~geVelocityEditor()
+: geBaseActionEditor(x, y, 200, 40, b)
{
- m::conf::conf.velocityEditorH = h();
}
/* -------------------------------------------------------------------------- */
{
baseDraw();
+ if (h() < geEnvelopePoint::SIDE)
+ return;
+
/* Print label. */
fl_color(G_COLOR_GREY_4);
fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE);
- fl_draw("Velocity", x() + 4, y(), w(), h(), (Fl_Align)(FL_ALIGN_LEFT));
+ fl_draw("Velocity", x() + 4, y(), w(), h(), FL_ALIGN_LEFT);
if (children() == 0)
return;
- Pixel side = geEnvelopePoint::SIDE / 2;
+ const Pixel side = geEnvelopePoint::SIDE / 2;
for (int i = 0; i < children(); i++)
{
geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
if (m_action == nullptr)
p->position(p->x(), valueToY(p->a1.event.getVelocity()));
- Pixel x1 = p->x() + side;
- Pixel y1 = p->y();
- Pixel y2 = y() + h();
+ const Pixel x1 = p->x() + side;
+ const Pixel y1 = p->y();
+ const Pixel y2 = y() + h();
fl_line(x1, y1, x1, y2);
}
m_base->rebuild(); // Rebuild pianoRoll as well
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#include "baseActionEditor.h"
-namespace giada
-{
-namespace m
-{
-class MidiChannel;
-}
-namespace v
+namespace giada::v
{
class geEnvelopePoint;
-
class geVelocityEditor : public geBaseActionEditor
{
public:
geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*);
- ~geVelocityEditor();
void draw() override;
void rebuild(c::actionEditor::Data& d) override;
- private:
+private:
void onMoveAction() override;
void onRefreshAction() override;
void onAddAction() override{};
Pixel valueToY(int v) const;
int yToValue(Pixel y) const;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
void geChoice::addItem(const std::string& label, ID id)
{
- assert(id >= 0);
-
Fl_Choice::add(label.c_str(), 0, cb_onChange, static_cast<void*>(this));
- m_ids.push_back(id);
+
+ if (id != -1)
+ m_ids.push_back(id);
+ else // auto-increment
+ m_ids.push_back(m_ids.size() == 0 ? 0 : m_ids.back() + 1);
}
/* -------------------------------------------------------------------------- */
ID getSelectedId() const;
- void addItem(const std::string& label, ID id);
+ /* addItem
+ Adds a new item with a certain ID. Pass id = -1 to auto-increment it. */
+
+ void addItem(const std::string& label, ID id = -1);
+
void showItem(const std::string& label);
void showItem(ID id);
void clear();
namespace v
{
/* geGroup
-A group that resizes itself accoring to the content. */
+A group that resizes itself according to the content. */
class geGroup : public Fl_Group
{
/* add
Adds a Fl_Widget 'w' to this group. Coordinates are relative to the group,
- so origin starts at (0, 0). */
+ so origin starts at (0, 0). As with any other FLTK group, the widget becomes
+ owned by this group: If you add static or automatic (local) variables, then
+ it is your responsibility to remove (or delete) all such static or automatic
+ child widgets before destroying the group - otherwise the child widgets'
+ destructors would be called twice! */
void add(Fl_Widget* w);
Fl_Widget* getChild(std::size_t i);
Fl_Widget* getLastChild();
- private:
+private:
/* m_widgets
The internal Fl_Scroll::array_ is unreliable when inspected with the child()
method. Let's keep track of widgets that belong to this group manually. */
#include "boxtypes.h"
#include "core/const.h"
-geLiquidScroll::geLiquidScroll(int x, int y, int w, int h)
-: geScroll(x, y, w, h, Fl_Scroll::VERTICAL_ALWAYS)
+namespace giada::v
+{
+geLiquidScroll::geLiquidScroll(int x, int y, int w, int h, Direction d)
+: geScroll(x, y, w, h, d == Direction::VERTICAL ? Fl_Scroll::VERTICAL_ALWAYS : Fl_Scroll::HORIZONTAL_ALWAYS)
+, m_direction(d)
{
}
void geLiquidScroll::resize(int X, int Y, int W, int H)
{
- int nc = children() - 2; // skip hscrollbar and vscrollbar
- for (int t = 0; t < nc; t++)
- { // tell children to resize to our new width
+ const int nc = children() - 2; // skip hscrollbar and vscrollbar
+ for (int t = 0; t < nc; t++) // tell children to resize to our new width
+ {
Fl_Widget* c = child(t);
- c->resize(c->x(), c->y(), W - 24, c->h()); // W-24: leave room for scrollbar
+ if (m_direction == Direction::VERTICAL)
+ c->resize(c->x(), c->y(), W - 24, c->h()); // -24: leave room for scrollbar
+ else
+ c->resize(c->x(), c->y(), c->w(), H - 24); // -24: leave room for scrollbar
}
init_sizes(); // tell scroll children changed in size
Fl_Scroll::resize(X, Y, W, H);
}
+} // namespace giada::v
\ No newline at end of file
#define GE_LIQUID_SCROLL_H
#include "core/const.h"
+#include "gui/types.h"
#include "scroll.h"
+namespace giada::v
+{
class geLiquidScroll : public geScroll
{
public:
- geLiquidScroll(int x, int y, int w, int h);
+ geLiquidScroll(int x, int y, int w, int h, Direction d);
void resize(int x, int y, int w, int h) override;
return wg;
}
+
+private:
+ Direction m_direction;
};
+} // namespace giada::v
#endif
#include "pack.h"
#include "core/const.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
gePack::gePack(int x, int y, Direction d, int gutter)
: geGroup(x, y)
geGroup::add(widget);
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
#include "core/const.h"
#include "gui/elems/basics/group.h"
+#include "gui/types.h"
-namespace giada
+namespace giada::v
{
-namespace v
-{
-enum class Direction
-{
- HORIZONTAL,
- VERTICAL
-};
-
/* gePack
A stack of widgets that resize itself according to its content. */
/* add
Adds a Fl_Widget 'w' to this pack. Coordinates are relative to the group,
- so origin starts at (0, 0). */
+ so origin starts at (0, 0). As with any other FLTK group, the widget becomes
+ owned by this group: If you add static or automatic (local) variables, then
+ it is your responsibility to remove (or delete) all such static or automatic
+ child widgets before destroying the group - otherwise the child widgets'
+ destructors would be called twice! */
void add(Fl_Widget* w);
- private:
+private:
Direction m_direction;
int m_gutter;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* Giada - Your Hardcore Loopmachine
*
- * geResizerBar
- * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from
- * FLTK dev team. http://seriss.com/people/erco/fltk/
- *
- * Shows a resize cursor when hovered over.
- * Assumes:
- * - Parent is an Fl_Scroll
- * - All children of Fl_Scroll are m_vertically arranged
- * - The widget above us has a bottom edge touching our top edge
- * ie. (w->y()+w->h() == this->y())
- *
- * When this widget is dragged:
- * - The widget above us (with a common edge) will be /resized/
- * m_vertically
- * - All children below us will be /moved/ m_vertically
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
#include "resizerBar.h"
#include "core/const.h"
#include <FL/Fl.H>
-#include <FL/Fl_Scroll.H>
+#include <FL/Fl_Group.H>
#include <FL/fl_draw.H>
+#include <cassert>
+#include <vector>
-geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type, Fl_Widget* target)
+namespace giada::v
+{
+geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, Direction dir, Mode mode)
: Fl_Box(X, Y, W, H)
-, m_type(type)
+, m_direction(dir)
+, m_mode(mode)
, m_minSize(minSize)
, m_lastPos(0)
-, m_initialPos(0)
, m_hover(false)
-, m_target(target)
{
- if (m_type == VERTICAL)
- {
- m_origSize = H;
- labelsize(H);
- }
- else
- {
- m_origSize = W;
- labelsize(W);
- }
- align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
- labelfont(FL_COURIER);
visible_focus(0);
}
void geResizerBar::handleDrag(int diff)
{
- Fl_Scroll* group = static_cast<Fl_Scroll*>(parent());
+ m_mode == Mode::MOVE ? move(diff) : resize(diff);
+
+ Fl_Group* group = static_cast<Fl_Group*>(parent());
+ group->init_sizes();
+ group->redraw();
+}
- const int top = m_type == VERTICAL ? y() : x();
- const int bot = m_type == VERTICAL ? y() + h() : x() + w();
+/* -------------------------------------------------------------------------- */
- // First pass: find widget directly above us with common edge
- // Possibly clamp 'diff' if widget would get too small..
+void geResizerBar::move(int diff)
+{
+ Fl_Widget& wfirst = getFirstWidget();
+ std::vector<Fl_Widget*> wothers = findWidgets([this](const Fl_Widget& wd) { return isAfter(wd); });
- for (int t = 0; t < group->children(); t++)
+ if (m_direction == Direction::VERTICAL)
{
- Fl_Widget* wd = group->child(t);
- if (m_type == VERTICAL)
- {
- if ((wd->y() + wd->h()) == top)
- { // found widget directly above?
- if ((wd->h() + diff) < m_minSize)
- diff = wd->h() - m_minSize; // clamp
- wd->resize(wd->x(), wd->y(), wd->w(), wd->h() + diff); // change height
- break; // done with first pass
- }
- }
- else
- {
- if ((wd->x() + wd->w()) == top)
- { // found widget directly above?
- if ((wd->w() + diff) < m_minSize)
- diff = wd->w() - m_minSize; // clamp
- wd->resize(wd->x(), wd->y(), wd->w() + diff, wd->h()); // change width
- break; // done with first pass
- }
- }
+ if (wfirst.h() + diff < m_minSize)
+ diff = 0;
+ wfirst.resize(wfirst.x(), wfirst.y(), wfirst.w(), wfirst.h() + diff);
+ for (Fl_Widget* wd : wothers)
+ wd->resize(wd->x(), wd->y() + diff, wd->w(), wd->h());
+ resize(x(), y() + diff, w(), h());
+ }
+ else if (m_direction == Direction::HORIZONTAL)
+ {
+ if (wfirst.w() + diff < m_minSize)
+ diff = 0;
+ wfirst.resize(wfirst.x(), wfirst.y(), wfirst.w() + diff, wfirst.h());
+ for (Fl_Widget* wd : wothers)
+ wd->resize(wd->x() + diff, wd->y(), wd->w(), wd->h());
+ resize(x() + diff, y(), w(), h());
}
+}
- // Second pass: find widgets below us, move based on clamped diff
+/* -------------------------------------------------------------------------- */
+
+void geResizerBar::resize(int diff)
+{
+ Fl_Widget& wa = getFirstWidget();
+ Fl_Widget& wb = *findWidgets([this](const Fl_Widget& wd) { return isAfter(wd); }, /*howmany=*/1)[0];
+
+ if (m_direction == Direction::VERTICAL)
+ {
+ if (wa.h() + diff < m_minSize || wb.h() - diff < m_minSize)
+ diff = 0;
+ wa.resize(wa.x(), wa.y(), wa.w(), wa.h() + diff);
+ wb.resize(wb.x(), wb.y() + diff, wb.w(), wb.h() - diff);
+ resize(x(), y() + diff, w(), h());
+ }
+ else if (m_direction == Direction::HORIZONTAL)
+ {
+ if (wa.w() + diff < m_minSize || wb.w() - diff < m_minSize)
+ diff = 0;
+ wa.resize(wa.x(), wa.y(), wa.w() + diff, wa.h());
+ wb.resize(wb.x() + diff, wb.y(), wb.w() - diff, wb.h());
+ resize(x() + diff, y(), w(), h());
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool geResizerBar::isBefore(const Fl_Widget& wd) const
+{
+ const int before = m_direction == Direction::VERTICAL ? y() : x();
+ return (m_direction == Direction::VERTICAL && wd.y() + wd.h() == before) ||
+ (m_direction == Direction::HORIZONTAL && wd.x() + wd.w() == before);
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool geResizerBar::isAfter(const Fl_Widget& wd) const
+{
+ const int after = m_direction == Direction::VERTICAL ? y() + h() : x() + w();
+ return (m_direction == Direction::VERTICAL && wd.y() >= after) ||
+ (m_direction == Direction::HORIZONTAL && wd.x() >= after);
+}
+
+/* -------------------------------------------------------------------------- */
+
+Fl_Widget& geResizerBar::getFirstWidget()
+{
+ return *findWidgets([this](const Fl_Widget& wd) { return isBefore(wd); }, /*howmany=*/1)[0];
+}
+
+/* -------------------------------------------------------------------------- */
+
+std::vector<Fl_Widget*> geResizerBar::findWidgets(std::function<bool(const Fl_Widget&)> f, int howmany) const
+{
+ std::vector<Fl_Widget*> out;
+ Fl_Group* group = static_cast<Fl_Group*>(parent());
for (int t = 0; t < group->children(); t++)
{
Fl_Widget* wd = group->child(t);
- if (m_type == VERTICAL)
- {
- if (wd->y() >= bot) // found widget below us?
- wd->resize(wd->x(), wd->y() + diff, wd->w(), wd->h()); // change position
- }
- else
- {
- if (wd->x() >= bot)
- wd->resize(wd->x() + diff, wd->y(), wd->w(), wd->h());
- }
+ if (!f(*wd))
+ continue;
+ out.push_back(wd);
+ if (howmany != -1 && out.size() == (size_t)howmany)
+ break;
}
- // Change our position last
+ /* Make sure it finds the exact number of widgets requested, in case
+ howmany != -1. */
- if (m_type == VERTICAL)
- resize(x(), y() + diff, w(), h());
- else
- resize(x() + diff, y(), w(), h());
+ assert(howmany == -1 || (howmany != -1 && out.size() == (size_t)howmany));
- group->init_sizes();
- group->redraw();
+ return out;
}
/* -------------------------------------------------------------------------- */
int geResizerBar::handle(int e)
{
int ret = 0;
- int currentPos = m_type == VERTICAL ? Fl::event_y_root() : Fl::event_x_root();
+ int currentPos = m_direction == Direction::VERTICAL ? Fl::event_y_root() : Fl::event_x_root();
switch (e)
{
break;
case FL_ENTER:
ret = 1;
- fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
+ fl_cursor(m_direction == Direction::VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
m_hover = true;
redraw();
break;
redraw();
break;
case FL_PUSH:
- ret = 1;
- m_lastPos = currentPos;
- m_initialPos = currentPos;
+ ret = 1;
+ m_lastPos = currentPos;
break;
case FL_DRAG:
handleDrag(currentPos - m_lastPos);
m_lastPos = currentPos;
ret = 1;
if (onDrag != nullptr)
- onDrag(m_target);
+ onDrag(getFirstWidget());
break;
case FL_RELEASE:
- if (m_initialPos != currentPos && onRelease != nullptr)
- onRelease(m_target);
+ if (onRelease != nullptr)
+ onRelease(getFirstWidget());
break;
default:
break;
/* -------------------------------------------------------------------------- */
-int geResizerBar::getMinSize() const
+void geResizerBar::resize(int X, int Y, int W, int H)
{
- return m_minSize;
+ if (m_direction == Direction::VERTICAL)
+ Fl_Box::resize(X, Y, W, h());
+ else
+ Fl_Box::resize(X, Y, w(), H);
}
/* -------------------------------------------------------------------------- */
-void geResizerBar::resize(int x, int y, int w, int h)
+void geResizerBar::moveTo(int p)
{
- if (m_type == VERTICAL)
- Fl_Box::resize(x, y, w, m_origSize); // Height of resizer stays constant size
- else
- Fl_Box::resize(x, y, m_origSize, h);
+ const Fl_Widget& wd = getFirstWidget();
+ const int curr = m_direction == Direction::VERTICAL ? wd.h() : wd.w();
+ handleDrag(p - curr);
}
+} // namespace giada::v
\ No newline at end of file
*
* Giada - Your Hardcore Loopmachine
*
- * geResizerBar
- * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from
- * FLTK dev team. http://seriss.com/people/erco/fltk/
- *
- * Shows a resize cursor when hovered over.
- * Assumes:
- * - Parent is an Fl_Scroll
- * - All children of Fl_Scroll are vertically arranged
- * - The widget above us has a bottom edge touching our top edge
- * ie. (w->y()+w->h() == this->y())
- *
- * When this widget is dragged:
- * - The widget above us (with a common edge) will be /resized/
- * vertically
- * - All children below us will be /moved/ vertically
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
#include <FL/Fl_Box.H>
#include <functional>
+/* geResizerBar
+A 'resizer bar' between widgets inside a Fl_Scroll. Thanks to Greg Ercolano from
+FLTK dev team (http://seriss.com/people/erco/fltk/). It also shows a resize
+cursor when hovered over.
+
+Assumes:
+ - Parent is an Fl_Group;
+ - The widget before us has an edge touching our edge;
+ ie. w->y() + w->h() == this->y() if Direction::VERTICAL.
+
+When this widget is dragged:
+ - The widget before us (with a common edge) will be resized;
+ - if Mode == MOVE
+ All children after us will be moved.
+ - else if Mode == RESIZE
+ The child after us is resized. */
+
+namespace giada::v
+{
class geResizerBar : public Fl_Box
{
public:
- static const int HORIZONTAL = 0;
- static const int VERTICAL = 1;
+ enum class Direction
+ {
+ HORIZONTAL,
+ VERTICAL
+ };
+
+ enum class Mode
+ {
+ MOVE,
+ RESIZE
+ };
- geResizerBar(int x, int y, int w, int h, int minSize, bool type, Fl_Widget* target = nullptr);
+ geResizerBar(int x, int y, int w, int h, int minSize, Direction dir, Mode m = Mode::MOVE);
int handle(int e) override;
void draw() override;
void resize(int x, int y, int w, int h) override;
- int getMinSize() const;
+ void moveTo(int p);
+
+ std::function<void(const Fl_Widget&)> onDrag = nullptr;
+ std::function<void(const Fl_Widget&)> onRelease = nullptr;
+
+private:
+ /* isBefore
+ True if widget 'w' is before the drag bar. */
- std::function<void(const Fl_Widget*)> onDrag = nullptr;
- std::function<void(const Fl_Widget*)> onRelease = nullptr;
+ bool isBefore(const Fl_Widget& w) const;
+
+ /* isBefore
+ True if widget 'w' is after the drag bar. */
+ bool isAfter(const Fl_Widget& w) const;
+
+ /* findWidgets
+ Returns a vector of widgets according to a certain logic specified in the
+ lambda function. Limits the output to 'howmany' widgets if 'howmany' != -1. */
+
+ std::vector<Fl_Widget*> findWidgets(std::function<bool(const Fl_Widget&)> f, int howmany = -1) const;
+
+ /* handleDrag
+ Main entrypoint for the dragging operation. */
- private:
void handleDrag(int diff);
- bool m_type;
- int m_origSize;
- int m_minSize;
- int m_lastPos;
- int m_initialPos;
- bool m_hover;
+ /* move
+ Resize the first widget and shift all others. */
+
+ void move(int diff);
+
+ /* resize
+ Resize the first and the second widget, leaving all others untouched. */
+
+ void resize(int diff);
+
+ /* getFirstWidget
+ Returns a ref to the first widget before the drag bar. */
+
+ Fl_Widget& getFirstWidget();
- Fl_Widget* m_target;
+ Direction m_direction;
+ Mode m_mode;
+ int m_minSize;
+ int m_lastPos;
+ bool m_hover;
};
+} // namespace giada::v
#endif
scrollbar.selection_color(G_COLOR_GREY_4);
scrollbar.labelcolor(G_COLOR_LIGHT_1);
scrollbar.slider(G_CUSTOM_BORDER_BOX);
+ scrollbar.callback(cb_onScrollV, this);
hscrollbar.color(G_COLOR_GREY_2);
hscrollbar.selection_color(G_COLOR_GREY_4);
hscrollbar.labelcolor(G_COLOR_LIGHT_1);
hscrollbar.slider(G_CUSTOM_BORDER_BOX);
+ hscrollbar.callback(cb_onScrollH, this);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollV(Fl_Widget* w, void* p)
+{
+ geScroll* s = static_cast<geScroll*>(w->parent());
+ Fl_Scrollbar* b = static_cast<Fl_Scrollbar*>(w);
+
+ s->scroll_to(s->xposition(), b->value());
+
+ (static_cast<geScroll*>(p))->cb_onScrollV();
+}
+
+void geScroll::cb_onScrollH(Fl_Widget* w, void* p)
+{
+ geScroll* s = static_cast<geScroll*>(w->parent());
+ Fl_Scrollbar* b = static_cast<Fl_Scrollbar*>(w);
+
+ s->scroll_to(b->value(), s->yposition());
+
+ (static_cast<geScroll*>(p))->cb_onScrollH();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollV()
+{
+ if (onScrollV != nullptr)
+ onScrollV(yposition());
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geScroll::cb_onScrollH()
+{
+ if (onScrollH != nullptr)
+ onScrollH(xposition());
}
/* -------------------------------------------------------------------------- */
#define GE_SCROLL_H
#include <FL/Fl_Scroll.H>
+#include <functional>
class geScroll : public Fl_Scroll
{
geScroll(int x, int y, int w, int h, int type = Fl_Scroll::BOTH);
int countChildren() const;
+
+ std::function<void(int)> onScrollV{nullptr};
+ std::function<void(int)> onScrollH{nullptr};
+
+private:
+ static void cb_onScrollV(Fl_Widget* w, void* p);
+ static void cb_onScrollH(Fl_Widget* w, void* p);
+ void cb_onScrollV();
+ void cb_onScrollH();
};
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "split.h"
+#include "core/const.h"
+#include "gui/elems/basics/box.h"
+
+namespace giada::v
+{
+geSplit::geSplit(int x, int y, int w, int h)
+: Fl_Group(x, y, w, h)
+, m_a(nullptr)
+, m_b(nullptr)
+, m_bar(0, 0, w, G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL, geResizerBar::Mode::RESIZE)
+{
+ end();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplit::init(Fl_Widget& a, Fl_Widget& b)
+{
+ a.resize(x(), y(), w(), (h() / 2) - G_GUI_INNER_MARGIN); // Panel A goes on top
+ a.redraw();
+ m_a = &a;
+
+ m_bar.resize(x(), m_a->y() + m_a->h(), w(), G_GUI_INNER_MARGIN);
+
+ b.resize(x(), m_bar.y() + m_bar.h(), w(), h() / 2); // Panel B goes on bottom
+ b.redraw();
+ m_b = &b;
+
+ Fl_Group::add(m_a);
+ Fl_Group::add(m_bar);
+ Fl_Group::add(m_b);
+
+ resizable(m_a);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void geSplit::resizePanel(Panel p, int s)
+{
+ m_bar.moveTo(p == Panel::A ? s : h() - s);
+}
+} // namespace giada::v
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef GE_SPLIT_H
+#define GE_SPLIT_H
+
+#include "gui/elems/basics/resizerBar.h"
+#include <FL/Fl_Group.H>
+
+namespace giada::v
+{
+/* geSplit
+A resizable split-view widget that contains two horizontal panels (A and B).
+TODO - add vertical mode. */
+
+class geSplit : public Fl_Group
+{
+public:
+ enum class Panel
+ {
+ A,
+ B
+ };
+
+ geSplit(int x, int y, int w, int h);
+
+ void init(Fl_Widget& a, Fl_Widget& b);
+ void resizePanel(Panel p, int s);
+
+private:
+ Fl_Widget* m_a;
+ Fl_Widget* m_b;
+ geResizerBar m_bar;
+};
+} // namespace giada::v
+
+#endif
namespace giada::v
{
-geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l, const std::vector<c::config::AudioDeviceData>& devices)
+geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l,
+ const std::vector<c::config::AudioDeviceData>& devices)
: geChoice(x, y, w, h, l)
{
if (devices.size() == 0)
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData& data)
+geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l,
+ const c::config::AudioDeviceData& data)
: geChoice(x, y, w, h, l)
, m_data(data)
{
/* -------------------------------------------------------------------------- */
-void geTabAudio::geChannelMenu::rebuild(c::config::AudioDeviceData& data)
+void geTabAudio::geChannelMenu::rebuild(const c::config::AudioDeviceData& data)
{
m_data = data;
struct geChannelMenu : public geChoice
{
- geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData&);
+ geChannelMenu(int x, int y, int w, int h, const char* l, const c::config::AudioDeviceData&);
int getChannelsCount() const;
int getChannelsStart() const;
- void rebuild(c::config::AudioDeviceData&);
+ void rebuild(const c::config::AudioDeviceData&);
private:
static constexpr int STEREO_OFFSET = 1000;
- c::config::AudioDeviceData& m_data;
+ c::config::AudioDeviceData m_data;
};
geTabAudio(int x, int y, int w, int h);
#include "gui/elems/basics/check.h"
#include <FL/Fl_Pack.H>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H)
+geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H, m::Conf::Data& c)
: Fl_Group(X, Y, W, H)
, m_container(X, Y + G_GUI_OUTER_MARGIN, Direction::VERTICAL, G_GUI_OUTER_MARGIN)
, m_chansStopOnSeqHalt(0, 0, 280, 30, "Dynamic channels stop immediately when the sequencer\nis halted")
, m_treatRecsAsLoops(0, 0, 280, 20, "Treat one shot channels with actions as loops")
, m_inputMonitorDefaultOn(0, 0, 280, 20, "New sample channels have input monitor on by default")
, m_overdubProtectionDefaultOn(0, 0, 280, 30, "New sample channels have overdub protection on\nby default")
+, m_conf(c)
{
end();
add(m_container);
- m_chansStopOnSeqHalt.value(m::conf::conf.chansStopOnSeqHalt);
- m_treatRecsAsLoops.value(m::conf::conf.treatRecsAsLoops);
- m_inputMonitorDefaultOn.value(m::conf::conf.inputMonitorDefaultOn);
- m_overdubProtectionDefaultOn.value(m::conf::conf.overdubProtectionDefaultOn);
+ m_chansStopOnSeqHalt.value(m_conf.chansStopOnSeqHalt);
+ m_treatRecsAsLoops.value(m_conf.treatRecsAsLoops);
+ m_inputMonitorDefaultOn.value(m_conf.inputMonitorDefaultOn);
+ m_overdubProtectionDefaultOn.value(m_conf.overdubProtectionDefaultOn);
}
/* -------------------------------------------------------------------------- */
void geTabBehaviors::save()
{
- m::conf::conf.chansStopOnSeqHalt = m_chansStopOnSeqHalt.value();
- m::conf::conf.treatRecsAsLoops = m_treatRecsAsLoops.value();
- m::conf::conf.inputMonitorDefaultOn = m_inputMonitorDefaultOn.value();
- m::conf::conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value();
+ m_conf.chansStopOnSeqHalt = m_chansStopOnSeqHalt.value();
+ m_conf.treatRecsAsLoops = m_treatRecsAsLoops.value();
+ m_conf.inputMonitorDefaultOn = m_inputMonitorDefaultOn.value();
+ m_conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value();
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+} // namespace giada::v
\ No newline at end of file
#ifndef GE_TAB_BEHAVIORS_H
#define GE_TAB_BEHAVIORS_H
+#include "core/conf.h"
#include "gui/elems/basics/check.h"
#include "gui/elems/basics/pack.h"
#include <FL/Fl_Group.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geTabBehaviors : public Fl_Group
{
public:
- geTabBehaviors(int x, int y, int w, int h);
+ geTabBehaviors(int x, int y, int w, int h, m::Conf::Data&);
void save();
- private:
+private:
gePack m_container;
geCheck m_chansStopOnSeqHalt;
geCheck m_treatRecsAsLoops;
geCheck m_inputMonitorDefaultOn;
geCheck m_overdubProtectionDefaultOn;
+
+ m::Conf::Data& m_conf;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* -------------------------------------------------------------------------- */
-#include "tabMidi.h"
-#include "core/conf.h"
+#include "gui/elems/config/tabMidi.h"
#include "core/const.h"
-#include "core/kernelMidi.h"
-#include "core/midiMapConf.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/check.h"
-#include "gui/elems/basics/choice.h"
#include "utils/gui.h"
-#include <RtMidi.h>
#include <string>
-namespace giada
+namespace giada::v
{
-namespace v
+geTabMidi::geMenu::geMenu(int x, int y, int w, int h, const char* l,
+ const std::vector<std::string>& data, const std::string& msgIfNotFound)
+: geChoice(x, y, w, h, l)
{
-geTabMidi::geTabMidi(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "MIDI")
-{
- begin();
- system = new geChoice(x() + w() - 250, y() + 9, 250, 20, "System");
- portOut = new geChoice(x() + w() - 250, system->y() + system->h() + 8, 250, 20, "Output port");
- portIn = new geChoice(x() + w() - 250, portOut->y() + portOut->h() + 8, 250, 20, "Input port");
- midiMap = new geChoice(x() + w() - 250, portIn->y() + portIn->h() + 8, 250, 20, "Output Midi Map");
- sync = new geChoice(x() + w() - 250, midiMap->y() + midiMap->h() + 8, 250, 20, "Sync");
- new geBox(x(), sync->y() + sync->h() + 8, w(), h() - 150, "Restart Giada for the changes to take effect.");
- end();
-
- labelsize(G_GUI_FONT_SIZE_BASE);
- selection_color(G_COLOR_GREY_4);
-
- system->callback(cb_changeSystem, (void*)this);
-
- fetchSystems();
- fetchOutPorts();
- fetchInPorts();
- fetchMidiMaps();
-
- sync->add("(disabled)");
- sync->add("MIDI Clock (master)");
- sync->add("MTC (master)");
- if (m::conf::conf.midiSync == MIDI_SYNC_NONE)
- sync->value(0);
- else if (m::conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- sync->value(1);
- else if (m::conf::conf.midiSync == MIDI_SYNC_MTC_M)
- sync->value(2);
-
- systemInitValue = system->value();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchOutPorts()
-{
- if (m::kernelMidi::countOutPorts() == 0)
+ if (data.size() == 0)
{
- portOut->add("-- no ports found --");
- portOut->value(0);
- portOut->deactivate();
+ addItem(msgIfNotFound.c_str(), 0);
+ showItem(0);
+ deactivate();
}
else
{
-
- portOut->add("(disabled)");
-
- for (unsigned i = 0; i < m::kernelMidi::countOutPorts(); i++)
- portOut->add(u::gui::removeFltkChars(m::kernelMidi::getOutPortName(i)).c_str());
-
- portOut->value(m::conf::conf.midiPortOut + 1); // +1 because midiPortOut=-1 is '(disabled)'
+ for (const std::string& d : data)
+ addItem(u::gui::removeFltkChars(d).c_str(), -1); // -1: auto-increment ID
}
}
/* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchInPorts()
-{
- if (m::kernelMidi::countInPorts() == 0)
- {
- portIn->add("-- no ports found --");
- portIn->value(0);
- portIn->deactivate();
- }
- else
- {
-
- portIn->add("(disabled)");
-
- for (unsigned i = 0; i < m::kernelMidi::countInPorts(); i++)
- portIn->add(u::gui::removeFltkChars(m::kernelMidi::getInPortName(i)).c_str());
-
- portIn->value(m::conf::conf.midiPortIn + 1); // +1 because midiPortIn=-1 is '(disabled)'
- }
-}
-
/* -------------------------------------------------------------------------- */
-
-void geTabMidi::fetchMidiMaps()
-{
- if (m::midimap::maps.size() == 0)
- {
- midiMap->add("(no MIDI maps available)");
- midiMap->value(0);
- midiMap->deactivate();
- return;
- }
-
- for (unsigned i = 0; i < m::midimap::maps.size(); i++)
- {
- const char* imap = m::midimap::maps.at(i).c_str();
- midiMap->add(imap);
- if (m::conf::conf.midiMapPath == imap)
- midiMap->value(i);
- }
-
- /* Preselect the 0 m::midimap if nothing is selected but midimaps exist. */
-
- if (midiMap->value() == -1 && m::midimap::maps.size() > 0)
- midiMap->value(0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::save()
-{
- std::string text = system->text(system->value());
-
- if (text == "ALSA")
- m::conf::conf.midiSystem = RtMidi::LINUX_ALSA;
- else if (text == "Jack")
- m::conf::conf.midiSystem = RtMidi::UNIX_JACK;
- else if (text == "Multimedia MIDI")
- m::conf::conf.midiSystem = RtMidi::WINDOWS_MM;
- else if (text == "OSX Core MIDI")
- m::conf::conf.midiSystem = RtMidi::MACOSX_CORE;
-
- m::conf::conf.midiPortOut = portOut->value() - 1; // -1 because midiPortOut=-1 is '(disabled)'
- m::conf::conf.midiPortIn = portIn->value() - 1; // -1 because midiPortIn=-1 is '(disabled)'
- m::conf::conf.midiMapPath = m::midimap::maps.size() == 0 ? "" : midiMap->text(midiMap->value());
-
- if (sync->value() == 0)
- m::conf::conf.midiSync = MIDI_SYNC_NONE;
- else if (sync->value() == 1)
- m::conf::conf.midiSync = MIDI_SYNC_CLOCK_M;
- else if (sync->value() == 2)
- m::conf::conf.midiSync = MIDI_SYNC_MTC_M;
-}
-
/* -------------------------------------------------------------------------- */
-void geTabMidi::fetchSystems()
+geTabMidi::geTabMidi(int X, int Y, int W, int H)
+: Fl_Group(X, Y, W, H, "MIDI")
+, m_data(c::config::getMidiData())
+, m_initialApi(m_data.api)
{
-#if defined(__linux__)
-
- if (m::kernelMidi::hasAPI(RtMidi::LINUX_ALSA))
- system->add("ALSA");
- if (m::kernelMidi::hasAPI(RtMidi::UNIX_JACK))
- system->add("Jack");
-
-#elif defined(__FreeBSD__)
-
- if (m::kernelMidi::hasAPI(RtMidi::UNIX_JACK))
- system->add("Jack");
-
-#elif defined(_WIN32)
+ begin();
+ system = new geChoice(x() + w() - 250, y() + 9, 250, 20, "System");
+ portOut = new geMenu(x() + w() - 250, system->y() + system->h() + 8, 234, 20, "Output port", m_data.outPorts, "-- no ports found --");
+ enableOut = new geCheck(portOut->x() + portOut->w() + 4, portOut->y(), 12, 20);
+ portIn = new geMenu(x() + w() - 250, portOut->y() + portOut->h() + 8, 234, 20, "Input port", m_data.inPorts, "-- no ports found --");
+ enableIn = new geCheck(portIn->x() + portIn->w() + 4, portIn->y(), 12, 20);
+ midiMap = new geMenu(x() + w() - 250, portIn->y() + portIn->h() + 8, 250, 20, "Output Midi Map", m_data.midiMaps, "(no MIDI maps available)");
+ sync = new geChoice(x() + w() - 250, midiMap->y() + midiMap->h() + 8, 250, 20, "Sync");
+ new geBox(x(), sync->y() + sync->h() + 8, w(), h() - 150, "Restart Giada for the changes to take effect.");
+ end();
- if (m::kernelMidi::hasAPI(RtMidi::WINDOWS_MM))
- system->add("Multimedia MIDI");
+ labelsize(G_GUI_FONT_SIZE_BASE);
+ selection_color(G_COLOR_GREY_4);
-#elif defined(__APPLE__)
+ for (const auto& [key, value] : m_data.apis)
+ system->addItem(value.c_str(), key);
+ system->showItem(m_data.api);
+ system->onChange = [this](ID id) { m_data.api = id; invalidate(); };
- system->add("OSX Core MIDI");
+ portOut->showItem(m_data.outPort);
+ portOut->onChange = [this](ID id) { m_data.outPort = id; };
+ if (m_data.outPort == -1)
+ portOut->deactivate();
-#endif
+ portIn->showItem(m_data.inPort);
+ portIn->onChange = [this](ID id) { m_data.inPort = id; };
+ if (m_data.inPort == -1)
+ portIn->deactivate();
- switch (m::conf::conf.midiSystem)
- {
- case RtMidi::LINUX_ALSA:
- system->showItem("ALSA");
- break;
- case RtMidi::UNIX_JACK:
- system->showItem("Jack");
- break;
- case RtMidi::WINDOWS_MM:
- system->showItem("Multimedia MIDI");
- break;
- case RtMidi::MACOSX_CORE:
- system->showItem("OSX Core MIDI");
- break;
- default:
- system->value(0);
- break;
- }
+ enableOut->copy_tooltip("Enable Output port");
+ enableOut->value(m_data.outPort != -1);
+ enableOut->onChange = [this](bool b) {
+ if (b)
+ {
+ m_data.outPort = portOut->value();
+ portOut->activate();
+ }
+ else
+ {
+ m_data.outPort = -1;
+ portOut->deactivate();
+ }
+ };
+
+ enableIn->copy_tooltip("Enable Input port");
+ enableIn->value(m_data.inPort != -1);
+ enableIn->onChange = [this](bool b) {
+ if (b)
+ {
+ m_data.inPort = portIn->value();
+ portIn->activate();
+ }
+ else
+ {
+ m_data.inPort = -1;
+ portIn->deactivate();
+ }
+ };
+
+ midiMap->showItem(m_data.midiMap);
+ midiMap->onChange = [this](ID id) { m_data.midiMap = id; };
+
+ for (const auto& [key, value] : m_data.syncModes)
+ sync->addItem(value.c_str(), key);
+ sync->showItem(m_data.syncMode);
+ sync->onChange = [this](ID id) { m_data.syncMode = id; };
}
/* -------------------------------------------------------------------------- */
-void geTabMidi::cb_changeSystem(Fl_Widget* /*w*/, void* p) { ((geTabMidi*)p)->cb_changeSystem(); }
-
-/* -------------------------------------------------------------------------- */
-
-void geTabMidi::cb_changeSystem()
+void geTabMidi::invalidate()
{
- /* if the user changes MIDI device (eg ALSA->JACK) device menu deactivates.
- * If it returns to the original system, we re-fill the list by
- * querying m::kernelMidi. */
+ /* If the user changes MIDI device (eg ALSA->JACK) device menu deactivates.
+ If it returns to the original system, we re-fill the list by re-using
+ previous data. */
- if (systemInitValue == system->value())
+ if (m_initialApi == m_data.api && m_initialApi != -1)
{
- portOut->clear();
- fetchOutPorts();
portOut->activate();
- portIn->clear();
- fetchInPorts();
portIn->activate();
+ enableOut->activate();
+ enableIn->activate();
+ if (m_data.midiMaps.size() > 0)
+ midiMap->activate();
sync->activate();
}
else
{
portOut->deactivate();
- portOut->clear();
- portOut->add("-- restart to fetch device(s) --");
- portOut->value(0);
portIn->deactivate();
- portIn->clear();
- portIn->add("-- restart to fetch device(s) --");
- portIn->value(0);
+ enableOut->deactivate();
+ enableIn->deactivate();
+ midiMap->deactivate();
sync->deactivate();
}
}
-} // namespace v
-} // namespace giada
\ No newline at end of file
+
+/* -------------------------------------------------------------------------- */
+
+void geTabMidi::save() const
+{
+ c::config::save(m_data);
+}
+} // namespace giada::v
#ifndef GE_TAB_MIDI_H
#define GE_TAB_MIDI_H
+#include "glue/config.h"
+#include "gui/elems/basics/choice.h"
#include <FL/Fl_Group.H>
class geCheck;
-namespace giada
+namespace giada::v
{
-namespace v
-{
-class geChoice;
class geTabMidi : public Fl_Group
{
public:
+ struct geMenu : public geChoice
+ {
+ geMenu(int x, int y, int w, int h, const char* l, const std::vector<std::string>&,
+ const std::string& msgIfNotFound);
+ };
+
geTabMidi(int x, int y, int w, int h);
- void save();
+ void save() const;
geChoice* system;
- geChoice* portOut;
- geChoice* portIn;
- geChoice* midiMap;
+ geMenu* portOut;
+ geMenu* portIn;
+ geCheck* enableOut;
+ geCheck* enableIn;
+ geMenu* midiMap;
geChoice* sync;
- private:
- void fetchSystems();
- void fetchOutPorts();
- void fetchInPorts();
- void fetchMidiMaps();
+private:
+ void invalidate();
- static void cb_changeSystem(Fl_Widget* /*w*/, void* p);
- void cb_changeSystem();
+ c::config::MidiData m_data;
- int systemInitValue;
+ int m_initialApi;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "tabMisc.h"
-#include "core/conf.h"
#include "core/const.h"
-#include <FL/Fl_Tooltip.H>
namespace giada::v
{
-geTabMisc::geTabMisc(int X, int Y, int W, int H)
+geTabMisc::geTabMisc(int X, int Y, int W)
: geGroup(X, Y)
+, m_data(c::config::getMiscData())
, m_debugMsg(W - 230, 9, 230, 20, "Debug messages")
, m_tooltips(W - 230, 37, 230, 20, "Tooltips")
{
add(&m_debugMsg);
add(&m_tooltips);
- m_debugMsg.add("Disabled");
- m_debugMsg.add("To standard output");
- m_debugMsg.add("To file");
+ m_debugMsg.addItem("Disabled");
+ m_debugMsg.addItem("To standard output");
+ m_debugMsg.addItem("To file");
+ m_debugMsg.onChange = [this](ID id) { m_data.logMode = id; };
- m_tooltips.add("Disabled");
- m_tooltips.add("Enabled");
+ m_tooltips.addItem("Disabled");
+ m_tooltips.addItem("Enabled");
+ m_tooltips.onChange = [this](ID id) { m_data.showTooltips = id; };
- switch (m::conf::conf.logMode)
- {
- case LOG_MODE_MUTE:
- m_debugMsg.value(0);
- break;
- case LOG_MODE_STDOUT:
- m_debugMsg.value(1);
- break;
- case LOG_MODE_FILE:
- m_debugMsg.value(2);
- break;
- }
-
- m_tooltips.value(m::conf::conf.showTooltips);
+ m_debugMsg.showItem(m_data.logMode);
+ m_tooltips.showItem(m_data.showTooltips);
copy_label("Misc");
labelsize(G_GUI_FONT_SIZE_BASE);
void geTabMisc::save()
{
- switch (m_debugMsg.value())
- {
- case 0:
- m::conf::conf.logMode = LOG_MODE_MUTE;
- break;
- case 1:
- m::conf::conf.logMode = LOG_MODE_STDOUT;
- break;
- case 2:
- m::conf::conf.logMode = LOG_MODE_FILE;
- break;
- }
-
- m::conf::conf.showTooltips = m_tooltips.value();
- Fl_Tooltip::enable(m_tooltips.value());
+ c::config::save(m_data);
}
} // namespace giada::v
\ No newline at end of file
#ifndef GE_TAB_MISC_H
#define GE_TAB_MISC_H
+#include "glue/config.h"
#include "gui/elems/basics/choice.h"
#include "gui/elems/basics/group.h"
namespace giada::v
{
-class geChoice;
class geTabMisc : public geGroup
{
public:
- geTabMisc(int x, int y, int w, int h);
+ geTabMisc(int x, int y, int w);
void save();
- private:
+private:
+ c::config::MiscData m_data;
+
geChoice m_debugMsg;
geChoice m_tooltips;
};
#include "core/conf.h"
#include "core/const.h"
#include "core/graphics.h"
-#include "core/plugins/pluginManager.h"
+#include "glue/layout.h"
#include "glue/plugin.h"
-#include "gui/dialogs/browser/browserDir.h"
-#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/window.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/check.h"
#include "gui/elems/basics/input.h"
-#include "utils/fs.h"
#include "utils/gui.h"
#include "utils/string.h"
#include <FL/Fl.H>
#include <functional>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
geTabPlugins::geTabPlugins(int X, int Y, int W, int H)
-: Fl_Group(X, Y, W, H, "Plugins")
+: Fl_Group(X, Y, W, H, "Plug-ins")
+, m_browse(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm)
+, m_folderPath(m_browse.x() - 258, y() + 9, 250, G_GUI_UNIT)
+, m_scanButton(x() + w() - 150, m_folderPath.y() + m_folderPath.h() + 8, 150, G_GUI_UNIT)
+, m_info(x(), m_scanButton.y() + m_scanButton.h() + 8, w(), 240)
{
- m_browse = new geButton(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
- m_folderPath = new geInput(m_browse->x() - 258, y() + 9, 250, G_GUI_UNIT);
- m_scanButton = new geButton(x() + w() - 150, m_folderPath->y() + m_folderPath->h() + 8, 150, G_GUI_UNIT);
- m_info = new geBox(x(), m_scanButton->y() + m_scanButton->h() + 8, w(), 240);
-
end();
labelsize(G_GUI_FONT_SIZE_BASE);
selection_color(G_COLOR_GREY_4);
- m_info->label("Scan in progress. Please wait...");
- m_info->hide();
-
- m_folderPath->value(m::conf::conf.pluginPath.c_str());
- m_folderPath->label("Plugins folder");
+ m_info.hide();
- m_browse->callback(cb_browse, (void*)this);
+ m_folderPath.label("Plug-ins folder");
+ m_folderPath.onChange = [this](const std::string& v) {
+ m_data.pluginPath = v;
+ };
- m_scanButton->callback(cb_scan, (void*)this);
+ m_browse.callback(cb_browse, (void*)this);
+ m_scanButton.callback(cb_scan, (void*)this);
- refreshCount();
+ rebuild();
}
/* -------------------------------------------------------------------------- */
-void geTabPlugins::refreshCount()
+void geTabPlugins::rebuild()
{
- std::string scanLabel = "Scan (" + std::to_string(m::pluginManager::countAvailablePlugins()) + " found)";
- m_scanButton->copy_label(scanLabel.c_str());
+ m_data = c::config::getPluginData();
+
+ const std::string scanLabel = "Scan (" + std::to_string(m_data.numAvailablePlugins) + " found)";
+ m_scanButton.copy_label(scanLabel.c_str());
+
+ m_folderPath.value(m_data.pluginPath.c_str());
+ m_folderPath.redraw();
}
/* -------------------------------------------------------------------------- */
void geTabPlugins::cb_browse()
{
- v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory",
- m::conf::conf.patchPath, c::plugin::setPluginPathCb);
-
- static_cast<v::gdWindow*>(top_window())->addSubWindow(browser);
+ c::layout::openBrowserForPlugins(*static_cast<v::gdWindow*>(top_window()));
}
/* -------------------------------------------------------------------------- */
{
std::function<void(float)> callback = [this](float progress) {
std::string l = "Scan in progress (" + std::to_string((int)(progress * 100)) + "%). Please wait...";
- m_info->label(l.c_str());
+ m_info.label(l.c_str());
Fl::wait();
};
- m_info->show();
- m::pluginManager::scanDirs(m_folderPath->value(), callback);
- m::pluginManager::saveList(u::fs::getHomePath() + G_SLASH + "plugins.xml");
- m_info->hide();
- refreshCount();
+ m_info.show();
+ c::config::scanPlugins(m_folderPath.value(), callback);
+ m_info.hide();
+ rebuild();
}
/* -------------------------------------------------------------------------- */
void geTabPlugins::save()
{
- m::conf::conf.pluginPath = m_folderPath->value();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void geTabPlugins::refreshVstPath()
-{
- m_folderPath->value(m::conf::conf.pluginPath.c_str());
- m_folderPath->redraw();
+ c::config::save(m_data);
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
-#endif // WITH_VST
+#endif // WITH_VST
\ No newline at end of file
#ifdef WITH_VST
+#include "glue/config.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/button.h"
+#include "gui/elems/basics/input.h"
#include <FL/Fl_Group.H>
-class geInput;
-class geButton;
-class geBox;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geTabPlugins : public Fl_Group
{
geTabPlugins(int x, int y, int w, int h);
void save();
- void refreshVstPath();
+ void rebuild();
- private:
+private:
static void cb_scan(Fl_Widget* /*w*/, void* p);
static void cb_browse(Fl_Widget* /*w*/, void* p);
void cb_scan();
void cb_browse();
- void refreshCount();
+ c::config::PluginData m_data;
- geInput* m_folderPath;
- geButton* m_browse;
- geButton* m_scanButton;
- geBox* m_info;
+ geButton m_browse;
+ geInput m_folderPath;
+ geButton m_scanButton;
+ geBox m_info;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif // WITH_VST
* -------------------------------------------------------------------------- */
#include "glue/channel.h"
-#include "channel.h"
-#include "channelButton.h"
-#include "channelStatus.h"
-#include "column.h"
-#include "core/const.h"
#include "core/graphics.h"
-#include "core/model/model.h"
-#include "core/plugins/pluginHost.h"
#include "glue/events.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/pluginList.h"
+#include "glue/layout.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/dial.h"
#include "gui/elems/basics/statusButton.h"
-#include "utils/gui.h"
+#include "gui/elems/mainWindow/keyboard/channel.h"
+#include "gui/elems/mainWindow/keyboard/channelButton.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/ui.h"
#include <FL/Fl.H>
#include <FL/fl_draw.H>
-extern giada::v::gdMainWindow* G_MainWin;
+extern giada::v::Ui g_ui;
namespace giada::v
{
#ifdef WITH_VST
void geChannel::cb_openFxWindow()
{
- u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST);
+ c::layout::openChannelPluginListWindow(m_channel.id);
}
#endif
void geChannel::blink()
{
- if (u::gui::shouldBlink())
+ if (g_ui.shouldBlink())
mainButton->setPlayMode();
else
mainButton->setDefaultMode();
#include "core/channels/channel.h"
#include "core/const.h"
#include "core/model/model.h"
-#include "core/recorder.h"
#include "glue/channel.h"
+#include "src/core/actions/actions.h"
#include "utils/string.h"
#include <FL/fl_draw.H>
#include <FL/fl_draw.H>
#include <cassert>
-namespace giada
-{
-namespace v
+namespace giada::v
{
geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d)
: Fl_Menu_Button(x, y, w, h)
add("Loop . once . bar", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_ONCE_BAR);
add("Loop . repeat", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_REPEAT);
add("Oneshot . basic", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC);
+ add("Oneshot . basic . pause", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC_PAUSE);
add("Oneshot . press", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_PRESS);
add("Oneshot . retrig", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_RETRIG);
add("Oneshot . endless", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_ENDLESS);
case SamplePlayerMode::SINGLE_BASIC:
fl_draw_pixmap(oneshotBasic_xpm, x() + 1, y() + 1);
break;
+ case SamplePlayerMode::SINGLE_BASIC_PAUSE:
+ fl_draw_pixmap(oneshotBasicPause_xpm, x() + 1, y() + 1);
+ break;
case SamplePlayerMode::SINGLE_PRESS:
fl_draw_pixmap(oneshotPress_xpm, x() + 1, y() + 1);
break;
case SamplePlayerMode::SINGLE_ENDLESS:
fl_draw_pixmap(oneshotEndless_xpm, x() + 1, y() + 1);
break;
+ default:
+ assert(false);
+ break;
}
}
{
c::channel::setSamplePlayerMode(m_channel.id, static_cast<SamplePlayerMode>(mode));
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
{
struct Data;
}
+
namespace giada::v
{
class geChannelMode : public Fl_Menu_Button
void draw() override;
- private:
+private:
static void cb_changeMode(Fl_Widget* /*w*/, void* p);
void cb_changeMode(int mode);
*
* -------------------------------------------------------------------------- */
-#include "channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
#include "core/const.h"
#include "glue/channel.h"
+#include "utils/math.h"
#include <FL/fl_draw.H>
namespace giada::v
fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // reset border
fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // reset background
- ChannelStatus playStatus = m_channel.getPlayStatus();
- ChannelStatus recStatus = m_channel.getRecStatus();
- Pixel pos = 0;
+ const ChannelStatus playStatus = m_channel.getPlayStatus();
+ const ChannelStatus recStatus = m_channel.getRecStatus();
+ const Frame tracker = m_channel.sample->getTracker();
+ const Frame begin = m_channel.sample->getBegin();
+ const Frame end = m_channel.sample->getEnd();
+ const Pixel pos = u::math::map(tracker, begin, end, 0, w());
if (playStatus == ChannelStatus::WAIT ||
playStatus == ChannelStatus::ENDING ||
}
else if (playStatus == ChannelStatus::PLAY)
{
- /* Equation for the progress bar:
- ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
- Frame tracker = m_channel.sample->getTracker();
- Frame begin = m_channel.sample->getBegin();
- Frame end = m_channel.sample->getEnd();
- pos = ((tracker - begin) * (w() - 1)) / ((end - begin));
fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
+ fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1);
}
else
+ {
fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // status empty
-
- if (pos != 0)
- fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1);
+ fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_GREY_4);
+ }
}
} // namespace giada::v
\ No newline at end of file
#include "midiChannel.h"
#include "sampleChannel.h"
#include "utils/fs.h"
+#include "utils/gui.h"
#include "utils/log.h"
#include "utils/string.h"
#include <FL/Fl_Menu_Button.H>
gch = new geMidiChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d);
geResizerBar* bar = new geResizerBar(x(), gch->y() + gch->h(), w(),
- G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::VERTICAL, gch);
+ G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL,
+ geResizerBar::Mode::MOVE);
/* Update the column height while dragging the resizer bar. */
- bar->onDrag = [this](const Fl_Widget* /*w*/) {
+ bar->onDrag = [this](const Fl_Widget& /*w*/) {
resizable(nullptr);
size(this->w(), (child(children() - 1)->y() - y()) + G_GUI_INNER_MARGIN);
};
/* Store the channel height in model when the resizer bar is released. */
- bar->onRelease = [channelId = d.id, this](const Fl_Widget* w) {
+ bar->onRelease = [channelId = d.id, this](const Fl_Widget& w) {
resizable(this);
- c::channel::setHeight(channelId, w->h());
+ c::channel::setHeight(channelId, w.h());
};
m_channels.push_back(gch);
u::log::print("[geColumn::cb_addChannel] id = %d\n", id);
Fl_Menu_Item menu[] = {
- {"Add Sample channel"},
- {"Add MIDI channel"},
- {"Remove"},
- {0}};
+ u::gui::makeMenuItem("Add Sample channel"),
+ u::gui::makeMenuItem("Add MIDI channel"),
+ u::gui::makeMenuItem("Remove"),
+ {}};
if (countChannels() > 0)
menu[2].deactivate();
#include <vector>
class geButton;
-class geResizerBar;
namespace giada::v
{
+class geResizerBar;
class geKeyboard;
class geChannel;
class geColumn : public Fl_Group
geResizerBar* resizerBar;
- private:
+private:
static void cb_addChannel(Fl_Widget* /*w*/, void* p);
void cb_addChannel();
#include "gui/dispatcher.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/resizerBar.h"
+#include "gui/ui.h"
#include "sampleChannel.h"
#include "utils/fs.h"
#include "utils/log.h"
#include <FL/fl_draw.H>
#include <cassert>
-namespace giada
-{
-namespace v
+extern giada::v::Ui g_ui;
+
+namespace giada::v
{
geKeyboard::geKeyboard(int X, int Y, int W, int H)
: geScroll(X, Y, W, H, Fl_Scroll::BOTH_ALWAYS)
case FL_KEYDOWN: // Keyboard key pushed
case FL_KEYUP:
{ // Keyboard key released
- dispatcher::dispatchKey(e);
+ g_ui.dispatcher.dispatchKey(e);
return 1;
}
case FL_DND_ENTER: // return(1) for these events to 'accept' dnd
/* Add a new column + a new resizer bar. */
- geResizerBar* bar = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::HORIZONTAL);
+ geResizerBar* bar = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::Direction::HORIZONTAL);
geColumn* column = new geColumn(colx, y(), width, G_GUI_UNIT, m_columnId.generate(id), bar);
/* Store the column width in layout when the resizer bar is released. */
- bar->onRelease = [=](const Fl_Widget* /*w*/) {
+ bar->onRelease = [=](const Fl_Widget& /*w*/) {
storeLayout();
};
layout.push_back({c->id, c->w()});
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#include <vector>
class geButton;
-class geResizerBar;
-namespace giada
-{
-namespace v
+namespace giada::v
{
+class geResizerBar;
class geColumn;
class geChannel;
class geKeyboard : public geScroll
void forEachColumn(std::function<void(const geColumn& c)> f) const;
/* layout
- The column layout. Each element is a column with a specific witdh. */
+ The column layout. Each element is a column with a specific width. */
std::vector<ColumnLayout> layout;
- private:
+private:
static constexpr int COLUMN_GAP = 20;
static void cb_addColumn(Fl_Widget* /*w*/, void* p);
geButton* m_addColumnBtn;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* -------------------------------------------------------------------------- */
-#include "midiChannel.h"
-#include "column.h"
+#include "gui/elems/mainWindow/keyboard/midiChannel.h"
#include "core/const.h"
#include "core/graphics.h"
-#include "core/model/model.h"
-#include "core/recorder.h"
#include "glue/channel.h"
#include "glue/io.h"
+#include "glue/layout.h"
#include "glue/recorder.h"
-#include "gui/dialogs/actionEditor/midiActionEditor.h"
-#include "gui/dialogs/channelNameInput.h"
-#include "gui/dialogs/keyGrabber.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputChannel.h"
-#include "gui/dialogs/midiIO/midiOutputMidiCh.h"
-#include "gui/dialogs/pluginList.h"
#include "gui/dialogs/warnings.h"
#include "gui/dispatcher.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/dial.h"
#include "gui/elems/basics/statusButton.h"
-#include "midiChannelButton.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/midiChannelButton.h"
#include "utils/gui.h"
#include "utils/string.h"
#include <FL/Fl_Menu_Button.H>
#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
namespace
{
case Menu::__END_CLEAR_ACTION_SUBMENU__:
break;
case Menu::EDIT_ACTIONS:
- u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR);
+ c::layout::openMidiActionEditor(data.id);
break;
case Menu::CLEAR_ACTIONS_ALL:
c::recorder::clearAllActions(data.id);
break;
case Menu::SETUP_KEYBOARD_INPUT:
- u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER);
+ c::layout::openKeyGrabberWindow(data);
break;
case Menu::SETUP_MIDI_INPUT:
- u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT);
+ c::layout::openChannelMidiInputWindow(data.id);
break;
case Menu::SETUP_MIDI_OUTPUT:
- u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT);
+ c::layout::openMidiChannelMidiOutputWindow(data.id);
break;
case Menu::CLONE_CHANNEL:
c::channel::cloneChannel(data.id);
break;
case Menu::RENAME_CHANNEL:
- u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME);
+ c::layout::openRenameChannelWindow(data);
break;
case Menu::DELETE_CHANNEL:
c::channel::deleteChannel(data.id);
void geMidiChannel::cb_playButton()
{
- v::dispatcher::dispatchTouch(*this, playButton->value());
+ m_channel.viewDispatcher.dispatchTouch(*this, playButton->value());
}
/* -------------------------------------------------------------------------- */
void geMidiChannel::cb_openMenu()
{
Fl_Menu_Item rclick_menu[] = {
- {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS},
- {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU},
- {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL},
- {0},
- {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT},
- {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT},
- {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT},
- {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL},
- {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL},
- {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL},
- {0}};
+ u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS),
+ u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU),
+ u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL),
+ {},
+ u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT),
+ u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT),
+ u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT),
+ u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL),
+ u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL),
+ u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL),
+ {}};
/* No 'clear actions' if there are no actions. */
packWidgets();
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
*
* -------------------------------------------------------------------------- */
-#include "sampleChannel.h"
-#include "channelMode.h"
-#include "channelStatus.h"
-#include "column.h"
-#include "core/channels/channel.h"
-#include "core/channels/samplePlayer.h"
-#include "core/clock.h"
-#include "core/conf.h"
+#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
#include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
-#include "core/recorder.h"
-#include "core/wave.h"
#include "glue/channel.h"
#include "glue/events.h"
#include "glue/io.h"
+#include "glue/layout.h"
#include "glue/recorder.h"
#include "glue/storage.h"
-#include "gui/dialogs/actionEditor/sampleActionEditor.h"
-#include "gui/dialogs/browser/browserLoad.h"
-#include "gui/dialogs/browser/browserSave.h"
-#include "gui/dialogs/channelNameInput.h"
-#include "gui/dialogs/keyGrabber.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputChannel.h"
-#include "gui/dialogs/midiIO/midiOutputSampleCh.h"
-#include "gui/dialogs/sampleEditor.h"
-#include "gui/dialogs/warnings.h"
#include "gui/dispatcher.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/dial.h"
#include "gui/elems/basics/statusButton.h"
-#include "keyboard.h"
-#include "sampleChannelButton.h"
+#include "gui/elems/mainWindow/keyboard/channelMode.h"
+#include "gui/elems/mainWindow/keyboard/channelStatus.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/keyboard/sampleChannelButton.h"
#include "utils/gui.h"
-#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
namespace
{
}
case Menu::LOAD_SAMPLE:
{
- gdWindow* w = new gdBrowserLoad("Browse sample",
- m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id);
- u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
+ c::layout::openBrowserForSampleLoad(data.id);
break;
}
case Menu::EXPORT_SAMPLE:
{
- gdWindow* w = new gdBrowserSave("Save sample",
- m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id);
- u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
+ c::layout::openBrowserForSampleSave(data.id);
break;
}
case Menu::SETUP_KEYBOARD_INPUT:
{
- u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data),
- WID_KEY_GRABBER);
+ c::layout::openKeyGrabberWindow(data);
break;
}
case Menu::SETUP_MIDI_INPUT:
{
- u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id),
- WID_MIDI_INPUT);
+ c::layout::openChannelMidiInputWindow(data.id);
break;
}
case Menu::SETUP_MIDI_OUTPUT:
{
- u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id),
- WID_MIDI_OUTPUT);
+ c::layout::openSampleChannelMidiOutputWindow(data.id);
break;
}
case Menu::EDIT_SAMPLE:
{
- u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id),
- WID_SAMPLE_EDITOR);
+ c::layout::openSampleEditor(data.id);
break;
}
case Menu::EDIT_ACTIONS:
{
- u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id),
- WID_ACTION_EDITOR);
+ c::layout::openSampleActionEditor(data.id);
break;
}
case Menu::CLEAR_ACTIONS:
}
case Menu::RENAME_CHANNEL:
{
- u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data),
- WID_SAMPLE_NAME);
+ c::layout::openRenameChannelWindow(data);
break;
}
case Menu::FREE_CHANNEL:
void geSampleChannel::cb_playButton()
{
- v::dispatcher::dispatchTouch(*this, playButton->value());
+ m_channel.viewDispatcher.dispatchTouch(*this, playButton->value());
}
/* -------------------------------------------------------------------------- */
/* If you're recording (input or actions) no menu is allowed; you can't do
anything, especially deallocate the channel. */
- if (m::recManager::isRecording())
+ if (m_channel.isRecordingAction() || m_channel.isRecordingInput())
return;
Fl_Menu_Item rclick_menu[] = {
- {"Input monitor", 0, menuCallback, (void*)Menu::INPUT_MONITOR,
- FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)},
- {"Overdub protection", 0, menuCallback, (void*)Menu::OVERDUB_PROTECTION,
- FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)},
- {"Load new sample...", 0, menuCallback, (void*)Menu::LOAD_SAMPLE},
- {"Export sample to file...", 0, menuCallback, (void*)Menu::EXPORT_SAMPLE},
- {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT},
- {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT},
- {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT},
- {"Edit sample...", 0, menuCallback, (void*)Menu::EDIT_SAMPLE},
- {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS},
- {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU},
- {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL},
- {"Volume", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME},
- {"Start/Stop", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP},
- {0},
- {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL},
- {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL},
- {"Free", 0, menuCallback, (void*)Menu::FREE_CHANNEL},
- {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL},
- {0}};
+ u::gui::makeMenuItem("Input monitor", menuCallback, (void*)Menu::INPUT_MONITOR,
+ FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)),
+ u::gui::makeMenuItem("Overdub protection", menuCallback, (void*)Menu::OVERDUB_PROTECTION,
+ FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)),
+ u::gui::makeMenuItem("Load new sample...", menuCallback, (void*)Menu::LOAD_SAMPLE),
+ u::gui::makeMenuItem("Export sample to file...", menuCallback, (void*)Menu::EXPORT_SAMPLE),
+ u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT),
+ u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT),
+ u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT),
+ u::gui::makeMenuItem("Edit sample...", menuCallback, (void*)Menu::EDIT_SAMPLE),
+ u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS),
+ u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU),
+ u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL),
+ u::gui::makeMenuItem("Volume", menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME),
+ u::gui::makeMenuItem("Start/Stop", menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP),
+ {},
+ u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL),
+ u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL),
+ u::gui::makeMenuItem("Free", menuCallback, (void*)Menu::FREE_CHANNEL),
+ u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL),
+ {}};
if (m_channel.sample->waveId == 0)
{
packWidgets();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
geChannelMode* modeBox;
geStatusButton* readActions;
- private:
+private:
static void cb_playButton(Fl_Widget* /*w*/, void* p);
static void cb_openMenu(Fl_Widget* /*w*/, void* p);
static void cb_readActions(Fl_Widget* /*w*/, void* p);
#include "utils/string.h"
#include <FL/Fl.H>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
: geChannelButton(x, y, w, h, d)
}
return ret;
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
*
* --------------------------------------------------------------------------- */
-#include "mainIO.h"
+#include "gui/elems/mainWindow/mainIO.h"
#include "core/const.h"
#include "core/graphics.h"
#include "glue/channel.h"
#include "glue/events.h"
+#include "glue/layout.h"
#include "glue/main.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/pluginList.h"
#include "gui/elems/basics/dial.h"
#include "gui/elems/basics/statusButton.h"
#include "gui/elems/soundMeter.h"
#include "utils/gui.h"
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
geMainIO::geMainIO(int x, int y)
: gePack(x, y, Direction::HORIZONTAL)
void geMainIO::cb_masterFxOut()
{
- u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_OUT_CHANNEL_ID), WID_FX_LIST);
+ c::layout::openMasterOutPluginListWindow();
}
void geMainIO::cb_masterFxIn()
{
- u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_IN_CHANNEL_ID), WID_FX_LIST);
+ c::layout::openMasterInPluginListWindow();
}
#endif
void geMainIO::refresh()
{
- outMeter.mixerPeak = m_io.getMasterOutPeak();
- inMeter.mixerPeak = m_io.getMasterInPeak();
+ outMeter.peak = m_io.getMasterOutPeak();
+ outMeter.ready = m_io.isKernelReady();
+ inMeter.peak = m_io.getMasterInPeak();
+ inMeter.ready = m_io.isKernelReady();
outMeter.redraw();
inMeter.redraw();
}
inToOut.value(m_io.inToOut);
#endif
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
#endif
#include "glue/main.h"
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geMainIO : public gePack
{
void setMasterFxInFull(bool v);
#endif
- private:
+private:
static void cb_outVol(Fl_Widget* /*w*/, void* p);
static void cb_inVol(Fl_Widget* /*w*/, void* p);
static void cb_inToOut(Fl_Widget* /*w*/, void* p);
geStatusButton masterFxIn;
#endif
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
*
* -------------------------------------------------------------------------- */
-#include "mainMenu.h"
-#include "core/conf.h"
+#include "gui/elems/mainWindow/mainMenu.h"
#include "core/const.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/model/model.h"
#include "core/patch.h"
+#include "glue/layout.h"
#include "glue/main.h"
-#include "glue/storage.h"
-#include "gui/dialogs/about.h"
-#include "gui/dialogs/browser/browserLoad.h"
-#include "gui/dialogs/browser/browserSave.h"
-#include "gui/dialogs/config.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/dialogs/midiIO/midiInputMaster.h"
-#include "gui/dialogs/warnings.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/button.h"
#include "keyboard/keyboard.h"
#include "utils/gui.h"
#include <FL/Fl_Menu_Button.H>
-#include <cassert>
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace v
+namespace giada::v
{
geMainMenu::geMainMenu(int x, int y)
: gePack(x, y, Direction::HORIZONTAL)
edit->callback(cb_edit, (void*)this);
about->callback([](Fl_Widget* /*w*/, void* /*v*/) {
- u::gui::openSubWindow(G_MainWin, new gdAbout(), WID_ABOUT);
+ c::layout::openAboutWindow();
});
config->callback([](Fl_Widget* /*w*/, void* /*v*/) {
- u::gui::openSubWindow(G_MainWin, new gdConfig(400, 370), WID_CONFIG);
+ c::layout::openConfigWindow();
});
}
/* An Fl_Menu_Button is made of many Fl_Menu_Item */
Fl_Menu_Item menu[] = {
- {"Open project..."},
- {"Save project..."},
- {"Close project"},
+ u::gui::makeMenuItem("Open project..."),
+ u::gui::makeMenuItem("Save project..."),
+ u::gui::makeMenuItem("Close project"),
#ifndef NDEBUG
- {"Debug stats"},
+ u::gui::makeMenuItem("Debug stats"),
#endif
- {"Quit Giada"},
- {0}};
+ u::gui::makeMenuItem("Quit Giada"),
+ {}};
Fl_Menu_Button b(0, 0, 100, 50);
b.box(G_CUSTOM_BORDER_BOX);
if (strcmp(m->label(), "Open project...") == 0)
{
- gdWindow* childWin = new gdBrowserLoad("Open project",
- conf::conf.patchPath, c::storage::loadProject, 0);
- u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER);
+ c::layout::openBrowserForProjectLoad();
}
else if (strcmp(m->label(), "Save project...") == 0)
{
- gdWindow* childWin = new gdBrowserSave("Save project", conf::conf.patchPath,
- patch::patch.name, c::storage::saveProject, 0);
- u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER);
+ c::layout::openBrowserForProjectSave();
}
else if (strcmp(m->label(), "Close project") == 0)
{
c::main::closeProject();
}
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
else if (strcmp(m->label(), "Debug stats") == 0)
{
- m::model::debug();
+ c::main::printDebugInfo();
}
#endif
else if (strcmp(m->label(), "Quit Giada") == 0)
{
- G_MainWin->do_callback();
+ c::main::quitGiada();
}
}
void geMainMenu::cb_edit()
{
- Fl_Menu_Item menu[] = {
- {"Free all Sample channels"},
- {"Clear all actions"},
- {"Setup global MIDI input..."},
- {0}};
+ c::main::MainMenu menu = c::main::getMainMenu();
- menu[0].deactivate();
- menu[1].deactivate();
+ Fl_Menu_Item menuItem[] = {
+ u::gui::makeMenuItem("Free all Sample channels"),
+ u::gui::makeMenuItem("Clear all actions"),
+ u::gui::makeMenuItem("Setup global MIDI input..."),
+ {}};
- if (m::mh::hasAudioData())
- menu[0].activate();
- if (m::mh::hasActions())
- menu[1].activate();
+ menuItem[0].deactivate();
+ menuItem[1].deactivate();
+
+ if (menu.hasAudioData)
+ menuItem[0].activate();
+ if (menu.hasActions)
+ menuItem[1].activate();
Fl_Menu_Button b(0, 0, 100, 50);
b.box(G_CUSTOM_BORDER_BOX);
b.textcolor(G_COLOR_LIGHT_2);
b.color(G_COLOR_GREY_2);
- const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
+ const Fl_Menu_Item* m = menuItem->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
if (!m)
return;
else if (strcmp(m->label(), "Clear all actions") == 0)
c::main::clearAllActions();
else if (strcmp(m->label(), "Setup global MIDI input...") == 0)
- u::gui::openSubWindow(G_MainWin, new gdMidiInputMaster(), WID_MIDI_INPUT);
+ c::layout::openMasterMidiInputWindow();
}
-
-} // namespace v
-} // namespace giada
+} // namespace giada::v
class geButton;
-namespace giada
-{
-namespace v
+namespace giada::v
{
class geMainMenu : public gePack
{
public:
geMainMenu(int x, int y);
- private:
+private:
static void cb_file(Fl_Widget* /*w*/, void* p);
static void cb_edit(Fl_Widget* /*w*/, void* p);
void cb_file();
geButton* config;
geButton* about;
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
* -------------------------------------------------------------------------- */
#include "mainTimer.h"
-#include "core/clock.h"
#include "core/const.h"
#include "core/graphics.h"
#include "glue/events.h"
+#include "glue/layout.h"
#include "glue/main.h"
-#include "gui/dialogs/beatsInput.h"
-#include "gui/dialogs/bpmInput.h"
-#include "gui/dialogs/mainWindow.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/choice.h"
#include "utils/gui.h"
#include "utils/string.h"
-extern giada::v::gdMainWindow* G_MainWin;
-
namespace giada::v
{
geMainTimer::geMainTimer(int x, int y)
void geMainTimer::cb_bpm()
{
- u::gui::openSubWindow(G_MainWin, new gdBpmInput(m_bpm.label()), WID_BPM);
+ c::layout::openBpmWindow(m_bpm.label());
}
/* -------------------------------------------------------------------------- */
void geMainTimer::cb_meter()
{
- u::gui::openSubWindow(G_MainWin, new gdBeatsInput(), WID_BEATS);
+ c::layout::openBeatsWindow(m_timer.beats, m_timer.bars);
}
/* -------------------------------------------------------------------------- */
*
* -------------------------------------------------------------------------- */
-#include "mainTransport.h"
-#include "core/clock.h"
+#include "gui/elems/mainWindow/mainTransport.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/recManager.h"
-#include "core/sequencer.h"
#include "glue/events.h"
#include "glue/main.h"
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "gui/elems/basics/statusButton.h"
namespace giada::v
{
void geMainTransport::refresh()
{
- m_play.setStatus(m::clock::isRunning());
- m_recAction.setStatus(m::recManager::isRecordingAction());
- m_recInput.setStatus(m::recManager::isRecordingInput());
- m_metronome.setStatus(m::sequencer::isMetronomeOn());
- m_recTriggerMode.setStatus(m::conf::conf.recTriggerMode == RecTriggerMode::SIGNAL);
- m_inputRecMode.setStatus(m::conf::conf.inputRecMode == InputRecMode::FREE);
+ c::main::Transport transport = c::main::getTransport();
+
+ m_play.setStatus(transport.isRunning);
+ m_recAction.setStatus(transport.isRecordingAction);
+ m_recInput.setStatus(transport.isRecordingInput);
+ m_metronome.setStatus(transport.isMetronomeOn);
+ m_recTriggerMode.setStatus(transport.recTriggerMode == RecTriggerMode::SIGNAL);
+ m_inputRecMode.setStatus(transport.inputRecMode == InputRecMode::FREE);
}
} // namespace giada::v
#ifdef WITH_VST
-#include "pluginBrowser.h"
+#include "gui/elems/plugin/pluginBrowser.h"
#include "core/const.h"
-#include "core/plugins/plugin.h"
-#include "core/plugins/pluginHost.h"
#include "core/plugins/pluginManager.h"
+#include "glue/plugin.h"
#include "gui/elems/basics/boxtypes.h"
#include <FL/fl_draw.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
gePluginBrowser::gePluginBrowser(int x, int y, int w, int h)
: Fl_Browser(x, y, w, h)
+, m_widths{0}
{
box(G_CUSTOM_BORDER_BOX);
textsize(G_GUI_FONT_SIZE_BASE);
computeWidths();
- column_widths(widths);
+ column_widths(m_widths);
column_char('\t'); // tabs as column delimiters
refresh();
add("NAME\tMANUFACTURER\tCATEGORY\tFORMAT\tUID");
add("---\t---\t---\t---\t---");
- for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++)
+ for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo())
{
- m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i);
- std::string m = m::pluginManager::doesPluginExist(pi.uid) ? "" : "@-";
- std::string s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m +
- pi.category + "\t" + m + pi.format + "\t" + m + pi.uid;
- add(s.c_str());
- }
+ std::string s;
+ if (pi.isKnown)
+ {
+ std::string m = pi.exists ? "" : "@-";
+
+ s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m +
+ pi.category + "\t" + m + pi.format + "\t" + m + pi.uid;
+ }
+ else
+ std::string s = "?\t?\t?\t?\t? " + pi.uid + " ?";
- for (int i = 0; i < m::pluginManager::countUnknownPlugins(); i++)
- {
- std::string s = "?\t?\t?\t?\t? " + m::pluginManager::getUnknownPluginInfo(i) + " ?";
add(s.c_str());
}
}
void gePluginBrowser::computeWidths()
{
int w0, w1, w3;
- for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++)
+ for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo())
{
- m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i);
- w0 = (int)fl_width(pi.name.c_str());
- w1 = (int)fl_width(pi.manufacturerName.c_str());
- w3 = (int)fl_width(pi.format.c_str());
- if (w0 > widths[0])
- widths[0] = w0;
- if (w1 > widths[1])
- widths[1] = w1;
- if (w3 > widths[3])
- widths[3] = w3;
+ w0 = static_cast<int>(fl_width(pi.name.c_str()));
+ w1 = static_cast<int>(fl_width(pi.manufacturerName.c_str()));
+ w3 = static_cast<int>(fl_width(pi.format.c_str()));
+ if (w0 > m_widths[0])
+ m_widths[0] = w0;
+ if (w1 > m_widths[1])
+ m_widths[1] = w1;
+ if (w3 > m_widths[3])
+ m_widths[3] = w3;
}
- widths[0] += 60;
- widths[1] += 60;
- widths[2] = static_cast<int>(fl_width("CATEGORY") + 60);
- widths[3] += 60;
- widths[4] = 0;
+ m_widths[0] += 60;
+ m_widths[1] += 60;
+ m_widths[2] = static_cast<int>(fl_width("CATEGORY") + 60);
+ m_widths[3] += 60;
+ m_widths[4] = 0;
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
#include <FL/Fl_Browser.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
class gePluginBrowser : public Fl_Browser
{
void refresh();
- private:
+private:
void computeWidths();
- int widths[5] = {0};
+ int m_widths[5];
};
-} // namespace v
-} // namespace giada
+} // namespace giada::v
#endif
/* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for
the parent window 'add plugin'. */
- int pwid = m_plugin.id + 1;
+ const int pwid = m_plugin.id + 1;
gdWindow* parent = static_cast<gdWindow*>(window());
gdWindow* child = parent->getChild(pwid);
+ /* If Plug-in window is already opened, just raise it on top and quit. */
+
if (child != nullptr)
{
- child->show(); // Raise it to top
+ child->show();
+ return;
}
+
+ if (m_plugin.hasEditor)
+ child = new gdPluginWindowGUI(m_plugin);
else
- {
- if (m_plugin.hasEditor)
- child = new gdPluginWindowGUI(m_plugin);
- else
- child = new gdPluginWindow(m_plugin);
- child->setId(pwid);
- parent->addSubWindow(child);
- }
+ child = new gdPluginWindow(m_plugin);
+ child->setId(pwid);
+ parent->addSubWindow(child);
}
/* -------------------------------------------------------------------------- */
* -------------------------------------------------------------------------- */
#include "pitchTool.h"
-#include "core/clock.h"
#include "core/const.h"
#include "core/graphics.h"
#include "core/model/model.h"
#include "utils/string.h"
#include <FL/Fl.H>
-namespace giada
-{
-namespace v
+namespace giada::v
{
gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y)
: gePack(x, y, Direction::HORIZONTAL)
void gePitchTool::cb_setPitchToBar()
{
- c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInBar(),
+ c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInBar(),
Thread::MAIN);
}
void gePitchTool::cb_setPitchToSong()
{
- c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInLoop(),
+ c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInLoop(),
Thread::MAIN);
}
{
c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN);
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
{
struct Data;
}
+
namespace giada::v
{
class gePitchTool : public gePack
void rebuild(const c::sampleEditor::Data& d);
void update(float v, bool isDial = false);
- private:
+private:
static void cb_setPitch(Fl_Widget* /*w*/, void* p);
static void cb_setPitchToBar(Fl_Widget* /*w*/, void* p);
static void cb_setPitchToSong(Fl_Widget* /*w*/, void* p);
#include "glue/sampleEditor.h"
#include "gui/dialogs/sampleEditor.h"
#include "gui/elems/basics/boxtypes.h"
+#include "utils/gui.h"
#include "waveform.h"
#include <FL/Fl_Menu_Button.H>
#include <FL/Fl_Menu_Item.H>
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-geWaveTools::geWaveTools(int x, int y, int w, int h)
+geWaveTools::geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal)
: Fl_Scroll(x, y, w, h, nullptr)
, m_data(nullptr)
{
hscrollbar.labelcolor(G_COLOR_LIGHT_1);
hscrollbar.slider(G_CUSTOM_BORDER_BOX);
- waveform = new v::geWaveform(x, y, w, h - 24);
+ waveform = new v::geWaveform(x, y, w, h - 24, gridEnabled, gridVal);
}
/* -------------------------------------------------------------------------- */
void geWaveTools::openMenu()
{
Fl_Menu_Item menu[] = {
- {"Cut", 0, menuCallback_, (void*)Menu::CUT, 0, 0, 0, 0, 0},
- {"Copy", 0, menuCallback_, (void*)Menu::COPY, 0, 0, 0, 0, 0},
- {"Paste", 0, menuCallback_, (void*)Menu::PASTE, 0, 0, 0, 0, 0},
- {"Trim", 0, menuCallback_, (void*)Menu::TRIM, 0, 0, 0, 0, 0},
- {"Silence", 0, menuCallback_, (void*)Menu::SILENCE, 0, 0, 0, 0, 0},
- {"Reverse", 0, menuCallback_, (void*)Menu::REVERSE, 0, 0, 0, 0, 0},
- {"Normalize", 0, menuCallback_, (void*)Menu::NORMALIZE, 0, 0, 0, 0, 0},
- {"Fade in", 0, menuCallback_, (void*)Menu::FADE_IN, 0, 0, 0, 0, 0},
- {"Fade out", 0, menuCallback_, (void*)Menu::FADE_OUT, 0, 0, 0, 0, 0},
- {"Smooth edges", 0, menuCallback_, (void*)Menu::SMOOTH_EDGES, 0, 0, 0, 0, 0},
- {"Set begin/end here", 0, menuCallback_, (void*)Menu::SET_BEGIN_END, 0, 0, 0, 0, 0},
- {"Copy to new channel", 0, menuCallback_, (void*)Menu::TO_NEW_CHANNEL, 0, 0, 0, 0, 0},
- {0}};
+ u::gui::makeMenuItem("Cut", menuCallback_, (void*)Menu::CUT),
+ u::gui::makeMenuItem("Copy", menuCallback_, (void*)Menu::COPY),
+ u::gui::makeMenuItem("Paste", menuCallback_, (void*)Menu::PASTE),
+ u::gui::makeMenuItem("Trim", menuCallback_, (void*)Menu::TRIM),
+ u::gui::makeMenuItem("Silence", menuCallback_, (void*)Menu::SILENCE),
+ u::gui::makeMenuItem("Reverse", menuCallback_, (void*)Menu::REVERSE),
+ u::gui::makeMenuItem("Normalize", menuCallback_, (void*)Menu::NORMALIZE),
+ u::gui::makeMenuItem("Fade in", menuCallback_, (void*)Menu::FADE_IN),
+ u::gui::makeMenuItem("Fade out", menuCallback_, (void*)Menu::FADE_OUT),
+ u::gui::makeMenuItem("Smooth edges", menuCallback_, (void*)Menu::SMOOTH_EDGES),
+ u::gui::makeMenuItem("Set begin/end here", menuCallback_, (void*)Menu::SET_BEGIN_END),
+ u::gui::makeMenuItem("Copy to new channel", menuCallback_, (void*)Menu::TO_NEW_CHANNEL),
+ {}};
if (!waveform->isSelected())
{
{
struct Data;
}
+
namespace giada::v
{
class geWaveform;
class geWaveTools : public Fl_Scroll
{
public:
- geWaveTools(int x, int y, int w, int h);
+ geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal);
void resize(int x, int y, int w, int h) override;
int handle(int e) override;
v::geWaveform* waveform;
- private:
+private:
void openMenu();
const c::sampleEditor::Data* m_data;
*
* -------------------------------------------------------------------------- */
-#include "waveform.h"
-#include "core/conf.h"
+#include "gui/elems/sampleEditor/waveform.h"
#include "core/const.h"
#include "core/mixer.h"
#include "core/model/model.h"
#include <cassert>
#include <cmath>
-namespace giada
+namespace giada::v
{
-namespace v
-{
-geWaveform::geWaveform(int x, int y, int w, int h)
+geWaveform::geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal)
: Fl_Widget(x, y, w, h, nullptr)
, m_selection{}
, m_data(nullptr)
{
m_waveform.size = w;
- m_grid.snap = m::conf::conf.sampleEditorGridOn;
- m_grid.level = m::conf::conf.sampleEditorGridVal;
+ m_grid.snap = gridEnabled;
+ m_grid.level = gridVal;
}
/* -------------------------------------------------------------------------- */
fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas
/* Draw things from 'from' (offset driven by the scrollbar) to 'to' (width of
- parent window). We don't draw the entire waveform, only the visibile part. */
+ parent window). We don't draw the entire waveform, only the visible part. */
int from = abs(x() - parent()->x());
int to = from + parent()->w();
/* -------------------------------------------------------------------------- */
+#ifdef G_OS_WINDOWS
+#undef IN
+#endif
+
void geWaveform::setZoom(Zoom z)
{
if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR))
m_selection.b = m_data->waveSize - 1;
redraw();
}
-} // namespace v
-} // namespace giada
+} // namespace giada::v
\ No newline at end of file
{
struct Data;
}
+
namespace giada::v
{
class geWaveform : public Fl_Widget
OUT
};
- geWaveform(int x, int y, int w, int h);
+ geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal);
void draw() override;
int handle(int e) override;
void setWaveId(ID /*id*/){/* TODO m_waveId = id;*/};
- private:
+private:
static const int FLAG_WIDTH = 20;
static const int FLAG_HEIGHT = 20;
static const int BORDER = 8; // window border <-> widget border
#include "core/const.h"
#include "core/kernelAudio.h"
#include "core/types.h"
+#include "gui/drawing.h"
#include "utils/math.h"
#include <FL/fl_draw.H>
#include <algorithm>
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
+float geSoundMeter::Meter::compute(float peak)
+{
+ /* dBFS (full scale) calculation, plus decay of -2dB per call. */
+
+ float dbLevelCur = u::math::linearToDB(std::fabs(peak));
+
+ if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE)
+ dbLevelCur = m_dbLevelOld - 2.0f;
+
+ m_dbLevelOld = dbLevelCur;
+
+ return dbLevelCur;
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l)
: Fl_Box(x, y, w, h, l)
-, mixerPeak(0.0f)
-, m_dbLevelOld(0.0f)
{
}
void geSoundMeter::draw()
{
- fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);
-
- /* Compute peak level on 0.0 -> 1.0 scale. 1.0 is considered clip. */
+ const geompp::Rect outline(x(), y(), w(), h());
+ const geompp::Rect body(outline.reduced(1));
- const bool clip = std::fabs(mixerPeak) >= 1.0f ? true : false;
+ drawRect(outline, G_COLOR_GREY_4);
- /* dBFS (full scale) calculation, plus decay of -2dB per frame. */
+ if (!ready)
+ {
+ drawRectf(body, G_COLOR_BLUE);
+ return;
+ }
- float dbLevelCur = u::math::linearToDB(std::fabs(mixerPeak));
-
- if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE)
- dbLevelCur = m_dbLevelOld - 2.0f;
-
- m_dbLevelOld = dbLevelCur;
+ drawRectf(body, G_COLOR_GREY_2); // Cleanup
- /* Paint the meter on screen. */
+ const float dbL = m_left.compute(peak.left);
+ const float dbR = m_right.compute(peak.right);
+ const int colorL = std::fabs(peak.left) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4;
+ const int colorR = std::fabs(peak.right) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4;
- const int bodyCol = clip || !m::kernelAudio::isReady() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4;
+ const geompp::Rect bodyL(body.withTrimmedBottom(h() / 2));
+ const geompp::Rect bodyR(body.withTrimmedTop(h() / 2));
- fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2);
- fl_rectf(x() + 1, y() + 1, dbToPx_(dbLevelCur, w()), h() - 2, bodyCol);
+ drawRectf(bodyL.withW(dbToPx_(dbL, w() - 2)), colorL);
+ drawRectf(bodyR.withW(dbToPx_(dbR, w() - 2)), colorR);
}
} // namespace giada::v
\ No newline at end of file
#ifndef GE_SOUND_METER_H
#define GE_SOUND_METER_H
+#include "core/types.h"
#include <FL/Fl_Box.H>
namespace giada::v
void draw() override;
- float mixerPeak; // peak from mixer
+ Peak peak; // Peak from Mixer
+ bool ready; // Kernel state
private:
- float m_dbLevelOld;
+ class Meter
+ {
+ public:
+ float compute(float peak);
+
+ private:
+ float m_dbLevelOld = 0.0f;
+ };
+
+ Meter m_left;
+ Meter m_right;
};
} // namespace giada::v
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#include "model.h"
-#include "core/patch.h"
-#include "gui/dialogs/mainWindow.h"
-#include "gui/elems/mainWindow/keyboard/channel.h"
-#include "gui/elems/mainWindow/keyboard/column.h"
-#include "gui/elems/mainWindow/keyboard/keyboard.h"
-#include "utils/log.h"
-
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada::v::model
-{
-void store(m::patch::Patch& patch)
-{
- G_MainWin->keyboard->forEachColumn([&](const geColumn& c) {
- patch.columns.push_back({c.id, c.w()});
- });
-}
-
-/* -------------------------------------------------------------------------- */
-
-void load(const m::patch::Patch& patch)
-{
- G_MainWin->keyboard->layout.clear();
- for (const m::patch::Column& col : patch.columns)
- G_MainWin->keyboard->layout.push_back({col.id, col.width});
-}
-} // namespace giada::v::model
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-#ifndef G_V_MODEL_H
-#define G_V_MODEL_H
-
-namespace giada::m::patch
-{
-struct Patch;
-}
-namespace giada::v::model
-{
-void store(m::patch::Patch& patch);
-void load(const m::patch::Patch& patch);
-} // namespace giada::v::model
-
-#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_V_TYPES_H
+#define G_V_TYPES_H
+
+namespace giada::v
+{
+enum class Direction
+{
+ HORIZONTAL,
+ VERTICAL
+};
+} // namespace giada::v
+
+#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#include "gui/ui.h"
+#include "core/const.h"
+#include "core/engine.h"
+#include "core/recorder.h"
+#include "gui/elems/mainWindow/keyboard/column.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/mainIO.h"
+#include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/updater.h"
+#include "utils/gui.h"
+#include "utils/log.h"
+#ifdef WITH_VST
+#include <FL/Fl.H>
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+#include <X11/Xlib.h> // For XInitThreads
+#endif
+#endif
+
+namespace giada::v
+{
+Ui::Ui(m::Recorder& recorder)
+: m_updater(*this)
+, m_blinker(0)
+{
+ dispatcher.onEventOccured = [&recorder]() {
+ recorder.startActionRecOnCallback();
+ };
+}
+
+/* -------------------------------------------------------------------------- */
+
+bool Ui::shouldBlink() const
+{
+ return m_blinker > 6; // TODO magic numbers
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::load(const m::Patch::Data& patch)
+{
+ reset();
+ mainWindow->keyboard->layout.clear();
+ for (const m::Patch::Column& col : patch.columns)
+ mainWindow->keyboard->layout.push_back({col.id, col.width});
+ mainWindow->keyboard->rebuild();
+ setMainWindowTitle(patch.name);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::store(m::Patch::Data& patch)
+{
+ patch.columns.clear();
+ mainWindow->keyboard->forEachColumn([&](const geColumn& c) {
+ patch.columns.push_back({c.id, c.w()});
+ });
+ setMainWindowTitle(patch.name);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::init(int argc, char** argv, m::Engine& engine)
+{
+ /* This is of paramount importance on Linux with VST enabled, otherwise many
+ plug-ins go nuts and crash hard. It seems that some plug-ins on our Juce-based
+ PluginHost use Xlib concurrently. */
+
+#if (defined(G_OS_LINUX) || defined(G_OS_FREEBSD)) && defined(WITH_VST)
+ XInitThreads();
+#endif
+
+ mainWindow = std::make_unique<gdMainWindow>(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT, "", argc, argv, engine.conf.data);
+ mainWindow->resize(engine.conf.data.mainWindowX, engine.conf.data.mainWindowY, engine.conf.data.mainWindowW,
+ engine.conf.data.mainWindowH);
+
+ setMainWindowTitle(engine.patch.data.name == "" ? G_DEFAULT_PATCH_NAME : engine.patch.data.name);
+
+ m_updater.init(engine.model);
+
+ if (engine.kernelAudio.isReady())
+ rebuildStaticWidgets();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::reset()
+{
+ setMainWindowTitle(G_DEFAULT_PATCH_NAME);
+ rebuildStaticWidgets();
+ closeAllSubwindows();
+ mainWindow->clearKeyboard();
+ mainWindow->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::shutdown()
+{
+ mainWindow.reset();
+ m_updater.close();
+
+ u::log::print("[ui] All windows closed\n");
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::refresh()
+{
+ /* Update dynamic elements inside main window: in and out meters, beat meter
+ and each channel. */
+
+ mainWindow->refresh();
+
+ /* Compute timer for blinker. */
+
+ m_blinker = (m_blinker + 1) % 12; // TODO magic numbers
+
+ /* Refresh Sample Editor and Action Editor for dynamic playhead. */
+
+ refreshSubWindow(WID_SAMPLE_EDITOR);
+ refreshSubWindow(WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuild()
+{
+ mainWindow->rebuild();
+ rebuildSubWindow(WID_FX_LIST);
+ rebuildSubWindow(WID_SAMPLE_EDITOR);
+ rebuildSubWindow(WID_ACTION_EDITOR);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuildSubWindow(int wid)
+{
+ v::gdWindow* w = getSubwindow(*mainWindow.get(), wid);
+ if (w != nullptr) // If its open
+ w->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::refreshSubWindow(int wid)
+{
+ v::gdWindow* w = getSubwindow(*mainWindow.get(), wid);
+ if (w != nullptr) // If its open
+ w->refresh();
+}
+
+/* -------------------------------------------------------------------------- */
+
+v::gdWindow* Ui::getSubwindow(v::gdWindow& parent, int wid)
+{
+ return parent.hasWindow(wid) ? parent.getChild(wid) : nullptr;
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::openSubWindow(v::gdWindow& parent, v::gdWindow* child, int wid)
+{
+ if (parent.hasWindow(wid))
+ {
+ u::log::print("[GU] parent has subwindow with id=%d, deleting\n", wid);
+ parent.delSubWindow(wid);
+ }
+ child->setId(wid);
+ parent.addSubWindow(child);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::closeSubWindow(int wid)
+{
+ mainWindow->delSubWindow(wid);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::closeAllSubwindows()
+{
+ mainWindow->delSubWindow(WID_ACTION_EDITOR);
+ mainWindow->delSubWindow(WID_SAMPLE_EDITOR);
+ mainWindow->delSubWindow(WID_FX_LIST);
+ mainWindow->delSubWindow(WID_FX);
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::setMainWindowTitle(const std::string& s)
+{
+ std::string out = std::string(G_APP_NAME) + " - " + s;
+ mainWindow->copy_label(out.c_str());
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Ui::startJuceDispatchLoop()
+{
+ Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop);
+}
+
+void Ui::stopJuceDispatchLoop()
+{
+ Fl::remove_timeout(juceDispatchLoop);
+}
+
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+void Ui::rebuildStaticWidgets()
+{
+ mainWindow->mainIO->rebuild();
+ mainWindow->mainTimer->rebuild();
+}
+
+/* -------------------------------------------------------------------------- */
+
+#ifdef WITH_VST
+
+void Ui::juceDispatchLoop(void*)
+{
+ juce::MessageManager* mm = juce::MessageManager::getInstanceWithoutCreating();
+ assert(mm != nullptr);
+ mm->runDispatchLoopUntil(1);
+ Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop);
+}
+
+#endif
+} // namespace giada::v
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+#ifndef G_V_UI_H
+#define G_V_UI_H
+
+#include "core/patch.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dispatcher.h"
+#include "gui/updater.h"
+#include <memory>
+#include <string>
+
+namespace giada::m
+{
+class Recorder;
+class Engine;
+} // namespace giada::m
+
+namespace giada::v
+{
+class Ui final
+{
+public:
+ Ui(m::Recorder&);
+
+ /* shouldBlink
+ Return whether is time to blink something or not. This is used to make
+ widgets blink. */
+
+ bool shouldBlink() const;
+
+ /* load
+ Reads UI information from a Patch when a new project has been loaded. */
+
+ void load(const m::Patch::Data&);
+
+ /* store
+ Writes UI information to a patch when a project needs to be saved. */
+
+ void store(m::Patch::Data& patch);
+
+ void init(int argc, char** argv, m::Engine&);
+ void reset();
+ void shutdown();
+
+ /* refresh
+ Repaints dynamic GUI elements. */
+
+ void refresh();
+
+ /* rebuild
+ Rebuilds the UI from scratch. Used when the model has changed. */
+
+ void rebuild();
+
+ /* [rebuild|refresh]SubWindow
+ Rebuilds or refreshes subwindow with ID 'wid' if it exists, i.e. if it's open. */
+
+ void rebuildSubWindow(int wid);
+ void refreshSubWindow(int wid);
+
+ /* getSubwindow
+ Returns a pointer to an open subwindow, otherwise nullptr. */
+
+ v::gdWindow* getSubwindow(v::gdWindow& parent, int wid);
+
+ /* openSubWindow
+ Opens a new sub-window as a child of parent and assigns 'wid' to child. */
+
+ void openSubWindow(v::gdWindow& parent, v::gdWindow* child, int wid);
+
+ /* closeSubWindow
+ Closes a sun-window currently attached to the main one. */
+
+ void closeSubWindow(int wid);
+
+ /* closeAllSubwindows
+ Closes all subwindows attached to the main one. */
+
+ void closeAllSubwindows();
+
+ /* setMainWindowTitle
+ Updates the title of the main window, usually visible on top of it. */
+
+ void setMainWindowTitle(const std::string&);
+
+#ifdef WITH_VST
+
+ /* [start|stop]JuceDispatchLoop
+ Starts and stops the JUCE dispatch loop from its MessageManager component.
+ This is needed for plugin-ins to wake up their UI editor and let it react
+ to UI events. */
+
+ void startJuceDispatchLoop();
+ void stopJuceDispatchLoop();
+
+#endif
+
+ std::unique_ptr<gdMainWindow> mainWindow;
+ Dispatcher dispatcher;
+
+private:
+#ifdef WITH_VST
+ static void juceDispatchLoop(void*);
+#endif
+
+ /* rebuildStaticWidgets
+ Updates attributes of static widgets, i.e. those elements that don't get
+ automatically refreshed during the UI update loop. Useful when loading a new
+ patch. */
+
+ void rebuildStaticWidgets();
+
+ Updater m_updater;
+ int m_blinker;
+};
+} // namespace giada::v
+
+#endif
\ No newline at end of file
*
* -------------------------------------------------------------------------- */
-#include "updater.h"
+#include "gui/updater.h"
#include "core/const.h"
#include "core/model/model.h"
-#include "utils/gui.h"
+#include "gui/ui.h"
#include <FL/Fl.H>
-namespace giada::v::updater
+namespace giada::v
{
-void init()
+Updater::Updater(Ui& ui)
+: m_ui(ui)
{
- m::model::onSwap([](m::model::SwapType type) {
+}
+
+/* -------------------------------------------------------------------------- */
+
+void Updater::init(m::model::Model& model)
+{
+ model.onSwap = [this](m::model::SwapType type) {
if (type == m::model::SwapType::NONE)
return;
synchronization with the main one. */
Fl::lock();
- type == m::model::SwapType::HARD ? u::gui::rebuild() : u::gui::refresh();
+ type == m::model::SwapType::HARD ? m_ui.rebuild() : m_ui.refresh();
Fl::unlock();
- });
+ };
- Fl::add_timeout(G_GUI_REFRESH_RATE, update, nullptr);
+ Fl::add_timeout(G_GUI_REFRESH_RATE, update, this);
}
/* -------------------------------------------------------------------------- */
-void update(void* /*p*/)
+void Updater::update(void* p) { static_cast<Updater*>(p)->update(); }
+
+/* -------------------------------------------------------------------------- */
+
+void Updater::update()
{
- u::gui::refresh();
- Fl::add_timeout(G_GUI_REFRESH_RATE, update, nullptr);
+ m_ui.refresh();
+ Fl::add_timeout(G_GUI_REFRESH_RATE, update, this);
}
/* -------------------------------------------------------------------------- */
-void close()
+void Updater::close()
{
Fl::remove_timeout(update);
}
-} // namespace giada::v::updater
+} // namespace giada::v
#ifndef G_V_UPDATER_H
#define G_V_UPDATER_H
-namespace giada::v::updater
+namespace giada::m::model
{
-void init();
-void update(void* p);
-void close();
-} // namespace giada::v::updater
+class Model;
+}
+
+namespace giada::v
+{
+class Ui;
+class Updater final
+{
+public:
+ Updater(Ui& ui);
+
+ void init(m::model::Model&);
+ void close();
+
+private:
+ static void update(void*);
+ void update();
+
+ Ui& m_ui;
+};
+} // namespace giada::v
#endif
\ No newline at end of file
*
* -------------------------------------------------------------------------- */
-#include "core/init.h"
-#include "gui/dialogs/mainWindow.h"
-#include <FL/Fl.H>
-#ifdef WITH_TESTS
-#define CATCH_CONFIG_RUNNER
-#include "tests/audioBuffer.cpp"
-#include "tests/recorder.cpp"
-#include "tests/utils.cpp"
-#include "tests/wave.cpp"
-#include "tests/waveFx.cpp"
-#include "tests/waveManager.cpp"
-#include <catch2/catch.hpp>
-#include <string>
-#include <vector>
-#endif
+#include "core/engine.h"
+#include "gui/ui.h"
-class giada::v::gdMainWindow* G_MainWin = nullptr;
+giada::m::Engine g_engine;
+giada::v::Ui g_ui(g_engine.recorder);
int main(int argc, char** argv)
{
-#ifdef WITH_TESTS
- std::vector<char*> args(argv, argv + argc);
- if (args.size() > 1 && strcmp(args[1], "--run-tests") == 0)
- return Catch::Session().run(args.size() - 1, &args[1]);
-#endif
-
+ if (int ret = giada::m::init::tests(argc, argv); ret != -1)
+ return ret;
giada::m::init::startup(argc, argv);
-
- Fl::lock(); // Enable multithreading in FLTK
- int ret = Fl::run();
-
- giada::m::init::shutdown();
-
- return ret;
+ return giada::m::init::run();
}
\ No newline at end of file
#elif defined(__linux__) || defined(__FreeBSD__)
#include <X11/xpm.h>
#endif
-#include "core/clock.h"
#include "core/conf.h"
#include "core/graphics.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/plugins/pluginHost.h"
+#include "core/sequencer.h"
#include "gui.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
-#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/sampleEditor.h"
#include "gui/dialogs/warnings.h"
#include "gui/dialogs/window.h"
-#include "gui/elems/mainWindow/sequencer.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
#include "gui/elems/mainWindow/mainIO.h"
#include "gui/elems/mainWindow/mainTimer.h"
#include "gui/elems/mainWindow/mainTransport.h"
+#include "gui/elems/mainWindow/sequencer.h"
#include "gui/elems/sampleEditor/waveTools.h"
#include "log.h"
#include "string.h"
-extern giada::v::gdMainWindow* G_MainWin;
-
-namespace giada
-{
-namespace u
-{
-namespace gui
-{
-namespace
-{
-int blinker_ = 0;
-} // namespace
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-void rebuildSubWindow(int wid)
-{
- v::gdWindow* w = getSubwindow(G_MainWin, wid);
- if (w != nullptr) // If its open
- w->rebuild();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refreshSubWindow(int wid)
-{
- v::gdWindow* w = getSubwindow(G_MainWin, wid);
- if (w != nullptr) // If its open
- w->refresh();
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refresh()
-{
- /* Update dynamic elements inside main window: in and out meters, beat meter
- and each channel. */
-
- G_MainWin->refresh();
-
- /* Compute timer for blinker. */
-
- blinker_ = (blinker_ + 1) % 12;
-
- /* Refresh Sample Editor (if open) for dynamic play head. */
-
- refreshSubWindow(WID_SAMPLE_EDITOR);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void rebuild()
-{
- G_MainWin->rebuild();
- rebuildSubWindow(WID_FX_LIST);
- rebuildSubWindow(WID_SAMPLE_EDITOR);
- rebuildSubWindow(WID_ACTION_EDITOR);
-}
-
-/* -------------------------------------------------------------------------- */
-
-bool shouldBlink()
-{
- return blinker_ > 6;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateStaticWidgets()
-{
- using namespace giada::m;
-
- G_MainWin->mainIO->setOutVol(mh::getOutVol());
- G_MainWin->mainIO->setInVol(mh::getInVol());
-
-#ifdef WITH_VST
-
- // G_MainWin->mainIO->setMasterFxOutFull(pluginHost::getStack(pluginHost::StackType::MASTER_OUT).plugins.size() > 0);
- // G_MainWin->mainIO->setMasterFxInFull(pluginHost::getStack(pluginHost::StackType::MASTER_IN).plugins.size() > 0);
-
-#endif
-
- G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars());
- G_MainWin->mainTimer->setBpm(clock::getBpm());
- G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue());
-}
-
-/* -------------------------------------------------------------------------- */
-
-void updateMainWinLabel(const std::string& s)
+namespace giada::u::gui
{
- std::string out = std::string(G_APP_NAME) + " - " + s;
- G_MainWin->copy_label(out.c_str());
-}
-
-/* -------------------------------------------------------------------------- */
-
void setFavicon(v::gdWindow* w)
{
#if defined(__linux__) || defined(__FreeBSD__)
/* -------------------------------------------------------------------------- */
-void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id)
-{
- if (parent->hasWindow(id))
- {
- u::log::print("[GU] parent has subwindow with id=%d, deleting\n", id);
- parent->delSubWindow(id);
- }
- child->setId(id);
- parent->addSubWindow(child);
-}
-
-/* -------------------------------------------------------------------------- */
-
-void refreshActionEditor()
-{
- v::gdBaseActionEditor* ae = static_cast<v::gdBaseActionEditor*>(G_MainWin->getChild(WID_ACTION_EDITOR));
- if (ae != nullptr)
- ae->rebuild();
-}
-
-/* -------------------------------------------------------------------------- */
-
-v::gdWindow* getSubwindow(v::gdWindow* parent, int id)
-{
- if (parent->hasWindow(id))
- return parent->getChild(id);
- else
- return nullptr;
-}
-
-/* -------------------------------------------------------------------------- */
-
-void closeAllSubwindows()
-{
- /* don't close WID_FILE_BROWSER, because it's the caller of this
- * function */
-
- G_MainWin->delSubWindow(WID_ACTION_EDITOR);
- G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
- G_MainWin->delSubWindow(WID_FX_LIST);
- G_MainWin->delSubWindow(WID_FX);
-}
-
-/* -------------------------------------------------------------------------- */
-
int getStringWidth(const std::string& s)
{
int w = 0;
{
return (Fl::h() / 2) - (h / 2);
}
-} // namespace gui
-} // namespace u
-} // namespace giada
+} // namespace giada::u::gui
#define G_UTILS_GUI_H
#include "core/types.h"
+#include <FL/Fl_Menu_Item.H>
#include <string>
-namespace giada
-{
-namespace v
+namespace giada::v
{
class gdWindow;
}
-namespace u
-{
-namespace gui
-{
-/* refresh
-Repaints some dynamic GUI elements. */
-
-void refresh();
-
-/* rebuild
-Rebuilds the UI from scratch. Used when the model has changed. */
-
-void rebuild();
-
-/* [rebuild|refresh]SubWindow
-Rebuilds or refreshes subwindow with ID 'wid' if it exists. i.e. if its open. */
-
-void rebuildSubWindow(int wid);
-void refreshSubWindow(int wid);
-
-/* shouldBlink
-Return whether is time to blink something or not. This is used to make widgets
-blink. */
-
-bool shouldBlink();
-
-/* updateStaticWidgets
-Updates attributes of static widgets, i.e. those elements that don't get
-automatically refreshed during the UI update loop. Useful when loading a new
-patch. */
-
-void updateStaticWidgets();
-
-/* updateMainWinLabel
-Updates the name of the main window */
-
-void updateMainWinLabel(const std::string& s);
+namespace giada::u::gui
+{
void setFavicon(v::gdWindow* w);
-void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id);
-
-// TODO closeSubWindow(...)
-
-/* refreshActionEditor
-Reloads the action editor window by closing and reopening it. It's used when you
-delete some actions from the mainWindow and the action editor window is open. */
-
-void refreshActionEditor();
-
-/* closeAllSubwindows
-Closes all subwindows attached to mainWin. */
-
-void closeAllSubwindows();
-
-/* getSubwindow
-Returns a pointer to an open subwindow, otherwise nullptr. */
-
-v::gdWindow* getSubwindow(v::gdWindow* parent, int id);
-
/* removeFltkChars
Strips special chars used by FLTK to split menus into sub-menus. */
std::string removeFltkChars(const std::string& s);
+/* getStringWidth
+Returns the width in pixels of a string 's'. */
+
int getStringWidth(const std::string& s);
/* truncate
int centerWindowX(int w);
int centerWindowY(int h);
-} // namespace gui
-} // namespace u
-} // namespace giada
+/* makeMenuItem
+Makes a new Fl_Menu_Item at compile time. Used to initialize pop-up menus. */
+
+constexpr Fl_Menu_Item makeMenuItem(const char* text, Fl_Callback* callback = nullptr,
+ void* data = nullptr, int flags = 0)
+{
+ return Fl_Menu_Item{text, 0, callback, data, flags, 0, 0, 0, 0};
+}
+} // namespace giada::u::gui
#endif
#ifndef G_UTILS_MATH_H
#define G_UTILS_MATH_H
+#include <cassert>
#include <type_traits>
namespace giada::u::math
static_assert(std::is_arithmetic_v<TI>);
static_assert(std::is_arithmetic_v<TO>);
+ if (a == b) // Prevents division by zero (undefined behavior)
+ return x;
return (((x - a) / (double)(b - a)) * (z - w)) + w;
}
{
return {i.begin(), i.end()};
}
+
+/* -------------------------------------------------------------------------- */
+
+template <typename Vector, typename Default>
+auto atOr(const Vector& v, int index, Default d)
+{
+ return index >= 0 && static_cast<size_t>(index) < v.size() ? v[index] : d;
+}
} // namespace giada::u::vector
#endif
--- /dev/null
+#include "src/core/actions/actionRecorder.h"
+#include "src/core/actions/action.h"
+#include "src/core/actions/actions.h"
+#include "src/core/const.h"
+#include "src/core/model/model.h"
+#include "src/core/types.h"
+#include <catch2/catch.hpp>
+
+TEST_CASE("ActionRecorder")
+{
+ using namespace giada;
+ using namespace giada::m;
+
+ model::Model model;
+ ActionRecorder ar(model);
+
+ REQUIRE(ar.hasActions(/*ch=*/0) == false);
+
+ SECTION("Test record")
+ {
+ const int ch = 0;
+ const Frame f1 = 10;
+ const Frame f2 = 70;
+ const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
+ const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
+
+ const Action a1 = ar.rec(ch, f1, e1);
+ const Action a2 = ar.rec(ch, f2, e2);
+
+ REQUIRE(ar.hasActions(ch) == true);
+ REQUIRE(a1.frame == f1);
+ REQUIRE(a2.frame == f2);
+ REQUIRE(a1.prevId == 0);
+ REQUIRE(a1.nextId == 0);
+ REQUIRE(a2.prevId == 0);
+ REQUIRE(a2.nextId == 0);
+
+ SECTION("Test clear actions by channel")
+ {
+ const int ch = 1;
+ const Frame f1 = 100;
+ const Frame f2 = 200;
+ const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
+ const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
+
+ ar.rec(ch, f1, e1);
+ ar.rec(ch, f2, e2);
+
+ ar.clearChannel(/*channel=*/0);
+
+ REQUIRE(ar.hasActions(/*channel=*/0) == false);
+ REQUIRE(ar.hasActions(/*channel=*/1) == true);
+ }
+
+ SECTION("Test clear actions by type")
+ {
+ ar.clearActions(/*channel=*/0, MidiEvent::NOTE_ON);
+ ar.clearActions(/*channel=*/0, MidiEvent::NOTE_OFF);
+
+ REQUIRE(ar.hasActions(/*channel=*/0) == false);
+ }
+
+ SECTION("Test clear all")
+ {
+ ar.clearAllActions();
+ REQUIRE(ar.hasActions(/*channel=*/0) == false);
+ }
+ }
+}
+++ /dev/null
-#include "../src/core/audioBuffer.h"
-#include <catch2/catch.hpp>
-#include <memory>
-
-TEST_CASE("AudioBuffer")
-{
- using namespace giada::m;
-
- static const int BUFFER_SIZE = 4096;
-
- /* Each SECTION the TEST_CASE is executed from the start. Any code between
- this comment and the first SECTION macro is exectuted before each SECTION. */
-
- AudioBuffer buffer;
- buffer.alloc(BUFFER_SIZE, 2);
-
- SECTION("test allocation")
- {
- SECTION("test mono")
- {
- buffer.alloc(BUFFER_SIZE, 1);
- REQUIRE(buffer.countFrames() == BUFFER_SIZE);
- REQUIRE(buffer.countSamples() == BUFFER_SIZE);
- REQUIRE(buffer.countChannels() == 1);
- }
-
- SECTION("test stereo")
- {
- REQUIRE(buffer.countFrames() == BUFFER_SIZE);
- REQUIRE(buffer.countSamples() == BUFFER_SIZE * 2);
- REQUIRE(buffer.countChannels() == 2);
- }
-
- buffer.free();
-
- REQUIRE(buffer.countFrames() == 0);
- REQUIRE(buffer.countSamples() == 0);
- REQUIRE(buffer.countChannels() == 0);
- }
-
- SECTION("test clear all")
- {
- buffer.clear();
- for (int i = 0; i < buffer.countFrames(); i++)
- for (int k = 0; k < buffer.countChannels(); k++)
- REQUIRE(buffer[i][k] == 0.0f);
- buffer.free();
- }
-
- SECTION("test clear range")
- {
- for (int i = 0; i < buffer.countFrames(); i++)
- for (int k = 0; k < buffer.countChannels(); k++)
- buffer[i][k] = 1.0f;
-
- buffer.clear(5, 6);
-
- for (int k = 0; k < buffer.countChannels(); k++)
- REQUIRE(buffer[5][k] == 0.0f);
-
- buffer.free();
- }
-
- SECTION("test copy")
- {
- AudioBuffer other(BUFFER_SIZE, 2);
-
- for (int i = 0; i < other.countFrames(); i++)
- for (int k = 0; k < other.countChannels(); k++)
- other[i][k] = (float)i;
-
- SECTION("test full copy")
- {
- buffer.set(other, 1.0f);
-
- REQUIRE(buffer[0][0] == 0.0f);
- REQUIRE(buffer[16][0] == 16.0f);
- REQUIRE(buffer[128][0] == 128.0f);
- REQUIRE(buffer[1024][0] == 1024.0f);
- REQUIRE(buffer[BUFFER_SIZE - 1][0] == (float)BUFFER_SIZE - 1);
- }
- }
-}
#define CATCH_CONFIG_MAIN
#define CATCH_CONFIG_FAST_COMPILE
-#include <catch2/catch.hpp>
-
-/* There's no main.cpp in the test suite and the following global var is
-unfortunately defined there. Let's fake it. */
-
-class gdMainWindow* G_MainWin;
\ No newline at end of file
+#include <catch2/catch.hpp>
\ No newline at end of file
--- /dev/null
+#include "../src/core/channels/midiLighter.h"
+#include "mocks/kernelMidiMock.h"
+#include <catch2/catch.hpp>
+#include <memory>
+
+TEST_CASE("MidiMapper")
+{
+ using namespace giada;
+
+ m::KernelMidiMock kernelMidi;
+ m::MidiMapper<m::KernelMidiMock> midiMapper(kernelMidi);
+ m::MidiLighter midiLighter(midiMapper);
+
+ midiMapper.currentMap = {
+ "test-brand",
+ "test-device",
+ {{0, "0x000000", 0, 0x000000}}, // init commands
+ {0, "0x000001", 0, 0x000001}, // mute on
+ {0, "0x000002", 0, 0x000002}, // mute off
+ {0, "0x000003", 0, 0x000003}, // solo on
+ {0, "0x000004", 0, 0x000004}, // solo off
+ {0, "0x000005", 0, 0x000005}, // waiting
+ {0, "0x000006", 0, 0x000006}, // playing
+ {0, "0x000007", 0, 0x000007}, // stopping
+ {0, "0x000008", 0, 0x000008}, // stopped
+ {0, "0x000009", 0, 0x000009}, // playingInaudible
+ };
+
+ midiLighter.playing = {0x000010, 0};
+ midiLighter.mute = {0x000011, 0};
+ midiLighter.solo = {0x000012, 0};
+
+ SECTION("Test initialization")
+ {
+ REQUIRE(midiLighter.enabled == false);
+ }
+
+ SECTION("Test send OFF status")
+ {
+ midiLighter.sendStatus(ChannelStatus::OFF, /*audible=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000008); // Stopped
+ }
+
+ SECTION("Test send WAIT status")
+ {
+ midiLighter.sendStatus(ChannelStatus::WAIT, /*audible=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000005); // Waiting
+ }
+
+ SECTION("Test send ENDING status")
+ {
+ midiLighter.sendStatus(ChannelStatus::ENDING, /*audible=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000007); // Stopping
+ }
+
+ SECTION("Test send PLAY status")
+ {
+ midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000006); // Playing
+
+ midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/false);
+ REQUIRE(kernelMidi.sent.back() == 0x000009); // Playing inaudible
+ }
+
+ SECTION("Test send mute")
+ {
+ midiLighter.sendMute(/*isMuted=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000001); // Mute on
+
+ midiLighter.sendMute(/*isMuted=*/false);
+ REQUIRE(kernelMidi.sent.back() == 0x000002); // Mute off
+ }
+
+ SECTION("Test send solo")
+ {
+ midiLighter.sendSolo(/*isSoloed=*/true);
+ REQUIRE(kernelMidi.sent.back() == 0x000003); // Solo on
+
+ midiLighter.sendSolo(/*isSoloed=*/false);
+ REQUIRE(kernelMidi.sent.back() == 0x000004); // Solo off
+ }
+}
--- /dev/null
+#ifndef G_TESTS_KERNELMIDI_MOCK_H
+#define G_TESTS_KERNELMIDI_MOCK_H
+
+namespace giada::m
+{
+class KernelMidiMock
+{
+public:
+ void send(uint32_t s)
+ {
+ sent.push_back(s);
+ }
+
+ std::vector<uint32_t> sent;
+};
+} // namespace giada::m
+
+#endif
\ No newline at end of file
+++ /dev/null
-#include "../src/core/recorder.h"
-#include "../src/core/action.h"
-#include "../src/core/const.h"
-#include "../src/core/types.h"
-#include <catch2/catch.hpp>
-
-TEST_CASE("recorder")
-{
- using namespace giada;
- using namespace giada::m;
-
- recorder::init();
-
- REQUIRE(recorder::hasActions(/*ch=*/0) == false);
-
- SECTION("Test record")
- {
- const int ch = 0;
- const Frame f1 = 10;
- const Frame f2 = 70;
- const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
- const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
-
- const Action a1 = recorder::rec(ch, f1, e1);
- const Action a2 = recorder::rec(ch, f2, e2);
-
- REQUIRE(recorder::hasActions(ch) == true);
- REQUIRE(a1.frame == f1);
- REQUIRE(a2.frame == f2);
- REQUIRE(a1.prevId == 0);
- REQUIRE(a1.nextId == 0);
- REQUIRE(a2.prevId == 0);
- REQUIRE(a2.nextId == 0);
-
- SECTION("Test clear actions by channel")
- {
- const int ch = 1;
- const Frame f1 = 100;
- const Frame f2 = 200;
- const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00);
- const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00);
-
- recorder::rec(ch, f1, e1);
- recorder::rec(ch, f2, e2);
-
- recorder::clearChannel(/*channel=*/0);
-
- REQUIRE(recorder::hasActions(/*channel=*/0) == false);
- REQUIRE(recorder::hasActions(/*channel=*/1) == true);
- }
-
- SECTION("Test clear actions by type")
- {
- recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_ON);
- recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_OFF);
-
- REQUIRE(recorder::hasActions(/*channel=*/0) == false);
- }
-
- SECTION("Test clear all")
- {
- recorder::clearAll();
- REQUIRE(recorder::hasActions(/*channel=*/0) == false);
- }
- }
-}
TEST_CASE("waveManager")
{
/* Each SECTION the TEST_CASE is executed from the start. Any code between
- this comment and the first SECTION macro is exectuted before each SECTION. */
+ this comment and the first SECTION macro is executed before each SECTION. */
+
+ WaveManager waveManager;
SECTION("test creation")
{
- waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav",
+ WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav",
/*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR);
REQUIRE(res.status == G_RES_OK);
SECTION("test recording")
{
- std::unique_ptr<Wave> wave = waveManager::createEmpty(G_BUFFER_SIZE,
+ std::unique_ptr<Wave> wave = waveManager.createEmpty(G_BUFFER_SIZE,
G_MAX_IO_CHANS, G_SAMPLE_RATE, "test.wav");
REQUIRE(wave->getRate() == G_SAMPLE_RATE);
SECTION("test resampling")
{
- waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav",
+ WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav",
/*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR);
int oldSize = res.wave->getBuffer().countFrames();
- waveManager::resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2);
+ waveManager.resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2);
REQUIRE(res.wave->getRate() == G_SAMPLE_RATE * 2);
REQUIRE(res.wave->getBuffer().countFrames() == oldSize * 2);