From: IOhannes m zmölnig (Debian/GNU) Date: Thu, 16 Dec 2021 15:48:47 +0000 (+0100) Subject: New upstream version 0.19.1+ds1 X-Git-Tag: archive/raspbian/0.20.1+ds1-1+rpi1~1^2~12^2~3 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=7a8f08d9a43006cc9cddcb6b224bc7df24e044f8;p=giada.git New upstream version 0.19.1+ds1 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index c1571c5..0c4427e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,15 +33,16 @@ project(giada LANGUAGES CXX) list(APPEND SOURCES src/main.cpp + src/core/engine.cpp src/core/worker.cpp src/core/eventDispatcher.cpp src/core/midiDispatcher.cpp - src/core/midiMapConf.cpp + src/core/midiMapper.cpp src/core/midiEvent.cpp - src/core/audioBuffer.cpp src/core/quantizer.cpp src/core/conf.cpp src/core/kernelAudio.cpp + src/core/jackTransport.cpp src/core/mixerHandler.cpp src/core/sequencer.cpp src/core/metronome.cpp @@ -51,12 +52,12 @@ list(APPEND SOURCES src/core/kernelMidi.cpp src/core/graphics.cpp src/core/patch.cpp - src/core/recorderHandler.cpp - src/core/recorder.cpp + src/core/actions/actionRecorder.cpp + src/core/actions/actions.cpp src/core/mixer.cpp - src/core/clock.cpp + src/core/synchronizer.cpp src/core/waveManager.cpp - src/core/recManager.cpp + src/core/recorder.cpp src/core/midiLearnParam.cpp src/core/resampler.cpp src/core/plugins/pluginHost.cpp @@ -77,6 +78,9 @@ list(APPEND SOURCES src/core/channels/midiReceiver.cpp src/core/channels/channel.cpp src/core/channels/channelManager.cpp + src/core/model/sequencer.cpp + src/core/model/mixer.cpp + src/core/model/recorder.cpp src/core/model/model.cpp src/core/model/storage.cpp src/core/idManager.cpp @@ -90,10 +94,11 @@ list(APPEND SOURCES src/glue/sampleEditor.cpp src/glue/actionEditor.cpp src/glue/config.cpp + src/glue/layout.cpp + src/gui/ui.cpp src/gui/dialogs/window.cpp src/gui/dispatcher.cpp src/gui/updater.cpp - src/gui/model.cpp src/gui/drawing.cpp src/gui/dialogs/keyGrabber.cpp src/gui/dialogs/about.cpp @@ -142,11 +147,11 @@ list(APPEND SOURCES src/gui/elems/actionEditor/velocityEditor.cpp src/gui/elems/actionEditor/envelopePoint.cpp src/gui/elems/actionEditor/pianoRoll.cpp - src/gui/elems/actionEditor/noteEditor.cpp src/gui/elems/actionEditor/pianoItem.cpp src/gui/elems/actionEditor/sampleActionEditor.cpp src/gui/elems/actionEditor/sampleAction.cpp src/gui/elems/actionEditor/gridTool.cpp + src/gui/elems/actionEditor/splitScroll.cpp src/gui/elems/mainWindow/mainIO.cpp src/gui/elems/mainWindow/mainMenu.cpp src/gui/elems/mainWindow/mainTimer.cpp @@ -183,6 +188,7 @@ list(APPEND SOURCES src/gui/elems/basics/slider.cpp src/gui/elems/basics/progress.cpp src/gui/elems/basics/check.cpp + src/gui/elems/basics/split.cpp src/utils/log.cpp src/utils/time.cpp src/utils/math.cpp @@ -190,7 +196,8 @@ list(APPEND SOURCES src/utils/fs.cpp src/utils/ver.cpp src/utils/string.cpp - src/deps/rtaudio/RtAudio.cpp) + src/deps/rtaudio/RtAudio.cpp + src/deps/mcl-audio-buffer/src/audioBuffer.cpp) list(APPEND PREPROCESSOR_DEFS) list(APPEND INCLUDE_DIRS @@ -222,7 +229,7 @@ endif() # ------------------------------------------------------------------------------ if(DEFINED OS_WINDOWS) - list(APPEND COMPILER_OPTIONS /W4) + list(APPEND COMPILER_OPTIONS /W4 /bigobj /external:anglebrackets /external:W0) else() list(APPEND COMPILER_OPTIONS -Wall -Wextra -Wpedantic) endif() @@ -257,8 +264,9 @@ endif() # Threads (system) +set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -list(APPEND LIBRARIES ${Threads_LIBRARY}) +list(APPEND LIBRARIES Threads::Threads) # pkg-config/pkgconf, required to find some external dependencies on some # platforms @@ -287,7 +295,7 @@ if (NOT RtMidi_FOUND) message(FATAL_ERROR "Can't find RtMidi, aborting.") endif() - # RtMidi header path may vary accross OSes, so a fix is needed. + # RtMidi header path may vary across OSes, so a fix is needed. # TODO - Find a way to avoid this additional step find_path(LIBRARY_RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES rtmidi) @@ -506,6 +514,8 @@ if(WITH_VST2 OR WITH_VST3) list(APPEND PREPROCESSOR_DEFS WITH_VST + JUCE_DEBUG=$> + JUCE_MODAL_LOOPS_PERMITTED=1 JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 JUCE_MODULE_AVAILABLE_juce_gui_basics=1 JUCE_STANDALONE_APPLICATION=1 diff --git a/ChangeLog b/ChangeLog index 3665e3b..c745055 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,45 @@ -------------------------------------------------------------------------------- +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 @@ -144,7 +183,7 @@ 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 @@ -292,7 +331,7 @@ - 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 @@ -876,7 +915,7 @@ - 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 @@ -1308,7 +1347,7 @@ - (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 diff --git a/README.md b/README.md index e0859b0..c63ef21 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Giada is an open source, minimalistic and hardcore music production tool. Design ## License Giada is available under the terms of the GNU General Public License. -Take a look at the COPYING file for further informations. +Take a look at the COPYING file for further information. ## Documentation diff --git a/extras/com.giadamusic.Giada.desktop b/extras/com.giadamusic.Giada.desktop index 766bdfb..b9d33b5 100644 --- a/extras/com.giadamusic.Giada.desktop +++ b/extras/com.giadamusic.Giada.desktop @@ -7,6 +7,6 @@ GenericName=Drum machine and loop sequencer GenericName[es]=Caja de ritmos y secuenciador de loops Exec=giada %f Terminal=false -Icon=giada-logo +Icon=com.giadamusic.Giada Categories=Music;AudioVideo;Audio;Midi;X-Digital_Processing;X-Jack;X-MIDI; -Keywords=Giada; \ No newline at end of file +Keywords=Giada; diff --git a/src/core/action.h b/src/core/action.h deleted file mode 100644 index 39384f8..0000000 --- a/src/core/action.h +++ /dev/null @@ -1,61 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_ACTION_H -#define G_ACTION_H - -#include "midiEvent.h" -#include "types.h" - -namespace giada::m -{ -struct Action -{ - ID id = 0; // Invalid - ID channelId; - Frame frame; - MidiEvent event; - ID pluginId = -1; - int pluginParam = -1; - ID prevId = 0; - ID nextId = 0; - - const Action* prev = nullptr; - const Action* next = nullptr; - - bool isValid() const - { - return id != 0; - } - - bool isVolumeEnvelope() const - { - return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1; - } -}; -} // namespace giada::m - -#endif \ No newline at end of file diff --git a/src/core/actions/action.h b/src/core/actions/action.h new file mode 100644 index 0000000..5c3d2e6 --- /dev/null +++ b/src/core/actions/action.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_ACTION_H +#define G_ACTION_H + +#include "src/core/midiEvent.h" +#include "src/core/types.h" + +namespace giada::m +{ +struct Action +{ + ID id = 0; // Invalid + ID channelId; + Frame frame; + MidiEvent event; + ID pluginId = -1; + int pluginParam = -1; + ID prevId = 0; + ID nextId = 0; + + const Action* prev = nullptr; + const Action* next = nullptr; + + bool isValid() const + { + return id != 0; + } + + bool isVolumeEnvelope() const + { + return event.getStatus() == MidiEvent::ENVELOPE && pluginId == -1; + } +}; +} // namespace giada::m + +#endif \ No newline at end of file diff --git a/src/core/actions/actionRecorder.cpp b/src/core/actions/actionRecorder.cpp new file mode 100644 index 0000000..38f7b3f --- /dev/null +++ b/src/core/actions/actionRecorder.cpp @@ -0,0 +1,354 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 +#include +#include +#include + +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(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 actions; + std::unordered_map 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 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 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& 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(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 ActionRecorder::serializeActions(const Actions::Map& actions) +{ + std::vector 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(a1).nextId = a2.id; + const_cast(a2).prevId = a1.id; + + break; + } +} + +/* -------------------------------------------------------------------------- */ + +const std::vector* 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 ActionRecorder::getActionsOnChannel(ID channelId) const +{ + return m_actions.getActionsOnChannel(channelId); +} + +void ActionRecorder::clearChannel(ID channelId) +{ + m_actions.clearChannel(channelId); +} + +void ActionRecorder::clearActions(ID channelId, int type) +{ + m_actions.clearActions(channelId, type); +} + +Action ActionRecorder::rec(ID channelId, Frame frame, MidiEvent e) +{ + return m_actions.rec(channelId, frame, e); +} + +void ActionRecorder::rec(ID channelId, Frame f1, Frame f2, MidiEvent e1, MidiEvent e2) +{ + return m_actions.rec(channelId, f1, f2, e1, e2); +} + +void ActionRecorder::updateSiblings(ID id, ID prevId, ID nextId) +{ + m_actions.updateSiblings(id, prevId, nextId); +} + +void ActionRecorder::deleteAction(ID id) +{ + m_actions.deleteAction(id); +} + +void ActionRecorder::deleteAction(ID currId, ID nextId) +{ + m_actions.deleteAction(currId, nextId); +} + +void ActionRecorder::updateEvent(ID id, MidiEvent e) +{ + m_actions.updateEvent(id, e); +} +} // namespace giada::m \ No newline at end of file diff --git a/src/core/actions/actionRecorder.h b/src/core/actions/actionRecorder.h new file mode 100644 index 0000000..c0ee704 --- /dev/null +++ b/src/core/actions/actionRecorder.h @@ -0,0 +1,130 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_ACTION_RECORDER_H +#define G_ACTION_RECORDER_H + +#include "core/actions/actions.h" +#include "core/midiEvent.h" +#include "core/types.h" +#include + +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 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& as); + std::vector serializeActions(const Actions::Map& as); + + /* Pass-thru functions. See Actions.h */ + + const std::vector* getActionsOnFrame(Frame f) const; + bool hasActions(ID channelId, int type = 0) const; + Action getClosestAction(ID channelId, Frame f, int type) const; + std::vector 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 m_liveActions; +}; +} // namespace giada::m + +#endif diff --git a/src/core/actions/actions.cpp b/src/core/actions/actions.cpp new file mode 100644 index 0000000..dc3bdb0 --- /dev/null +++ b/src/core/actions/actions.cpp @@ -0,0 +1,352 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "actions.h" +#include "action.h" +#include "core/idManager.h" +#include "core/model/model.h" +#include "utils/log.h" +#include +#include +#include + +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().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 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()) + { + 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() = std::move(temp); +} + +/* -------------------------------------------------------------------------- */ + +void Actions::updateEvent(ID id, MidiEvent e) +{ + model::DataLock lock = m_model.lockData(); + + findAction(m_model.getAllShared(), id)->event = e; +} + +/* -------------------------------------------------------------------------- */ + +void Actions::updateSiblings(ID id, ID prevId, ID nextId) +{ + model::DataLock lock = m_model.lockData(); + + Action* pcurr = findAction(m_model.getAllShared(), id); + Action* pprev = findAction(m_model.getAllShared(), prevId); + Action* pnext = findAction(m_model.getAllShared(), 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()) + 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()[frame].push_back(a); + updateMapPointers(m_model.getAllShared()); + + return a; +} + +/* -------------------------------------------------------------------------- */ + +void Actions::rec(std::vector& actions) +{ + if (actions.size() == 0) + return; + + model::DataLock lock = m_model.lockData(); + + Map& map = m_model.getAllShared(); + + 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[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* Actions::getActionsOnFrame(Frame frame) const +{ + if (m_model.getAllShared().count(frame) == 0) + return nullptr; + return &m_model.getAllShared().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 Actions::getActionsOnChannel(ID channelId) const +{ + std::vector out; + forEachAction([&](const Action& a) { + if (a.channelId == channelId) + out.push_back(a); + }); + return out; +} + +/* -------------------------------------------------------------------------- */ + +void Actions::forEachAction(std::function f) const +{ + for (auto& [_, actions] : m_model.getAllShared()) + 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 f) +{ + model::DataLock lock = m_model.lockData(); + + Map& map = m_model.getAllShared(); + 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()); +} +} // namespace giada::m diff --git a/src/core/actions/actions.h b/src/core/actions/actions.h new file mode 100644 index 0000000..12c224b --- /dev/null +++ b/src/core/actions/actions.h @@ -0,0 +1,185 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 +#include +#include +#include + +namespace giada::m::model +{ +class Model; +} + +namespace giada::m +{ +class Actions +{ +public: + using Map = std::map>; + + 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 f) const; + + /* getActionsOnChannel + Returns a vector of actions belonging to channel 'ch'. */ + + std::vector 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* 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 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& 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 f); + + model::Model& m_model; + + //TODO - move to actionManager + IdManager m_actionId; +}; +} // namespace giada::m + +#endif diff --git a/src/core/audioBuffer.cpp b/src/core/audioBuffer.cpp deleted file mode 100644 index c117c58..0000000 --- a/src/core/audioBuffer.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#include "audioBuffer.h" -#include -#include - -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(b, framesToCopy, srcOffset, destOffset, gain, pan); -} - -void AudioBuffer::set(const AudioBuffer& b, Frame framesToCopy, Frame srcOffset, - Frame destOffset, float gain, Pan pan) -{ - copyData(b, framesToCopy, srcOffset, destOffset, gain, pan); -} - -void AudioBuffer::sum(const AudioBuffer& b, float gain, Pan pan) -{ - copyData(b, -1, 0, 0, gain, pan); -} - -void AudioBuffer::set(const AudioBuffer& b, float gain, Pan pan) -{ - copyData(b, -1, 0, 0, gain, pan); -} - -/* -------------------------------------------------------------------------- */ - -template -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(const AudioBuffer&, Frame, Frame, Frame, float, Pan); -template void AudioBuffer::copyData(const AudioBuffer&, Frame, Frame, Frame, float, Pan); -} // namespace giada::m \ No newline at end of file diff --git a/src/core/audioBuffer.h b/src/core/audioBuffer.h deleted file mode 100644 index 3ff25ec..0000000 --- a/src/core/audioBuffer.h +++ /dev/null @@ -1,164 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_AUDIO_BUFFER_H -#define G_AUDIO_BUFFER_H - -#include "core/types.h" -#include - -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; - - /* 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; kcountFrames(), k++) - for (int i=0; icountChannels(); 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 - 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 diff --git a/src/core/channels/audioReceiver.cpp b/src/core/channels/audioReceiver.cpp index dc785f6..c0957a7 100644 --- a/src/core/channels/audioReceiver.cpp +++ b/src/core/channels/audioReceiver.cpp @@ -26,10 +26,11 @@ #include "audioReceiver.h" #include "core/channels/channel.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" -namespace giada::m::audioReceiver +namespace giada::m { -Data::Data(const patch::Channel& p) +AudioReceiver::AudioReceiver(const Patch::Channel& p) : inputMonitor(p.inputMonitor) , overdubProtection(p.overdubProtection) { @@ -37,14 +38,14 @@ Data::Data(const patch::Channel& p) /* -------------------------------------------------------------------------- */ -void render(const channel::Data& ch, const AudioBuffer& in) +void AudioReceiver::render(const Channel& ch, const mcl::AudioBuffer& in) const { /* If armed and input monitor is on, copy input buffer to channel buffer: this enables the input monitoring. The channel buffer will be overwritten later on by pluginHost::processStack, so that you would record "clean" audio (i.e. not plugin-processed). */ - if (ch.armed && ch.audioReceiver->inputMonitor) - ch.buffer->audio.set(in, /*gain=*/1.0f); // add, don't overwrite + if (ch.armed && inputMonitor) + ch.shared->audioBuffer.set(in, /*gain=*/1.0f); // add, don't overwrite } -} // namespace giada::m::audioReceiver \ No newline at end of file +} // namespace giada::m diff --git a/src/core/channels/audioReceiver.h b/src/core/channels/audioReceiver.h index f04a124..99e4e9d 100644 --- a/src/core/channels/audioReceiver.h +++ b/src/core/channels/audioReceiver.h @@ -27,31 +27,28 @@ #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 diff --git a/src/core/channels/channel.cpp b/src/core/channels/channel.cpp index 2d90efc..5aa00ab 100644 --- a/src/core/channels/channel.cpp +++ b/src/core/channels/channel.cpp @@ -24,17 +24,26 @@ * * -------------------------------------------------------------------------- */ -#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 -namespace giada::m::channel +extern giada::m::Engine g_engine; + +namespace giada::m { namespace { -AudioBuffer::Pan calcPanning_(float pan) +mcl::AudioBuffer::Pan calcPanning_(float pan) { /* TODO - precompute the AudioBuffer::Pan when pan value changes instead of building it on the fly. */ @@ -45,133 +54,54 @@ AudioBuffer::Pan calcPanning_(float pan) return {1.0f, 1.0f}; return {1.0f - pan, pan}; } - -/* -------------------------------------------------------------------------- */ - -void react_(Data& d, const eventDispatcher::Event& e) -{ - switch (e.type) - { - case eventDispatcher::EventType::CHANNEL_VOLUME: - d.volume = std::get(e.data); - break; - - case eventDispatcher::EventType::CHANNEL_PAN: - d.pan = std::get(e.data); - break; - - case eventDispatcher::EventType::CHANNEL_MUTE: - d.mute = !d.mute; - break; - - case eventDispatcher::EventType::CHANNEL_TOGGLE_ARM: - d.armed = !d.armed; - break; - - case eventDispatcher::EventType::CHANNEL_SOLO: - d.solo = !d.solo; - m::mh::updateSoloCount(); - break; - - default: - break; - } -} - -/* -------------------------------------------------------------------------- */ - -void renderMasterOut_(const Data& d, AudioBuffer& out) -{ - d.buffer->audio.set(out, /*gain=*/1.0f); -#ifdef WITH_VST - if (d.plugins.size() > 0) - pluginHost::processStack(d.buffer->audio, d.plugins, nullptr); -#endif - out.set(d.buffer->audio, d.volume); -} - -/* -------------------------------------------------------------------------- */ - -void renderMasterIn_(const Data& d, AudioBuffer& in) -{ -#ifdef WITH_VST - if (d.plugins.size() > 0) - pluginHost::processStack(in, d.plugins, nullptr); -#endif -} - -/* -------------------------------------------------------------------------- */ - -void renderChannel_(const Data& d, AudioBuffer& out, AudioBuffer& in, bool audible) -{ - d.buffer->audio.clear(); - - if (d.samplePlayer) - samplePlayer::render(d); - if (d.audioReceiver) - audioReceiver::render(d, in); - - /* If MidiReceiver exists, let it process the plug-in stack, as it can - contain plug-ins that take MIDI events (i.e. synths). Otherwise process the - plug-in stack internally with no MIDI events. */ - -#ifdef WITH_VST - if (d.midiReceiver) - midiReceiver::render(d); - else if (d.plugins.size() > 0) - pluginHost::processStack(d.buffer->audio, d.plugins, nullptr); -#endif - - if (audible) - out.sum(d.buffer->audio, d.volume * d.volume_i, calcPanning_(d.pan)); -} } // namespace /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Buffer::Buffer(Frame bufferSize) -: audio(bufferSize, G_MAX_IO_CHANS) +Channel::Shared::Shared(Frame bufferSize) +: audioBuffer(bufferSize, G_MAX_IO_CHANS) { } /* -------------------------------------------------------------------------- */ -Data::Data(ChannelType type, ID id, ID columnId, State& state, Buffer& buffer) -: state(&state) -, buffer(&buffer) +Channel::Channel(ChannelType type, ID id, ID columnId, Shared& s) +: shared(&s) , id(id) , type(type) , columnId(columnId) , volume(G_DEFAULT_VOL) , volume_i(G_DEFAULT_VOL) , pan(G_DEFAULT_PAN) -, mute(false) -, solo(false) , armed(false) , key(0) , hasActions(false) , height(G_GUI_UNIT) +, midiLighter(g_engine.midiMapper) +, m_mute(false) +, m_solo(false) { switch (type) { case ChannelType::SAMPLE: - samplePlayer.emplace(&state.resampler.value()); - sampleReactor.emplace(id); + samplePlayer.emplace(&(shared->resampler.value())); + sampleAdvancer.emplace(); + sampleReactor.emplace(id, g_engine.sequencer, g_engine.model); audioReceiver.emplace(); - sampleActionRecorder.emplace(); + sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer); break; case ChannelType::PREVIEW: - samplePlayer.emplace(&state.resampler.value()); - sampleReactor.emplace(id); + samplePlayer.emplace(&(shared->resampler.value())); + sampleReactor.emplace(id, g_engine.sequencer, g_engine.model); break; case ChannelType::MIDI: midiController.emplace(); - midiSender.emplace(); - midiActionRecorder.emplace(); + midiSender.emplace(g_engine.kernelMidi); + midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer); #ifdef WITH_VST midiReceiver.emplace(); #endif @@ -180,52 +110,55 @@ Data::Data(ChannelType type, ID id, ID columnId, State& state, Buffer& buffer) default: break; } + + initCallbacks(); } /* -------------------------------------------------------------------------- */ -Data::Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio) -: state(&state) -, buffer(&buffer) +Channel::Channel(const Patch::Channel& p, Shared& s, float samplerateRatio, Wave* wave) +: shared(&s) , id(p.id) , type(p.type) , columnId(p.columnId) , volume(p.volume) , volume_i(G_DEFAULT_VOL) , pan(p.pan) -, mute(p.mute) -, solo(p.solo) , armed(p.armed) , key(p.key) , hasActions(p.hasActions) , name(p.name) , height(p.height) #ifdef WITH_VST -, plugins(pluginManager::hydratePlugins(p.pluginIds)) +, plugins(g_engine.pluginManager.hydratePlugins(p.pluginIds, g_engine.model)) // TODO move outside, as constructor parameter #endif , midiLearner(p) +, midiLighter(g_engine.midiMapper, p) +, m_mute(p.mute) +, m_solo(p.solo) { - state.readActions.store(p.readActions); - state.recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF); + shared->readActions.store(p.readActions); + shared->recStatus.store(p.readActions ? ChannelStatus::PLAY : ChannelStatus::OFF); switch (type) { case ChannelType::SAMPLE: - samplePlayer.emplace(p, samplerateRatio, &state.resampler.value()); - sampleReactor.emplace(id); + samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), wave); + sampleAdvancer.emplace(); + sampleReactor.emplace(id, g_engine.sequencer, g_engine.model); audioReceiver.emplace(p); - sampleActionRecorder.emplace(); + sampleActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer); break; case ChannelType::PREVIEW: - samplePlayer.emplace(p, samplerateRatio, &state.resampler.value()); - sampleReactor.emplace(id); + samplePlayer.emplace(p, samplerateRatio, &(shared->resampler.value()), nullptr); + sampleReactor.emplace(id, g_engine.sequencer, g_engine.model); break; case ChannelType::MIDI: midiController.emplace(); - midiSender.emplace(p); - midiActionRecorder.emplace(); + midiSender.emplace(p, g_engine.kernelMidi); + midiActionRecorder.emplace(g_engine.actionRecorder, g_engine.sequencer); #ifdef WITH_VST midiReceiver.emplace(); #endif @@ -234,29 +167,88 @@ Data::Data(const patch::Channel& p, State& state, Buffer& buffer, float samplera default: break; } + + initCallbacks(); } /* -------------------------------------------------------------------------- */ -bool Data::operator==(const Data& other) +Channel::Channel(const Channel& other) +: midiLighter(g_engine.midiMapper) +{ + *this = other; +} + +/* -------------------------------------------------------------------------- */ + +Channel& Channel::operator=(const Channel& other) +{ + if (this == &other) + return *this; + + shared = other.shared; + id = other.id; + type = other.type; + columnId = other.columnId; + volume = other.volume; + volume_i = other.volume_i; + pan = other.pan; + m_mute = other.m_mute; + m_solo = other.m_solo; + armed = other.armed; + key = other.key; + hasActions = other.hasActions; + name = other.name; + height = other.height; +#ifdef WITH_VST + plugins = other.plugins; +#endif + + midiLearner = other.midiLearner; + midiLighter = other.midiLighter; + samplePlayer = other.samplePlayer; + sampleAdvancer = other.sampleAdvancer; + sampleReactor = other.sampleReactor; + audioReceiver = other.audioReceiver; + midiController = other.midiController; +#ifdef WITH_VST + midiReceiver = other.midiReceiver; +#endif + midiSender = other.midiSender; + sampleActionRecorder = other.sampleActionRecorder; + midiActionRecorder = other.midiActionRecorder; + + initCallbacks(); + + return *this; +} + +/* -------------------------------------------------------------------------- */ + +bool Channel::operator==(const Channel& other) { return id == other.id; } /* -------------------------------------------------------------------------- */ -bool Data::isInternal() const +bool Channel::isInternal() const { return type == ChannelType::MASTER || type == ChannelType::PREVIEW; } -bool Data::isMuted() const +bool Channel::isMuted() const { /* Internals can't be muted. */ - return !isInternal() && mute; + return !isInternal() && m_mute; } -bool Data::canInputRec() const +bool Channel::isSoloed() const +{ + return m_solo; +} + +bool Channel::canInputRec() const { if (type != ChannelType::SAMPLE) return false; @@ -268,87 +260,202 @@ bool Data::canInputRec() const return armed && canOverdub; } -bool Data::canActionRec() const +bool Channel::canActionRec() const { return hasWave() && !samplePlayer->isAnyLoopMode(); } -bool Data::hasWave() const +bool Channel::hasWave() const { return samplePlayer && samplePlayer->hasWave(); } -bool Data::isPlaying() const +bool Channel::isPlaying() const { - ChannelStatus s = state->playStatus.load(); + ChannelStatus s = shared->playStatus.load(); return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING; } -bool Data::isReadingActions() const +bool Channel::isReadingActions() const { - ChannelStatus s = state->recStatus.load(); + ChannelStatus s = shared->recStatus.load(); return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING; } /* -------------------------------------------------------------------------- */ -void advance(const Data& d, const sequencer::EventBuffer& events) +void Channel::setMute(bool v) +{ + if (m_mute != v) + midiLighter.sendMute(v); + m_mute = v; +} + +void Channel::setSolo(bool v) +{ + if (m_solo != v) + midiLighter.sendSolo(v); + m_solo = v; +} + +/* -------------------------------------------------------------------------- */ + +void Channel::initCallbacks() { - for (const sequencer::Event& e : events) + shared->playStatus.onChange = [this](ChannelStatus status) { + midiLighter.sendStatus(status, g_engine.mixer.isChannelAudible(*this)); + }; + + if (samplePlayer) { - if (d.midiController) - midiController::advance(d, e); - if (d.samplePlayer) - samplePlayer::advance(d, e); - if (d.midiSender) - midiSender::advance(d, e); + samplePlayer->onLastFrame = [this]() { + sampleAdvancer->onLastFrame(*this, g_engine.sequencer.isRunning()); + }; + } +} + +/* -------------------------------------------------------------------------- */ + +void Channel::advance(const Sequencer::EventBuffer& events) const +{ + for (const Sequencer::Event& e : events) + { + if (midiController) + midiController->advance(*this, e); + if (samplePlayer) + sampleAdvancer->advance(*this, e); + if (midiSender) + midiSender->advance(*this, e); #ifdef WITH_VST - if (d.midiReceiver) - midiReceiver::advance(d, e); + if (midiReceiver) + midiReceiver->advance(*this, e); #endif } } /* -------------------------------------------------------------------------- */ -void react(Data& d, const eventDispatcher::EventBuffer& events, bool audible) +void Channel::react(const EventDispatcher::EventBuffer& events) { - for (const eventDispatcher::Event& e : events) + for (const EventDispatcher::Event& e : events) { - if (e.channelId > 0 && e.channelId != d.id) + if (e.channelId > 0 && e.channelId != id) continue; - react_(d, e); - midiLighter::react(d, e, audible); - - if (d.midiController) - midiController::react(d, e); - if (d.midiSender) - midiSender::react(d, e); - if (d.samplePlayer) - samplePlayer::react(d, e); - if (d.midiActionRecorder) - midiActionRecorder::react(d, e); - if (d.sampleActionRecorder) - sampleActionRecorder::react(d, e); - if (d.sampleReactor) - sampleReactor::react(d, e); + react(e); + if (midiController) + midiController->react(*this, e); + if (midiSender) + midiSender->react(*this, e); + if (samplePlayer) + samplePlayer->react(e); + if (midiActionRecorder) + midiActionRecorder->react(*this, e, g_engine.recorder.canRecordActions()); + if (sampleActionRecorder) + sampleActionRecorder->react(*this, e, g_engine.conf.data.treatRecsAsLoops, + g_engine.sequencer.isRunning(), g_engine.recorder.canRecordActions()); + if (sampleReactor) + sampleReactor->react(*this, e, g_engine.sequencer, g_engine.conf.data); #ifdef WITH_VST - if (d.midiReceiver) - midiReceiver::react(d, e); + if (midiReceiver) + midiReceiver->react(*this, e); #endif } } /* -------------------------------------------------------------------------- */ -void render(const Data& d, AudioBuffer* out, AudioBuffer* in, bool audible) +void Channel::react(const EventDispatcher::Event& e) +{ + switch (e.type) + { + case EventDispatcher::EventType::CHANNEL_VOLUME: + volume = std::get(e.data); + break; + + case EventDispatcher::EventType::CHANNEL_PAN: + pan = std::get(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 diff --git a/src/core/channels/channel.h b/src/core/channels/channel.h index 1273e00..1d1d81c 100644 --- a/src/core/channels/channel.h +++ b/src/core/channels/channel.h @@ -31,7 +31,6 @@ #ifdef WITH_VST #include "deps/juce-config.h" #endif -#include "core/audioBuffer.h" #include "core/channels/audioReceiver.h" #include "core/channels/midiActionRecorder.h" #include "core/channels/midiController.h" @@ -39,12 +38,18 @@ #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 @@ -52,65 +57,79 @@ namespace giada::m { class Plugin; -} -namespace giada::m::channel +class Channel final { -struct State -{ - WeakAtomic tracker = 0; - WeakAtomic playStatus = ChannelStatus::OFF; - WeakAtomic recStatus = ChannelStatus::OFF; - WeakAtomic 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 midiQueue; +#endif + + WeakAtomic tracker = 0; + WeakAtomic playStatus = ChannelStatus::OFF; + WeakAtomic recStatus = ChannelStatus::OFF; + WeakAtomic 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 = {}; -}; + std::optional resampler = {}; + }; -struct Buffer -{ - Buffer(Frame bufferSize); + Channel(ChannelType t, ID id, ID columnId, Shared&); + Channel(const Patch::Channel& p, Shared&, float samplerateRatio, Wave* w); + Channel(const Channel& o); + Channel(Channel&& o) = default; - AudioBuffer audio; -#ifdef WITH_VST - juce::MidiBuffer midi; -#endif -}; + Channel& operator=(const Channel&); + Channel& operator=(Channel&&) = default; + bool operator==(const Channel&); -struct Data -{ - Data(ChannelType t, ID id, ID columnId, State& state, Buffer& buffer); - Data(const patch::Channel& p, State& state, Buffer& buffer, float samplerateRatio); - Data(const Data& o) = default; - Data(Data&& o) = default; - Data& operator=(const Data&) = default; - Data& operator=(Data&&) = default; + /* advance + Advances internal state by processing static events (e.g. pre-recorded + actions or sequencer events) in the current block. */ + + void advance(const Sequencer::EventBuffer& e) const; + + /* render + Renders audio data to I/O buffers. */ - bool operator==(const Data&); + void render(mcl::AudioBuffer* out, mcl::AudioBuffer* in, bool audible) const; + + /* react + Reacts to live events coming from the EventDispatcher (human events) and + updates itself accordingly. */ + + void react(const EventDispatcher::EventBuffer& e); bool isPlaying() const; bool isReadingActions() const; bool isInternal() const; bool isMuted() const; + bool isSoloed() const; bool canInputRec() const; bool canActionRec() const; bool hasWave() const; - State* state; - Buffer* buffer; + void setMute(bool); + void setSolo(bool); + + Shared* shared; ID id; ChannelType type; ID columnId; float volume; float volume_i; // Internal volume used for velocity-drives-volume mode on Sample Channels float pan; - bool mute; - bool solo; bool armed; int key; bool hasActions; @@ -120,37 +139,32 @@ struct Data std::vector plugins; #endif - midiLearner::Data midiLearner; - midiLighter::Data midiLighter; + MidiLearner midiLearner; + MidiLighter midiLighter; - std::optional samplePlayer; - std::optional sampleReactor; - std::optional audioReceiver; - std::optional midiController; + std::optional samplePlayer; + std::optional sampleAdvancer; + std::optional sampleReactor; + std::optional audioReceiver; + std::optional midiController; #ifdef WITH_VST - std::optional midiReceiver; + std::optional midiReceiver; #endif - std::optional midiSender; - std::optional sampleActionRecorder; - std::optional 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; + std::optional sampleActionRecorder; + std::optional 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 diff --git a/src/core/channels/channelManager.cpp b/src/core/channels/channelManager.cpp index 489106f..da24d72 100644 --- a/src/core/channels/channelManager.cpp +++ b/src/core/channels/channelManager.cpp @@ -24,102 +24,78 @@ * * -------------------------------------------------------------------------- */ -#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 -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 state = std::make_unique(); - - if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW) - state->resampler = Resampler(static_cast(conf::conf.rsmpQuality), G_MAX_IO_CHANS); - - model::add(std::move(state)); - return model::back(); } /* -------------------------------------------------------------------------- */ -channel::Buffer& makeBuffer_() +ID ChannelManager::getNextId() const { - model::add(std::make_unique(kernelAudio::getRealBufSize())); - return model::back(); + 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(pch.waveId)); } /* -------------------------------------------------------------------------- */ -const patch::Channel serializeChannel(const channel::Data& c) +const Patch::Channel ChannelManager::serializeChannel(const Channel& c) { - patch::Channel pc; + Patch::Channel pc; #ifdef WITH_VST for (const Plugin* p : c.plugins) @@ -132,12 +108,12 @@ const patch::Channel serializeChannel(const channel::Data& c) pc.height = c.height; pc.name = c.name; pc.key = c.key; - pc.mute = c.mute; - pc.solo = c.solo; + pc.mute = c.isMuted(); + pc.solo = c.isSoloed(); pc.volume = c.volume; pc.pan = c.pan; pc.hasActions = c.hasActions; - pc.readActions = c.state->readActions.load(); + pc.readActions = c.shared->readActions.load(); pc.armed = c.armed; pc.midiIn = c.midiLearner.enabled; pc.midiInFilter = c.midiLearner.filter; @@ -175,4 +151,17 @@ const patch::Channel serializeChannel(const channel::Data& c) return pc; } -} // namespace giada::m::channelManager + +/* -------------------------------------------------------------------------- */ + +Channel::Shared& ChannelManager::makeShared(ChannelType type, int bufferSize) +{ + std::unique_ptr shared = std::make_unique(bufferSize); + + if (type == ChannelType::SAMPLE || type == ChannelType::PREVIEW) + shared->resampler = Resampler(static_cast(m_conf.rsmpQuality), G_MAX_IO_CHANS); + + m_model.addShared(std::move(shared)); + return m_model.backShared(); +} +} // namespace giada::m diff --git a/src/core/channels/channelManager.h b/src/core/channels/channelManager.h index fd8598a..0e32e04 100644 --- a/src/core/channels/channelManager.h +++ b/src/core/channels/channelManager.h @@ -27,43 +27,60 @@ #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 diff --git a/src/core/channels/midiActionRecorder.cpp b/src/core/channels/midiActionRecorder.cpp index a759159..61d2925 100644 --- a/src/core/channels/midiActionRecorder.cpp +++ b/src/core/channels/midiActionRecorder.cpp @@ -24,46 +24,32 @@ * * -------------------------------------------------------------------------- */ -#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 +#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(e.data).event); + if (e.type == EventDispatcher::EventType::MIDI && canRecordActions) + { + MidiEvent flat(std::get(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 diff --git a/src/core/channels/midiActionRecorder.h b/src/core/channels/midiActionRecorder.h index 3e08c9a..8abc229 100644 --- a/src/core/channels/midiActionRecorder.h +++ b/src/core/channels/midiActionRecorder.h @@ -27,21 +27,25 @@ #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 diff --git a/src/core/channels/midiController.cpp b/src/core/channels/midiController.cpp index 3403ead..ffc86a5 100644 --- a/src/core/channels/midiController.cpp +++ b/src/core/channels/midiController.cpp @@ -29,13 +29,42 @@ #include "core/conf.h" #include -namespace giada::m::midiController +namespace giada::m { -namespace +void MidiController::react(Channel& ch, const EventDispatcher::Event& e) const { -ChannelStatus onFirstBeat_(const channel::Data& ch) + switch (e.type) + { + case EventDispatcher::EventType::KEY_PRESS: + ch.shared->playStatus.store(press(ch)); + break; + + case EventDispatcher::EventType::KEY_KILL: + case EventDispatcher::EventType::SEQUENCER_STOP: + ch.shared->playStatus.store(ChannelStatus::OFF); + break; + + case EventDispatcher::EventType::SEQUENCER_REWIND: + ch.shared->playStatus.store(onFirstBeat(ch)); + + default: + break; + } +} + +/* -------------------------------------------------------------------------- */ + +void MidiController::advance(const Channel& ch, const Sequencer::Event& e) const +{ + if (e.type == Sequencer::EventType::FIRST_BEAT) + ch.shared->playStatus.store(onFirstBeat(ch)); +} + +/* -------------------------------------------------------------------------- */ + +ChannelStatus MidiController::onFirstBeat(const Channel& ch) const { - ChannelStatus playStatus = ch.state->playStatus.load(); + ChannelStatus playStatus = ch.shared->playStatus.load(); if (playStatus == ChannelStatus::ENDING) playStatus = ChannelStatus::OFF; @@ -47,9 +76,9 @@ ChannelStatus onFirstBeat_(const channel::Data& ch) /* -------------------------------------------------------------------------- */ -ChannelStatus press_(const channel::Data& ch) +ChannelStatus MidiController::press(const Channel& ch) const { - ChannelStatus playStatus = ch.state->playStatus.load(); + ChannelStatus playStatus = ch.shared->playStatus.load(); switch (playStatus) { @@ -72,39 +101,4 @@ ChannelStatus press_(const channel::Data& ch) return playStatus; } -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void react(channel::Data& ch, const eventDispatcher::Event& e) -{ - switch (e.type) - { - - case eventDispatcher::EventType::KEY_PRESS: - ch.state->playStatus.store(press_(ch)); - break; - - case eventDispatcher::EventType::KEY_KILL: - case eventDispatcher::EventType::SEQUENCER_STOP: - ch.state->playStatus.store(ChannelStatus::OFF); - break; - - case eventDispatcher::EventType::SEQUENCER_REWIND: - ch.state->playStatus.store(onFirstBeat_(ch)); - - default: - break; - } -} - -/* -------------------------------------------------------------------------- */ - -void advance(const channel::Data& ch, const sequencer::Event& e) -{ - if (e.type == sequencer::EventType::FIRST_BEAT) - ch.state->playStatus.store(onFirstBeat_(ch)); -} -} // namespace giada::m::midiController +} // namespace giada::m diff --git a/src/core/channels/midiController.h b/src/core/channels/midiController.h index 8f73435..48b1a79 100644 --- a/src/core/channels/midiController.h +++ b/src/core/channels/midiController.h @@ -27,26 +27,21 @@ #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 diff --git a/src/core/channels/midiLearner.cpp b/src/core/channels/midiLearner.cpp index 48e2a9f..bb5492d 100644 --- a/src/core/channels/midiLearner.cpp +++ b/src/core/channels/midiLearner.cpp @@ -27,9 +27,17 @@ #include "midiLearner.h" #include "core/patch.h" -namespace giada::m::midiLearner +namespace giada::m { -Data::Data(const patch::Channel& p) +MidiLearner::MidiLearner() +: enabled(false) +, filter(-1) +{ +} + +/* -------------------------------------------------------------------------- */ + +MidiLearner::MidiLearner(const Patch::Channel& p) : enabled(p.midiIn) , filter(p.midiInFilter) , keyPress(p.midiInKeyPress) @@ -46,8 +54,8 @@ Data::Data(const patch::Channel& p) /* -------------------------------------------------------------------------- */ -bool Data::isAllowed(int c) const +bool MidiLearner::isAllowed(int c) const { return enabled && (filter == -1 || filter == c); } -} // namespace giada::m::midiLearner \ No newline at end of file +} // namespace giada::m \ No newline at end of file diff --git a/src/core/channels/midiLearner.h b/src/core/channels/midiLearner.h index e1e95c2..286b3cc 100644 --- a/src/core/channels/midiLearner.h +++ b/src/core/channels/midiLearner.h @@ -28,18 +28,16 @@ #define G_CHANNEL_MIDI_LEARNER_H #include "core/midiLearnParam.h" +#include "core/patch.h" -namespace giada::m::patch +namespace giada::m { -struct Channel; -} -namespace giada::m::midiLearner +class MidiLearner final { -struct Data -{ - Data() = default; - Data(const patch::Channel&); - Data(const Data&) = default; +public: + MidiLearner(); + MidiLearner(const Patch::Channel&); + MidiLearner(const MidiLearner&) = default; /* isAllowed Tells whether the MIDI channel 'c' is enabled to receive MIDI data. */ @@ -69,6 +67,6 @@ struct Data MidiLearnParam readActions; // Sample Channels only MidiLearnParam pitch; // Sample Channels only }; -} // namespace giada::m::midiLearner +} // namespace giada::m #endif diff --git a/src/core/channels/midiLighter.cpp b/src/core/channels/midiLighter.cpp index c6f2ca4..687c059 100644 --- a/src/core/channels/midiLighter.cpp +++ b/src/core/channels/midiLighter.cpp @@ -24,109 +24,93 @@ * * -------------------------------------------------------------------------- */ -#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 +MidiLighter::MidiLighter(MidiMapper& 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 +MidiLighter::MidiLighter(MidiMapper& 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 +void MidiLighter::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 +void MidiLighter::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 +void MidiLighter::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; +#ifdef WITH_TESTS +template struct MidiLighter; +#endif +} // namespace giada::m \ No newline at end of file diff --git a/src/core/channels/midiLighter.h b/src/core/channels/midiLighter.h index 483d21e..b34dbc0 100644 --- a/src/core/channels/midiLighter.h +++ b/src/core/channels/midiLighter.h @@ -27,41 +27,44 @@ #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 +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&); + MidiLighter(MidiMapper&, 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* m_midiMapper; }; -void react(channel::Data& ch, const eventDispatcher::Event& e, bool audible); -} // namespace giada::m::midiLighter +extern template struct MidiLighter; +#ifdef WITH_TESTS +extern template struct MidiLighter; +#endif +} // namespace giada::m #endif diff --git a/src/core/channels/midiReceiver.cpp b/src/core/channels/midiReceiver.cpp index 595d682..ada713a 100644 --- a/src/core/channels/midiReceiver.cpp +++ b/src/core/channels/midiReceiver.cpp @@ -32,74 +32,75 @@ #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(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(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 diff --git a/src/core/channels/midiReceiver.h b/src/core/channels/midiReceiver.h index b2f7aa8..050bc4f 100644 --- a/src/core/channels/midiReceiver.h +++ b/src/core/channels/midiReceiver.h @@ -29,28 +29,24 @@ #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 diff --git a/src/core/channels/midiSender.cpp b/src/core/channels/midiSender.cpp index e4994ca..b50fc9a 100644 --- a/src/core/channels/midiSender.cpp +++ b/src/core/channels/midiSender.cpp @@ -24,60 +24,65 @@ * * -------------------------------------------------------------------------- */ -#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& 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& 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 diff --git a/src/core/channels/midiSender.h b/src/core/channels/midiSender.h index dbecca3..a405aff 100644 --- a/src/core/channels/midiSender.h +++ b/src/core/channels/midiSender.h @@ -27,29 +27,24 @@ #ifndef G_CHANNEL_MIDI_SENDER_H #define G_CHANNEL_MIDI_SENDER_H -namespace giada::m::channel -{ -struct Data; -} -namespace giada::m::patch -{ -struct Channel; -} -namespace giada::m::eventDispatcher -{ -struct Event; -} -namespace giada::m::sequencer -{ -struct Event; -} -namespace giada::m::midiSender +#include "core/patch.h" +#include "core/sequencer.h" + +namespace giada::m { -struct Data +class KernelMidi; +class Channel; +class MidiSender final { - Data() = default; - Data(const patch::Channel& p); - Data(const Data& o) = default; +public: + MidiSender(KernelMidi&); + MidiSender(const Patch::Channel& p, KernelMidi&); + MidiSender(const MidiSender& o) = default; + + void react(const Channel& ch, const EventDispatcher::Event& e); + void advance(const Channel& ch, const Sequencer::Event& e) const; + + KernelMidi* kernelMidi; /* enabled Tells whether MIDI output is enabled or not. */ @@ -60,10 +55,11 @@ struct Data Which MIDI channel data should be sent to. */ int filter; -}; -void react(const channel::Data& ch, const eventDispatcher::Event& e); -void advance(const channel::Data& ch, const sequencer::Event& e); -} // namespace giada::m::midiSender +private: + void send(MidiEvent e) const; + void parseActions(const Channel& ch, const std::vector& as) const; +}; +} // namespace giada::m #endif diff --git a/src/core/channels/sampleActionRecorder.cpp b/src/core/channels/sampleActionRecorder.cpp index cbfd7bd..cb0445c 100644 --- a/src/core/channels/sampleActionRecorder.cpp +++ b/src/core/channels/sampleActionRecorder.cpp @@ -24,175 +24,149 @@ * * -------------------------------------------------------------------------- */ -#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 -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 diff --git a/src/core/channels/sampleActionRecorder.h b/src/core/channels/sampleActionRecorder.h index 4ecac81..7f86b09 100644 --- a/src/core/channels/sampleActionRecorder.h +++ b/src/core/channels/sampleActionRecorder.h @@ -27,21 +27,33 @@ #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 diff --git a/src/core/channels/sampleAdvancer.cpp b/src/core/channels/sampleAdvancer.cpp index d60df06..e029c34 100644 --- a/src/core/channels/sampleAdvancer.cpp +++ b/src/core/channels/sampleAdvancer.cpp @@ -24,67 +24,132 @@ * * -------------------------------------------------------------------------- */ -#include "sampleAdvancer.h" +#include "core/channels/sampleAdvancer.h" #include "core/channels/channel.h" -#include "core/clock.h" #include -namespace giada::m::sampleAdvancer +namespace giada::m { -namespace +void SampleAdvancer::onLastFrame(const Channel& ch, bool seqIsRunning) const { -void rewind_(const channel::Data& ch, Frame localFrame) + const SamplePlayerMode mode = ch.samplePlayer->mode; + const bool isLoop = ch.samplePlayer->isAnyLoopMode(); + + switch (ch.shared->playStatus.load()) + { + case ChannelStatus::PLAY: + /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for + SINGLE_ENDLESS, which runs forever unless it's in ENDING mode. + Other loop once modes are put in wait mode. */ + if ((mode == SamplePlayerMode::SINGLE_BASIC || + mode == SamplePlayerMode::SINGLE_BASIC_PAUSE || + mode == SamplePlayerMode::SINGLE_PRESS || + mode == SamplePlayerMode::SINGLE_RETRIG) || + (isLoop && !seqIsRunning)) + stop(ch, 0); + else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR) + wait(ch); + break; + + case ChannelStatus::ENDING: + stop(ch, 0); + break; + + default: + break; + } +} + +/* -------------------------------------------------------------------------- */ + +void SampleAdvancer::advance(const Channel& ch, const Sequencer::Event& e) const { - ch.state->rewinding = true; - ch.state->offset = localFrame; + switch (e.type) + { + case Sequencer::EventType::FIRST_BEAT: + onFirstBeat(ch, e.delta); + break; + + case Sequencer::EventType::BAR: + onBar(ch, e.delta); + break; + + case Sequencer::EventType::REWIND: + rewind(ch, e.delta); + break; + + case Sequencer::EventType::ACTIONS: + if (ch.shared->readActions.load() == true) + parseActions(ch, *e.actions, e.delta); + break; + + default: + break; + } } /* -------------------------------------------------------------------------- */ -void stop_(const channel::Data& ch, Frame localFrame) +void SampleAdvancer::rewind(const Channel& ch, Frame localFrame) const { - ch.state->playStatus.store(ChannelStatus::OFF); - ch.state->tracker.store(ch.samplePlayer->begin); + ch.shared->rewinding = true; + ch.shared->offset = localFrame; +} + +/* -------------------------------------------------------------------------- */ + +void SampleAdvancer::stop(const Channel& ch, Frame localFrame) const +{ + ch.shared->playStatus.store(ChannelStatus::OFF); + ch.shared->tracker.store(ch.samplePlayer->begin); /* Clear data in range [localFrame, (buffer.size)) if the event occurs in the middle of the buffer. TODO - samplePlayer should be responsible for this*/ if (localFrame != 0) - ch.buffer->audio.clear(localFrame); + ch.shared->audioBuffer.clear(localFrame); +} + +/* -------------------------------------------------------------------------- */ + +void SampleAdvancer::play(const Channel& ch, Frame localFrame) const +{ + ch.shared->playStatus.store(ChannelStatus::PLAY); + ch.shared->offset = localFrame; } /* -------------------------------------------------------------------------- */ -void play_(const channel::Data& ch, Frame localFrame) +void SampleAdvancer::wait(const Channel& ch) const { - ch.state->playStatus.store(ChannelStatus::PLAY); - ch.state->offset = localFrame; + ch.shared->playStatus.store(ChannelStatus::WAIT); + ch.shared->tracker.store(ch.samplePlayer->begin); } /* -------------------------------------------------------------------------- */ -void onFirstBeat_(const channel::Data& ch, Frame localFrame) +void SampleAdvancer::onFirstBeat(const Channel& ch, Frame localFrame) const { G_DEBUG("onFirstBeat ch=" << ch.id << ", localFrame=" << localFrame); - ChannelStatus playStatus = ch.state->playStatus.load(); - ChannelStatus recStatus = ch.state->recStatus.load(); - bool isLoop = ch.samplePlayer->isAnyLoopMode(); + const ChannelStatus playStatus = ch.shared->playStatus.load(); + const ChannelStatus recStatus = ch.shared->recStatus.load(); + const bool isLoop = ch.samplePlayer->isAnyLoopMode(); switch (playStatus) { case ChannelStatus::PLAY: if (isLoop) - rewind_(ch, localFrame); + rewind(ch, localFrame); break; case ChannelStatus::WAIT: - play_(ch, localFrame); + play(ch, localFrame); break; case ChannelStatus::ENDING: if (isLoop) - stop_(ch, localFrame); + stop(ch, localFrame); break; default: @@ -94,13 +159,13 @@ void onFirstBeat_(const channel::Data& ch, Frame localFrame) switch (recStatus) { case ChannelStatus::WAIT: - ch.state->recStatus.store(ChannelStatus::PLAY); - ch.state->readActions.store(true); + ch.shared->recStatus.store(ChannelStatus::PLAY); + ch.shared->readActions.store(true); break; case ChannelStatus::ENDING: - ch.state->recStatus.store(ChannelStatus::OFF); - ch.state->readActions.store(false); + ch.shared->recStatus.store(ChannelStatus::OFF); + ch.shared->readActions.store(false); break; default: @@ -110,59 +175,46 @@ void onFirstBeat_(const channel::Data& ch, Frame localFrame) /* -------------------------------------------------------------------------- */ -void onBar_(const channel::Data& ch, Frame localFrame) +void SampleAdvancer::onBar(const Channel& ch, Frame localFrame) const { G_DEBUG("onBar ch=" << ch.id << ", localFrame=" << localFrame); - ChannelStatus playStatus = ch.state->playStatus.load(); - SamplePlayerMode mode = ch.samplePlayer->mode; + const ChannelStatus playStatus = ch.shared->playStatus.load(); + const SamplePlayerMode mode = ch.samplePlayer->mode; if (playStatus == ChannelStatus::PLAY && (mode == SamplePlayerMode::LOOP_REPEAT || mode == SamplePlayerMode::LOOP_ONCE_BAR)) - rewind_(ch, localFrame); + rewind(ch, localFrame); else if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR) - ch.state->playStatus.store(ChannelStatus::PLAY); + play(ch, localFrame); } /* -------------------------------------------------------------------------- */ -void onNoteOn_(const channel::Data& ch, Frame localFrame) +void SampleAdvancer::onNoteOn(const Channel& ch, Frame localFrame) const { - ChannelStatus playStatus = ch.state->playStatus.load(); - - if (playStatus == ChannelStatus::OFF) - { - playStatus = ChannelStatus::PLAY; - } - else if (playStatus == ChannelStatus::PLAY) + switch (ch.shared->playStatus.load()) { + case ChannelStatus::OFF: + play(ch, localFrame); + break; + + case ChannelStatus::PLAY: if (ch.samplePlayer->mode == SamplePlayerMode::SINGLE_RETRIG) - rewind_(ch, localFrame); + rewind(ch, localFrame); else - playStatus = ChannelStatus::OFF; - } - - ch.state->playStatus.store(playStatus); - ch.state->offset = localFrame; -} - -/* -------------------------------------------------------------------------- */ - -void onNoteOff_(const channel::Data& ch, Frame localFrame) -{ - ch.state->playStatus.store(ChannelStatus::OFF); - ch.state->tracker.store(ch.samplePlayer->begin); - - /* Clear data in range [localFrame, (buffer.size)) if the kill event occurs - in the middle of the buffer. */ + stop(ch, localFrame); + break; - if (localFrame != 0) - ch.buffer->audio.clear(localFrame); + default: + break; + } } /* -------------------------------------------------------------------------- */ -void parseActions_(const channel::Data& ch, const std::vector& as, Frame localFrame) +void SampleAdvancer::parseActions(const Channel& ch, + const std::vector& as, Frame localFrame) const { if (ch.samplePlayer->isAnyLoopMode() || !ch.isReadingActions()) return; @@ -175,15 +227,12 @@ void parseActions_(const channel::Data& ch, const std::vector& as, Frame switch (a.event.getStatus()) { case MidiEvent::NOTE_ON: - onNoteOn_(ch, localFrame); + onNoteOn(ch, localFrame); break; case MidiEvent::NOTE_OFF: - onNoteOff_(ch, localFrame); - break; - case MidiEvent::NOTE_KILL: - onNoteOff_(ch, localFrame); + stop(ch, localFrame); break; default: @@ -191,63 +240,4 @@ void parseActions_(const channel::Data& ch, const std::vector& as, Frame } } } -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void onLastFrame(const channel::Data& ch) -{ - ChannelStatus playStatus = ch.state->playStatus.load(); - SamplePlayerMode mode = ch.samplePlayer->mode; - bool isLoop = ch.samplePlayer->isAnyLoopMode(); - bool running = clock::isRunning(); - - if (playStatus == ChannelStatus::PLAY) - { - /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for - SINGLE_ENDLESS, which runs forever unless it's in ENDING mode. - Other loop once modes are put in wait mode. */ - if ((mode == SamplePlayerMode::SINGLE_BASIC || - mode == SamplePlayerMode::SINGLE_PRESS || - mode == SamplePlayerMode::SINGLE_RETRIG) || - (isLoop && !running)) - playStatus = ChannelStatus::OFF; - else if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR) - playStatus = ChannelStatus::WAIT; - } - else if (playStatus == ChannelStatus::ENDING) - playStatus = ChannelStatus::OFF; - - ch.state->playStatus.store(playStatus); -} - -/* -------------------------------------------------------------------------- */ - -void advance(const channel::Data& ch, const sequencer::Event& e) -{ - switch (e.type) - { - case sequencer::EventType::FIRST_BEAT: - onFirstBeat_(ch, e.delta); - break; - - case sequencer::EventType::BAR: - onBar_(ch, e.delta); - break; - - case sequencer::EventType::REWIND: - rewind_(ch, e.delta); - break; - - case sequencer::EventType::ACTIONS: - if (ch.state->readActions.load() == true) - parseActions_(ch, *e.actions, e.delta); - break; - - default: - break; - } -} -} // namespace giada::m::sampleAdvancer \ No newline at end of file +} // namespace giada::m \ No newline at end of file diff --git a/src/core/channels/sampleAdvancer.h b/src/core/channels/sampleAdvancer.h index c19b97e..bf5a8d4 100644 --- a/src/core/channels/sampleAdvancer.h +++ b/src/core/channels/sampleAdvancer.h @@ -29,14 +29,25 @@ #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& as, Frame localFrame) const; +}; +} // namespace giada::m #endif diff --git a/src/core/channels/samplePlayer.cpp b/src/core/channels/samplePlayer.cpp index 78c6747..350d341 100644 --- a/src/core/channels/samplePlayer.cpp +++ b/src/core/channels/samplePlayer.cpp @@ -26,72 +26,19 @@ #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 #include -namespace giada::m::samplePlayer +namespace giada::m { -namespace -{ -bool shouldLoop_(const channel::Data& ch) -{ - ChannelStatus playStatus = ch.state->playStatus.load(); - SamplePlayerMode mode = ch.samplePlayer->mode; - - return (mode == SamplePlayerMode::LOOP_BASIC || - mode == SamplePlayerMode::LOOP_REPEAT || - mode == SamplePlayerMode::SINGLE_ENDLESS) && - playStatus == ChannelStatus::PLAY; -} - -/* -------------------------------------------------------------------------- */ - -WaveReader::Result fillBuffer_(const channel::Data& ch, Frame start, Frame offset) -{ - AudioBuffer& buffer = ch.buffer->audio; - const WaveReader& waveReader = ch.samplePlayer->waveReader; - - return waveReader.fill(buffer, start, ch.samplePlayer->end, offset, ch.samplePlayer->pitch); -} - -/* -------------------------------------------------------------------------- */ - -bool isPlaying_(const channel::Data& ch) -{ - return ch.samplePlayer->waveReader.wave != nullptr && ch.isPlaying(); -} - -/* -------------------------------------------------------------------------- */ - -void setWave_(samplePlayer::Data& sp, Wave* w, float samplerateRatio) -{ - if (w == nullptr) - { - sp.waveReader.wave = nullptr; - return; - } - - sp.waveReader.wave = w; - - if (samplerateRatio != 1.0f) - { - sp.begin *= samplerateRatio; - sp.end *= samplerateRatio; - sp.shift *= samplerateRatio; - } -} -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -Data::Data(Resampler* r) +SamplePlayer::SamplePlayer(Resampler* r) : pitch(G_DEFAULT_PITCH) , mode(SamplePlayerMode::SINGLE_BASIC) +, shift(0) +, begin(0) +, end(0) , velocityAsVol(false) , waveReader(r) { @@ -99,7 +46,7 @@ Data::Data(Resampler* r) /* -------------------------------------------------------------------------- */ -Data::Data(const patch::Channel& p, float samplerateRatio, Resampler* r) +SamplePlayer::SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w) : pitch(p.pitch) , mode(p.mode) , shift(p.shift) @@ -107,19 +54,20 @@ Data::Data(const patch::Channel& p, float samplerateRatio, Resampler* r) , end(p.end) , velocityAsVol(p.midiInVeloAsVol) , waveReader(r) +, onLastFrame(nullptr) { - setWave_(*this, waveManager::hydrateWave(p.waveId), samplerateRatio); + setWave(w, samplerateRatio); } /* -------------------------------------------------------------------------- */ -bool Data::hasWave() const { return waveReader.wave != nullptr; } -bool Data::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); } -bool Data::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); } +bool SamplePlayer::hasWave() const { return waveReader.wave != nullptr; } +bool SamplePlayer::hasLogicalWave() const { return hasWave() && waveReader.wave->isLogical(); } +bool SamplePlayer::hasEditedWave() const { return hasWave() && waveReader.wave->isEdited(); } /* -------------------------------------------------------------------------- */ -bool Data::isAnyLoopMode() const +bool SamplePlayer::isAnyLoopMode() const { return mode == SamplePlayerMode::LOOP_BASIC || mode == SamplePlayerMode::LOOP_ONCE || @@ -129,12 +77,12 @@ bool Data::isAnyLoopMode() const /* -------------------------------------------------------------------------- */ -Wave* Data::getWave() const +Wave* SamplePlayer::getWave() const { return waveReader.wave; } -ID Data::getWaveId() const +ID SamplePlayer::getWaveId() const { if (hasWave()) return waveReader.wave->id; @@ -143,57 +91,45 @@ ID Data::getWaveId() const /* -------------------------------------------------------------------------- */ -Frame Data::getWaveSize() const +Frame SamplePlayer::getWaveSize() const { return hasWave() ? waveReader.wave->getBuffer().countFrames() : 0; } /* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void react(channel::Data& ch, const eventDispatcher::Event& e) -{ - if (e.type == eventDispatcher::EventType::CHANNEL_PITCH) - ch.samplePlayer->pitch = std::get(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(e.data); } /* -------------------------------------------------------------------------- */ -void render(const channel::Data& ch) +void SamplePlayer::render(const Channel& ch) const { - if (!isPlaying_(ch)) + if (!isPlaying(ch)) return; - const Frame begin = ch.samplePlayer->begin; - const Frame end = ch.samplePlayer->end; - /* Make sure tracker stays within begin-end range. */ - Frame tracker = std::clamp(ch.state->tracker.load(), begin, end); + Frame tracker = std::clamp(ch.shared->tracker.load(), begin, end); /* If rewinding, fill the tail first, then reset the tracker to the begin point. The rest is performed as usual. */ - if (ch.state->rewinding) + if (ch.shared->rewinding) { if (tracker < end) { - fillBuffer_(ch, tracker, 0); - ch.samplePlayer->waveReader.last(); + fillBuffer(ch, tracker, 0); + waveReader.last(); } - ch.state->rewinding = false; + ch.shared->rewinding = false; tracker = begin; } - WaveReader::Result res = fillBuffer_(ch, tracker, ch.state->offset); + WaveReader::Result res = fillBuffer(ch, tracker, ch.shared->offset); tracker += res.used; /* If tracker has looped, special care is needed for the rendering. If the @@ -202,53 +138,94 @@ void render(const channel::Data& ch) if (tracker >= end) { - ch.samplePlayer->waveReader.last(); + assert(onLastFrame != nullptr); + tracker = begin; - sampleAdvancer::onLastFrame(ch); // TODO - better moving this to samplerAdvancer::advance - if (shouldLoop_(ch) && res.generated < ch.buffer->audio.countFrames()) - tracker += fillBuffer_(ch, tracker, res.generated).used; + waveReader.last(); + onLastFrame(); + if (shouldLoop(ch) && res.generated < ch.shared->audioBuffer.countFrames()) + tracker += fillBuffer(ch, tracker, res.generated).used; } - ch.state->offset = 0; - ch.state->tracker.store(tracker); + ch.shared->offset = 0; + ch.shared->tracker.store(tracker); } /* -------------------------------------------------------------------------- */ -void loadWave(channel::Data& ch, Wave* w) +void SamplePlayer::loadWave(Channel& ch, Wave* w) { - ch.samplePlayer->waveReader.wave = w; + waveReader.wave = w; - ch.state->tracker.store(0); - ch.samplePlayer->shift = 0; - ch.samplePlayer->begin = 0; + ch.shared->tracker.store(0); + shift = 0; + begin = 0; if (w != nullptr) { - ch.state->playStatus.store(ChannelStatus::OFF); - ch.name = w->getBasename(/*ext=*/false); - ch.samplePlayer->end = w->getBuffer().countFrames() - 1; + ch.shared->playStatus.store(ChannelStatus::OFF); + ch.name = w->getBasename(/*ext=*/false); + end = w->getBuffer().countFrames() - 1; } else { - ch.state->playStatus.store(ChannelStatus::EMPTY); - ch.name = ""; - ch.samplePlayer->end = 0; + ch.shared->playStatus.store(ChannelStatus::EMPTY); + ch.name = ""; + end = 0; + } +} + +/* -------------------------------------------------------------------------- */ + +void SamplePlayer::setWave(Wave* w, float samplerateRatio) +{ + if (w == nullptr) + { + waveReader.wave = nullptr; + return; + } + + waveReader.wave = w; + + if (samplerateRatio != 1.0f) + { + begin *= samplerateRatio; + end *= samplerateRatio; + shift *= samplerateRatio; } } /* -------------------------------------------------------------------------- */ -void setWave(channel::Data& ch, Wave* w, float samplerateRatio) +void SamplePlayer::kickIn(Channel& ch, Frame f) +{ + ch.shared->tracker.store(f); + ch.shared->playStatus.store(ChannelStatus::PLAY); +} + +/* -------------------------------------------------------------------------- */ + +bool SamplePlayer::isPlaying(const Channel& ch) const { - setWave_(ch.samplePlayer.value(), w, samplerateRatio); + return waveReader.wave != nullptr && ch.isPlaying(); } /* -------------------------------------------------------------------------- */ -void kickIn(channel::Data& ch, Frame f) +WaveReader::Result SamplePlayer::fillBuffer(const Channel& ch, Frame start, Frame offset) const { - ch.state->tracker.store(f); - ch.state->playStatus.store(ChannelStatus::PLAY); + return waveReader.fill(ch.shared->audioBuffer, start, end, offset, pitch); +} + +/* -------------------------------------------------------------------------- */ + +bool SamplePlayer::shouldLoop(const Channel& ch) const +{ + const ChannelStatus playStatus = ch.shared->playStatus.load(); + + return (mode == SamplePlayerMode::LOOP_BASIC || + mode == SamplePlayerMode::LOOP_REPEAT || + mode == SamplePlayerMode::SINGLE_ENDLESS) && + playStatus == ChannelStatus::PLAY; } -} // namespace giada::m::samplePlayer \ No newline at end of file +} // namespace giada::m \ No newline at end of file diff --git a/src/core/channels/samplePlayer.h b/src/core/channels/samplePlayer.h index e8dff83..d9bcbcf 100644 --- a/src/core/channels/samplePlayer.h +++ b/src/core/channels/samplePlayer.h @@ -27,30 +27,22 @@ #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 -namespace giada::m::channel +namespace giada::m { -struct Data; -} -namespace giada::m::patch +class Channel; +class SamplePlayer final { -struct Channel; -} -namespace giada::m::samplePlayer -{ -struct Data -{ - Data(Resampler* r); - Data(const patch::Channel& p, float samplerateRatio, Resampler* r); - Data(const Data& o) = default; - Data(Data&& o) = default; - Data& operator=(const Data&) = default; - Data& operator=(Data&&) = default; +public: + SamplePlayer(Resampler* r); + SamplePlayer(const Patch::Channel& p, float samplerateRatio, Resampler* r, Wave* w); bool hasWave() const; bool hasLogicalWave() const; @@ -59,6 +51,27 @@ struct Data ID getWaveId() const; Frame getWaveSize() const; Wave* getWave() const; + void render(const Channel& ch) const; + + void react(const EventDispatcher::Event& e); + + /* loadWave + Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */ + + void loadWave(Channel& ch, Wave* w); + + /* setWave + Just sets the pointer to a Wave object. Used during de-serialization. The + ratio is used to adjust begin/end points in case of patch vs. conf sample + rate mismatch. If nullptr, set the wave to invalid. */ + + void setWave(Wave* w, float samplerateRatio); + + /* kickIn + Starts the player right away at frame 'f'. Used when launching a loop after + being live recorded. */ + + void kickIn(Channel& ch, Frame f); float pitch; SamplePlayerMode mode; @@ -67,29 +80,14 @@ struct Data Frame end; bool velocityAsVol; // Velocity drives volume WaveReader waveReader; -}; - -void react(channel::Data& ch, const eventDispatcher::Event& e); -void advance(const channel::Data& ch, const sequencer::Event& e); -void render(const channel::Data& ch); - -/* loadWave -Loads Wave 'w' into channel ch and sets it up (name, markers, ...). */ -void loadWave(channel::Data& ch, Wave* w); + std::function 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 diff --git a/src/core/channels/sampleReactor.cpp b/src/core/channels/sampleReactor.cpp index 378cb58..1358a5f 100644 --- a/src/core/channels/sampleReactor.cpp +++ b/src/core/channels/sampleReactor.cpp @@ -24,122 +24,93 @@ * * -------------------------------------------------------------------------- */ -#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 -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(e.data)); break; - case ChannelStatus::PLAY: - playStatus = pressWhilePlay_(ch, mode, isLoop); + case EventDispatcher::EventType::KEY_RELEASE: + release(ch, sequencer); break; - case ChannelStatus::WAIT: - playStatus = ChannelStatus::OFF; + case EventDispatcher::EventType::KEY_KILL: + kill(ch); break; - case ChannelStatus::ENDING: - playStatus = ChannelStatus::PLAY; + case EventDispatcher::EventType::SEQUENCER_STOP: + onStopBySeq(ch, conf.chansStopOnSeqHalt); + break; + + case EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS: + toggleReadActions(ch, sequencer.isRunning(), conf.treatRecsAsLoops); break; default: break; } - - ch.state->playStatus.store(playStatus); } /* -------------------------------------------------------------------------- */ -void release_(channel::Data& ch) +void SampleReactor::rewind(Channel& ch, Frame localFrame) const { - /* Key release is meaningful only for SINGLE_PRESS modes. */ - - if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS) - return; - - /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a - quantization step in progress that would play the channel later on: - disable it. */ - - if (ch.state->playStatus.load() == ChannelStatus::PLAY) - kill_(ch); - else if (sequencer::quantizer.hasBeenTriggered()) - sequencer::quantizer.clear(); + ch.shared->rewinding = true; + ch.shared->offset = localFrame; } /* -------------------------------------------------------------------------- */ -void kill_(channel::Data& ch) +void SampleReactor::reset(Channel& ch) const { - ch.state->playStatus.store(ChannelStatus::OFF); - ch.state->tracker.store(ch.samplePlayer->begin); + ch.shared->tracker.store(ch.samplePlayer->begin); } /* -------------------------------------------------------------------------- */ -void onStopBySeq_(channel::Data& ch) -{ - G_DEBUG("onStopBySeq ch=" << ch.id); - - ChannelStatus playStatus = ch.state->playStatus.load(); - bool isReadingActions = ch.state->readActions.load(); - bool isLoop = ch.samplePlayer->isAnyLoopMode(); - - switch (playStatus) - { - - case ChannelStatus::WAIT: - /* Loop-mode channels in wait status get stopped right away. */ - if (isLoop) - ch.state->playStatus.store(ChannelStatus::OFF); - break; - - case ChannelStatus::PLAY: - if (conf::conf.chansStopOnSeqHalt && (isLoop || isReadingActions)) - kill_(ch); - break; - - default: - break; - } -} - -/* -------------------------------------------------------------------------- */ - -ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop) +ChannelStatus SampleReactor::pressWhileOff(Channel& ch, Sequencer& sequencer, + int velocity, bool isLoop) const { if (isLoop) return ChannelStatus::WAIT; @@ -147,9 +118,9 @@ ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop) if (ch.samplePlayer->velocityAsVol) ch.volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME); - if (clock::canQuantize()) + if (sequencer.canQuantize()) { - sequencer::quantizer.trigger(Q_ACTION_PLAY + ch.id); + sequencer.quantizer.trigger(Q_ACTION_PLAY + ch.id); return ChannelStatus::OFF; } else @@ -158,103 +129,127 @@ ChannelStatus pressWhileOff_(channel::Data& ch, int velocity, bool isLoop) /* -------------------------------------------------------------------------- */ -ChannelStatus pressWhilePlay_(channel::Data& ch, SamplePlayerMode mode, bool isLoop) +ChannelStatus SampleReactor::pressWhilePlay(Channel& ch, Sequencer& sequencer, + SamplePlayerMode mode, bool isLoop) const { - if (mode == SamplePlayerMode::SINGLE_RETRIG) + if (isLoop) + return ChannelStatus::ENDING; + + switch (mode) { - if (clock::canQuantize()) - sequencer::quantizer.trigger(Q_ACTION_REWIND + ch.id); + case SamplePlayerMode::SINGLE_RETRIG: + if (sequencer.canQuantize()) + sequencer.quantizer.trigger(Q_ACTION_REWIND + ch.id); else - rewind_(ch); + rewind(ch, /*localFrame=*/0); return ChannelStatus::PLAY; - } - if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS) + case SamplePlayerMode::SINGLE_ENDLESS: return ChannelStatus::ENDING; - if (mode == SamplePlayerMode::SINGLE_BASIC) - { - rewind_(ch); + case SamplePlayerMode::SINGLE_BASIC: + reset(ch); return ChannelStatus::OFF; - } - return ChannelStatus::OFF; + default: + return ChannelStatus::OFF; + } } /* -------------------------------------------------------------------------- */ -void toggleReadActions_(channel::Data& ch) +void SampleReactor::press(Channel& ch, Sequencer& sequencer, int velocity) const { - if (clock::isRunning() && ch.state->recStatus.load() == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops) - kill_(ch); -} + const SamplePlayerMode mode = ch.samplePlayer->mode; + const bool isLoop = ch.samplePlayer->isAnyLoopMode(); -/* -------------------------------------------------------------------------- */ + ChannelStatus playStatus = ch.shared->playStatus.load(); -void rewind_(channel::Data& ch, Frame localFrame) -{ - if (ch.isPlaying()) + switch (playStatus) { - ch.state->rewinding = true; - ch.state->offset = localFrame; + case ChannelStatus::OFF: + playStatus = pressWhileOff(ch, sequencer, velocity, isLoop); + break; + + case ChannelStatus::PLAY: + playStatus = pressWhilePlay(ch, sequencer, mode, isLoop); + break; + + case ChannelStatus::WAIT: + playStatus = ChannelStatus::OFF; + break; + + case ChannelStatus::ENDING: + playStatus = ChannelStatus::PLAY; + break; + + default: + break; } - else - ch.state->tracker.store(ch.samplePlayer->begin); + + ch.shared->playStatus.store(playStatus); } -} // namespace -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Data::Data(ID channelId) +void SampleReactor::kill(Channel& ch) const { - sequencer::quantizer.schedule(Q_ACTION_PLAY + channelId, [channelId](Frame delta) { - channel::Data& ch = model::get().getChannel(channelId); - ch.state->offset = delta; - ch.state->playStatus.store(ChannelStatus::PLAY); - }); - - sequencer::quantizer.schedule(Q_ACTION_REWIND + channelId, [channelId](Frame delta) { - channel::Data& ch = model::get().getChannel(channelId); - rewind_(ch, delta); - }); + ch.shared->playStatus.store(ChannelStatus::OFF); + ch.shared->tracker.store(ch.samplePlayer->begin); } - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void react(channel::Data& ch, const eventDispatcher::Event& e) +void SampleReactor::release(Channel& ch, Sequencer& sequencer) const { - if (!ch.hasWave()) + /* Key release is meaningful only for SINGLE_PRESS modes. */ + + if (ch.samplePlayer->mode != SamplePlayerMode::SINGLE_PRESS) return; - switch (e.type) - { + /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a + quantization step in progress that would play the channel later on: + disable it. */ - case eventDispatcher::EventType::KEY_PRESS: - press_(ch, std::get(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 diff --git a/src/core/channels/sampleReactor.h b/src/core/channels/sampleReactor.h index a82b1c7..cfb725c 100644 --- a/src/core/channels/sampleReactor.h +++ b/src/core/channels/sampleReactor.h @@ -27,30 +27,43 @@ #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 diff --git a/src/core/channels/waveReader.cpp b/src/core/channels/waveReader.cpp index fd190ca..0d7946b 100644 --- a/src/core/channels/waveReader.cpp +++ b/src/core/channels/waveReader.cpp @@ -25,10 +25,10 @@ * -------------------------------------------------------------------------- */ #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 #include @@ -44,7 +44,7 @@ WaveReader::WaveReader(Resampler* r) /* -------------------------------------------------------------------------- */ -WaveReader::Result WaveReader::fill(AudioBuffer& out, Frame start, Frame max, +WaveReader::Result WaveReader::fill(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset, float pitch) const { assert(wave != nullptr); @@ -60,7 +60,7 @@ WaveReader::Result WaveReader::fill(AudioBuffer& out, Frame start, Frame max, /* -------------------------------------------------------------------------- */ -WaveReader::Result WaveReader::fillResampled(AudioBuffer& dest, Frame start, +WaveReader::Result WaveReader::fillResampled(mcl::AudioBuffer& dest, Frame start, Frame max, Frame offset, float pitch) const { Resampler::Result res = m_resampler->process( @@ -78,7 +78,8 @@ WaveReader::Result WaveReader::fillResampled(AudioBuffer& dest, Frame start, /* -------------------------------------------------------------------------- */ -WaveReader::Result WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame max, Frame offset) const +WaveReader::Result WaveReader::fillCopy(mcl::AudioBuffer& dest, Frame start, + Frame max, Frame offset) const { Frame used = dest.countFrames() - offset; if (used > max - start) diff --git a/src/core/channels/waveReader.h b/src/core/channels/waveReader.h index ee0ab6d..9af720d 100644 --- a/src/core/channels/waveReader.h +++ b/src/core/channels/waveReader.h @@ -29,10 +29,14 @@ #include "core/types.h" +namespace mcl +{ +class AudioBuffer; +} + namespace giada::m { class Wave; -class AudioBuffer; class Resampler; class WaveReader final { @@ -56,7 +60,7 @@ public: Fills audio buffer 'out' with data coming from Wave, copying it from 'start' frame up to 'max'. The buffer is filled starting at 'offset'. */ - Result fill(AudioBuffer& out, Frame start, Frame max, Frame offset, + Result fill(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset, float pitch) const; /* last @@ -71,9 +75,9 @@ public: Wave* wave; private: - Result fillResampled(AudioBuffer& out, Frame start, Frame max, Frame offset, + Result fillResampled(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset, float pitch) const; - Result fillCopy(AudioBuffer& out, Frame start, Frame max, Frame offset) const; + Result fillCopy(mcl::AudioBuffer& out, Frame start, Frame max, Frame offset) const; Resampler* m_resampler; }; diff --git a/src/core/clock.cpp b/src/core/clock.cpp deleted file mode 100644 index da98093..0000000 --- a/src/core/clock.cpp +++ /dev/null @@ -1,457 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 -#include - -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((conf::conf.samplerate * (60.0f / c.bpm)) * c.beats); - c.framesInBar = static_cast(c.framesInLoop / (float)c.bars); - c.framesInBeat = static_cast(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((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(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(conf::conf.samplerate)); -} -} // namespace giada::m::clock diff --git a/src/core/clock.h b/src/core/clock.h deleted file mode 100644 index 716ec9f..0000000 --- a/src/core/clock.h +++ /dev/null @@ -1,125 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 diff --git a/src/core/conf.cpp b/src/core/conf.cpp index 02c9f15..7240433 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -24,7 +24,7 @@ * * -------------------------------------------------------------------------- */ -#include "conf.h" +#include "core/conf.h" #include "core/const.h" #include "core/types.h" #include "deps/json/single_include/nlohmann/json.hpp" @@ -37,282 +37,216 @@ 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(conf.recTriggerMode); - j[CONF_KEY_REC_TRIGGER_LEVEL] = conf.recTriggerLevel; - j[CONF_KEY_INPUT_REC_MODE] = static_cast(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(data.recTriggerMode); + j[CONF_KEY_REC_TRIGGER_LEVEL] = data.recTriggerLevel; + j[CONF_KEY_INPUT_REC_MODE] = static_cast(data.inputRecMode); #ifdef WITH_VST - j[CONF_KEY_PLUGIN_CHOOSER_X] = conf.pluginChooserX; - j[CONF_KEY_PLUGIN_CHOOSER_Y] = conf.pluginChooserY; - j[CONF_KEY_PLUGIN_CHOOSER_W] = conf.pluginChooserW; - j[CONF_KEY_PLUGIN_CHOOSER_H] = conf.pluginChooserH; - j[CONF_KEY_PLUGIN_SORT_METHOD] = conf.pluginSortMethod; + j[CONF_KEY_PLUGIN_CHOOSER_X] = data.pluginChooserX; + j[CONF_KEY_PLUGIN_CHOOSER_Y] = data.pluginChooserY; + j[CONF_KEY_PLUGIN_CHOOSER_W] = data.pluginChooserW; + j[CONF_KEY_PLUGIN_CHOOSER_H] = data.pluginChooserH; + j[CONF_KEY_PLUGIN_SORT_METHOD] = data.pluginSortMethod; #endif - std::ofstream ofs(confFilePath_); + std::ofstream ofs(m_confFilePath); if (!ofs.good()) { u::log::print("[conf::write] unable to write configuration file!\n"); @@ -322,4 +256,47 @@ bool write() ofs << j; return true; } -} // namespace giada::m::conf \ No newline at end of file + +/* -------------------------------------------------------------------------- */ + +bool Conf::createConfigFolder() const +{ +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) + + if (u::fs::dirExists(m_confDirPath)) + return true; + + u::log::print("[conf::createConfigFolder] .giada folder not present. Updating...\n"); + + if (u::fs::mkdir(m_confDirPath)) + { + u::log::print("[conf::createConfigFolder] status: ok\n"); + return true; + } + else + { + u::log::print("[conf::createConfigFolder] status: error!\n"); + return false; + } + +#else // Windows: nothing to do + + return true; + +#endif +} + +/* -------------------------------------------------------------------------- */ + +void Conf::sanitize() +{ + data.soundDeviceOut = std::max(0, data.soundDeviceOut); + data.channelsOutCount = G_MAX_IO_CHANS; + data.channelsOutStart = std::max(0, data.channelsOutStart); + data.channelsInCount = std::max(1, data.channelsInCount); + data.channelsInStart = std::max(0, data.channelsInStart); + + data.midiPortOut = std::max(-1, data.midiPortOut); + data.midiPortIn = std::max(-1, data.midiPortIn); +} +} // namespace giada::m \ No newline at end of file diff --git a/src/core/conf.h b/src/core/conf.h index 4d1a2db..a34b544 100644 --- a/src/core/conf.h +++ b/src/core/conf.h @@ -32,120 +32,128 @@ #include "utils/gui.h" #include -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 diff --git a/src/core/const.h b/src/core/const.h index 8da87cb..35eccbb 100644 --- a/src/core/const.h +++ b/src/core/const.h @@ -59,9 +59,9 @@ /* -- version --------------------------------------------------------------- */ constexpr auto G_APP_NAME = "Giada"; -constexpr auto G_VERSION_STR = "0.18.1"; +constexpr auto G_VERSION_STR = "0.19.1"; constexpr int G_VERSION_MAJOR = 0; -constexpr int G_VERSION_MINOR = 18; +constexpr int G_VERSION_MINOR = 19; constexpr int G_VERSION_PATCH = 1; constexpr auto CONF_FILENAME = "giada.conf"; @@ -92,7 +92,6 @@ constexpr int G_GUI_ZOOM_FACTOR = 2; #define G_COLOR_RED fl_rgb_color(28, 32, 80) #define G_COLOR_BLUE fl_rgb_color(113, 31, 31) -#define G_COLOR_RED_ALERT fl_rgb_color(239, 75, 53) #define G_COLOR_LIGHT_2 fl_rgb_color(200, 200, 200) #define G_COLOR_LIGHT_1 fl_rgb_color(170, 170, 170) #define G_COLOR_GREY_4 fl_rgb_color(78, 78, 78) @@ -141,18 +140,20 @@ constexpr int G_SYS_API_PULSE = 6; constexpr int G_SYS_API_WASAPI = 7; /* -- kernel midi ----------------------------------------------------------- */ -constexpr int G_MIDI_API_JACK = 0x01; // 0000 0001 -constexpr int G_MIDI_API_ALSA = 0x02; // 0000 0010 +constexpr int G_MIDI_API_JACK = 1; +constexpr int G_MIDI_API_ALSA = 2; +constexpr int G_MIDI_API_MM = 3; +constexpr int G_MIDI_API_CORE = 4; /* -- default system -------------------------------------------------------- */ #if defined(G_OS_LINUX) -#define G_DEFAULT_SOUNDSYS G_SYS_API_NONE +constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_NONE; #elif defined(G_OS_FREEBSD) -#define G_DEFAULT_SOUNDSYS G_SYS_API_PULSE +constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_PULSE; #elif defined(G_OS_WINDOWS) -#define G_DEFAULT_SOUNDSYS G_SYS_API_DS +constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_DS; #elif defined(G_OS_MAC) -#define G_DEFAULT_SOUNDSYS G_SYS_API_CORE +constexpr int G_DEFAULT_SOUNDSYS = G_SYS_API_CORE; #endif constexpr int G_DEFAULT_SOUNDDEV_OUT = -1; // disabled by default @@ -191,9 +192,9 @@ constexpr int G_RES_ERR = 0; constexpr int G_RES_OK = 1; /* -- log modes ------------------------------------------------------------- */ -constexpr int LOG_MODE_STDOUT = 0x01; -constexpr int LOG_MODE_FILE = 0x02; -constexpr int LOG_MODE_MUTE = 0x04; +constexpr int LOG_MODE_MUTE = 0; +constexpr int LOG_MODE_STDOUT = 1; +constexpr int LOG_MODE_FILE = 2; /* -- unique IDs of mainWin's subwindows ------------------------------------ */ /* -- wid > 0 are reserved by gg_keyboard ----------------------------------- */ @@ -273,11 +274,11 @@ constexpr int MIDI_EOX = 0xF7; // end of sysex /* midi sync constants */ -constexpr int MIDI_SYNC_NONE = 0x00; -constexpr int MIDI_SYNC_CLOCK_M = 0x01; // master -constexpr int MIDI_SYNC_CLOCK_S = 0x02; // slave -constexpr int MIDI_SYNC_MTC_M = 0x04; // master -constexpr int MIDI_SYNC_MTC_S = 0x08; // slave +constexpr int G_MIDI_SYNC_NONE = 0; +constexpr int G_MIDI_SYNC_CLOCK_M = 1; // master +constexpr int G_MIDI_SYNC_CLOCK_S = 2; // slave +constexpr int G_MIDI_SYNC_MTC_M = 3; // master +constexpr int G_MIDI_SYNC_MTC_S = 4; // slave /* JSON patch keys */ @@ -423,6 +424,7 @@ constexpr auto CONF_KEY_ACTION_EDITOR_Y = "action_editor_y"; constexpr auto CONF_KEY_ACTION_EDITOR_W = "action_editor_w"; constexpr auto CONF_KEY_ACTION_EDITOR_H = "action_editor_h"; constexpr auto CONF_KEY_ACTION_EDITOR_ZOOM = "action_editor_zoom"; +constexpr auto CONF_KEY_ACTION_EDITOR_SPLIT_H = "action_editor_split_h"; constexpr auto CONF_KEY_ACTION_EDITOR_GRID_VAL = "action_editor_grid_val"; constexpr auto CONF_KEY_ACTION_EDITOR_GRID_ON = "action_editor_grid_on"; constexpr auto CONF_KEY_SAMPLE_EDITOR_X = "sample_editor_x"; @@ -431,11 +433,7 @@ constexpr auto CONF_KEY_SAMPLE_EDITOR_W = "sample_editor_w"; constexpr auto CONF_KEY_SAMPLE_EDITOR_H = "sample_editor_h"; constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_VAL = "sample_editor_grid_val"; constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_ON = "sample_editor_grid_on"; -constexpr auto CONF_KEY_PIANO_ROLL_Y = "piano_roll_y"; -constexpr auto CONF_KEY_PIANO_ROLL_H = "piano_roll_h"; -constexpr auto CONF_KEY_SAMPLE_ACTION_EDITOR_H = "sample_action_editor_h"; -constexpr auto CONF_KEY_VELOCITY_EDITOR_H = "velocity_editor_h"; -constexpr auto CONF_KEY_ENVELOPE_EDITOR_H = "envelope_editor_h"; +constexpr auto CONF_KEY_ACTION_EDITOR_PIANO_ROLL_Y = "piano_roll_y"; constexpr auto CONF_KEY_PLUGIN_LIST_X = "plugin_list_x"; constexpr auto CONF_KEY_PLUGIN_LIST_Y = "plugin_list_y"; constexpr auto CONF_KEY_PLUGIN_CHOOSER_X = "plugin_chooser_x"; diff --git a/src/core/engine.cpp b/src/core/engine.cpp new file mode 100644 index 0000000..a71c863 --- /dev/null +++ b/src/core/engine.cpp @@ -0,0 +1,373 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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(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(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(kernelInfo.outBuf), kernelInfo.bufferSize, kernelInfo.channelsOutCount); + mcl::AudioBuffer in; + if (kernelInfo.channelsInCount > 0) + in = mcl::AudioBuffer(static_cast(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& w : model.getAllShared()) + { + w->setPath(makeUniqueWavePath(projectPath, *w, model.getAllShared())); + waveManager.save(*w, w->getPath()); // TODO - error checking + } + + /* Write Model into Patch, then into file. */ + + patch.data.name = projectName; + model::store(patch.data); + + if (!patch.write(patchPath)) + return false; + + /* Store the parent folder the project belongs to, in order to reuse it the + next time. */ + + conf.data.patchPath = u::fs::getUpDir(u::fs::getUpDir(patchPath)); + + u::log::print("[Engine::store] Project patch saved as %s\n", patchPath); + + return true; +} + +/* -------------------------------------------------------------------------- */ + +int Engine::load(const std::string& projectPath, const std::string& patchPath) +{ + u::log::print("[Engine::load] Load project from %s\n", projectPath); + + patch.reset(); + if (int res = patch.read(patchPath, projectPath); res != G_PATCH_OK) + return res; + + /* Then suspend Mixer, reset and fill the model. */ + + mixer.disable(); + reset(); + m::model::load(patch.data); + + /* Prepare the engine. Recorder has to recompute the actions positions if + the current samplerate != patch samplerate. Clock needs to update frames + in sequencer. */ + + mixerHandler.updateSoloCount(); + actionRecorder.updateSamplerate(kernelAudio.getSampleRate(), patch.data.samplerate); + sequencer.recomputeFrames(kernelAudio.getSampleRate()); + mixer.allocRecBuffer(sequencer.getMaxFramesInLoop(kernelAudio.getSampleRate())); + + /* Store the parent folder the project belongs to, in order to reuse it the + next time. */ + + conf.data.patchPath = u::fs::getUpDir(projectPath); + + /* Mixer is ready to go back online. */ + + mixer.enable(); + + return G_PATCH_OK; +} + +} // namespace giada::m \ No newline at end of file diff --git a/src/core/engine.h b/src/core/engine.h new file mode 100644 index 0000000..b081162 --- /dev/null +++ b/src/core/engine.h @@ -0,0 +1,126 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 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 diff --git a/src/core/eventDispatcher.cpp b/src/core/eventDispatcher.cpp index 6027fe4..e0c8939 100644 --- a/src/core/eventDispatcher.cpp +++ b/src/core/eventDispatcher.cpp @@ -24,92 +24,89 @@ * * -------------------------------------------------------------------------- */ -#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 +#include -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>(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(e.data).event); + break; + + case EventType::MIDI_DISPATCHER_PROCESS: + onMidiProcess(std::get(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 UIevents; -Queue 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 diff --git a/src/core/eventDispatcher.h b/src/core/eventDispatcher.h index 094c39c..b980537 100644 --- a/src/core/eventDispatcher.h +++ b/src/core/eventDispatcher.h @@ -27,72 +27,111 @@ #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 #include #include #include -/* 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>; + struct Event + { + using EventData = std::variant; -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; + + 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; + 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 UIevents; -extern Queue MidiEvents; + Queue UIevents; + Queue 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 onMidiLearn; + std::function onMidiProcess; + std::function onProcessChannels; + std::function onProcessSequencer; + std::function onMixerSignalCallback; + std::function 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 diff --git a/src/core/graphics.cpp b/src/core/graphics.cpp index 2715433..eb41137 100644 --- a/src/core/graphics.cpp +++ b/src/core/graphics.cpp @@ -272,6 +272,32 @@ const char* oneshotBasic_xpm[] = { "..................", ".................."}; +const char* oneshotBasicPause_xpm[] = { + "18 18 5 1", + " c #252525", + ". c #4E4E4E", + "+ c #767676", + "@ c #9F9F9F", + "# c #C8C8C8", + " ", + " ", + " ", + " +## ", + " +## ", + " +## ", + " +## ", + " +## ", + " +## ", + " +## ", + " +## ", + " +## ", + " +++++++++@## ", + " ############ ", + " ############ ", + " ", + " ", + " "}; + const char* oneshotRetrig_xpm[] = { "18 18 8 1", " c #181917", diff --git a/src/core/graphics.h b/src/core/graphics.h index ce3082a..6fb2c05 100644 --- a/src/core/graphics.h +++ b/src/core/graphics.h @@ -36,6 +36,7 @@ extern const char* loopBasic_xpm[]; extern const char* loopOnce_xpm[]; extern const char* loopOnceBar_xpm[]; extern const char* oneshotBasic_xpm[]; +extern const char* oneshotBasicPause_xpm[]; extern const char* oneshotRetrig_xpm[]; extern const char* oneshotPress_xpm[]; extern const char* oneshotEndless_xpm[]; diff --git a/src/core/idManager.cpp b/src/core/idManager.cpp index fc00142..2e34592 100644 --- a/src/core/idManager.cpp +++ b/src/core/idManager.cpp @@ -55,8 +55,15 @@ ID IdManager::generate(ID id) /* -------------------------------------------------------------------------- */ -ID IdManager::get() +ID IdManager::get() const { return m_id; } + +/* -------------------------------------------------------------------------- */ + +ID IdManager::getNext() const +{ + return m_id + 1; +} } // namespace giada::m diff --git a/src/core/idManager.h b/src/core/idManager.h index a25ccca..6fe9c6e 100644 --- a/src/core/idManager.h +++ b/src/core/idManager.h @@ -51,9 +51,15 @@ public: /* get Returns the current id, a.k.a. the last generated one. */ - ID get(); + ID get() const; - private: + /* getNext + Returns the upcoming ID, that is the ID that will be generated on the next + call. */ + + ID getNext() const; + +private: ID m_id; }; } // namespace giada::m diff --git a/src/core/init.cpp b/src/core/init.cpp index 1dee5aa..9e1504e 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -24,176 +24,52 @@ * * -------------------------------------------------------------------------- */ -#include -#include -#include #ifdef __APPLE__ #include #endif -#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_VST) -#include // 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 +#include +#include +#endif #include -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 args(argv, argv + argc); + if (args.size() > 1 && strcmp(args[1], "--run-tests") == 0) + return Catch::Session().run(args.size() - 1, &args[1]); + else + return -1; +#else + (void)argc; + (void)argv; + return -1; #endif } /* -------------------------------------------------------------------------- */ -void shutdownGUI_() -{ - u::gui::closeAllSubwindows(); - - u::log::print("[init] All subwindows and UI thread closed\n"); -} - -/* -------------------------------------------------------------------------- */ - -void printBuildInfo_() +void printBuildInfo() { u::log::print("[init] Giada %s\n", G_VERSION_STR); u::log::print("[init] Build date: " BUILD_DATE "\n"); @@ -213,80 +89,50 @@ void printBuildInfo_() #ifdef WITH_VST u::log::print("[init] JUCE - %d.%d.%d\n", JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER); #endif - kernelAudio::logCompiledAPIs(); + KernelAudio::logCompiledAPIs(); } -} // namespace -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ void startup(int argc, char** argv) { - printBuildInfo_(); +#ifdef WITH_VST + juce::initialiseJuce_GUI(); +#endif + g_engine.init(); + g_ui.init(argc, argv, g_engine); - initConf_(); - initSystem_(); - initAudio_(); - initMIDI_(); - initGUI_(argc, argv); + if (!g_engine.kernelAudio.isReady()) + v::gdAlert("Your soundcard isn't configured correctly.\n" + "Check the configuration and restart Giada."); } /* -------------------------------------------------------------------------- */ -void closeMainWindow() +int run() { - if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?")) - return; - - v::updater::close(); - G_MainWin->hide(); - delete G_MainWin; + Fl::lock(); // Enable multithreading in FLTK + return Fl::run(); } /* -------------------------------------------------------------------------- */ -void reset() +void closeMainWindow() { - u::gui::closeAllSubwindows(); - G_MainWin->clearKeyboard(); - - mh::close(); -#ifdef WITH_VST - pluginHost::close(); -#endif - - model::init(); - channelManager::init(); - waveManager::init(); - clock::init(conf::conf.samplerate, conf::conf.midiTCfps); - mh::init(); - sequencer::init(); - recorder::init(); -#ifdef WITH_VST - pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize()); -#endif - - u::gui::updateMainWinLabel(G_DEFAULT_PATCH_NAME); - u::gui::updateStaticWidgets(); + if (!v::gdConfirmWin("Warning", "Quit Giada: are you sure?")) + return; + shutdown(); } /* -------------------------------------------------------------------------- */ void shutdown() { - shutdownGUI_(); - - model::store(conf::conf); - - if (!conf::write()) - u::log::print("[init] error while saving configuration file!\n"); - else - u::log::print("[init] configuration saved\n"); - - shutdownAudio_(); - + g_ui.shutdown(); + g_engine.shutdown(); +#ifdef WITH_VST + juce::shutdownJuce_GUI(); +#endif u::log::print("[init] Giada %s closed\n\n", G_VERSION_STR); - u::log::close(); } } // namespace giada::m::init diff --git a/src/core/init.h b/src/core/init.h index 5370f6c..c29e6ef 100644 --- a/src/core/init.h +++ b/src/core/init.h @@ -29,8 +29,15 @@ namespace giada::m::init { +/* tests +Performs tests, if requested. Returns -1 if no tests are available or the +`--run-tests` has not been passed in. */ + +int tests(int argc, char** argv); + +void printBuildInfo(); void startup(int argc, char** argv); -void reset(); +int run(); void closeMainWindow(); void shutdown(); } // namespace giada::m::init diff --git a/src/core/jackTransport.cpp b/src/core/jackTransport.cpp new file mode 100644 index 0000000..45c1c7c --- /dev/null +++ b/src/core/jackTransport.cpp @@ -0,0 +1,147 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "jackTransport.h" +#ifdef WITH_AUDIO_JACK +#include +#include +#endif + +namespace giada::m +{ +bool JackTransport::State::operator!=(const State& o) const +{ + return !(running == o.running && bpm == o.bpm && frame == o.frame); +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +JackTransport::JackTransport() +#ifdef WITH_AUDIO_JACK +: m_jackHandle(nullptr) +#endif +{ +} + +/* -------------------------------------------------------------------------- */ + +bool JackTransport::start() const +{ +#ifdef WITH_AUDIO_JACK + if (m_jackHandle == nullptr) + return false; + jack_transport_start(m_jackHandle); + return true; +#else + return false; +#endif +} + +/* -------------------------------------------------------------------------- */ + +bool JackTransport::stop() const +{ +#ifdef WITH_AUDIO_JACK + if (m_jackHandle == nullptr) + return false; + jack_transport_stop(m_jackHandle); + return true; +#else + return false; +#endif +} + +/* -------------------------------------------------------------------------- */ + +bool JackTransport::setPosition(uint32_t frame) const +{ +#ifdef WITH_AUDIO_JACK + if (m_jackHandle == nullptr) + return false; + jack_position_t position; + jack_transport_query(m_jackHandle, &position); + position.frame = frame; + jack_transport_reposition(m_jackHandle, &position); + return true; +#else + (void)frame; + return false; +#endif +} + +/* -------------------------------------------------------------------------- */ + +bool JackTransport::setBpm(double bpm) const +{ +#ifdef WITH_AUDIO_JACK + if (m_jackHandle == nullptr) + return false; + jack_position_t position; + jack_transport_query(m_jackHandle, &position); + position.valid = jack_position_bits_t::JackPositionBBT; + position.bar = 0; // no such info from Giada + position.beat = 0; // no such info from Giada + position.tick = 0; // no such info from Giada + position.beats_per_minute = bpm; + jack_transport_reposition(m_jackHandle, &position); + return true; +#else + (void)bpm; + return false; +#endif +} + +/* -------------------------------------------------------------------------- */ + +JackTransport::State JackTransport::getState() const +{ +#ifdef WITH_AUDIO_JACK + if (m_jackHandle == nullptr) + return {}; + + jack_position_t position; + jack_transport_state_t ts = jack_transport_query(m_jackHandle, &position); + + return { + ts != JackTransportStopped, + position.beats_per_minute, + position.frame}; +#else + return {}; +#endif +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_AUDIO_JACK +void JackTransport::setHandle(jack_client_t* h) +{ + m_jackHandle = h; +} +#endif +} // namespace giada::m diff --git a/src/core/jackTransport.h b/src/core/jackTransport.h new file mode 100644 index 0000000..5e6f7f7 --- /dev/null +++ b/src/core/jackTransport.h @@ -0,0 +1,74 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_JACK_TRANSPORT_H +#define G_JACK_TRANSPORT_H + +#ifdef WITH_AUDIO_JACK +#include +#endif +#include + +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 diff --git a/src/core/kernelAudio.cpp b/src/core/kernelAudio.cpp index 2ac51ac..4a52d88 100644 --- a/src/core/kernelAudio.cpp +++ b/src/core/kernelAudio.cpp @@ -26,203 +26,65 @@ * * -------------------------------------------------------------------------- */ -#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 -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 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(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(info.outputChannels), - static_cast(info.inputChannels), - static_cast(info.duplexChannels), - info.isDefaultOutput, - info.isDefaultInput, - u::vector::cast(info.sampleRates)}; - } - catch (RtAudioError& e) - { - u::log::print("[KA] Error fetching device %d: %s\n", deviceIndex, e.getMessage()); - return {0}; - } } /* -------------------------------------------------------------------------- */ -std::vector fetchDevices_() +int KernelAudio::openDevice(const Conf::Data& conf) { - std::vector out; - for (unsigned i = 0; i < rtSystem_->getDeviceCount(); i++) - out.push_back(fetchDevice_(i)); - return out; -} + assert(onAudioCallback != nullptr); -/* -------------------------------------------------------------------------- */ - -void printDevices_(const std::vector& 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(outBuf), bufferSize, G_MAX_IO_CHANS); - AudioBuffer in; - if (isInputEnabled()) - in = AudioBuffer(static_cast(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::UNIX_JACK); + else if (m_api == G_SYS_API_ALSA && hasAPI(RtAudio::LINUX_ALSA)) + m_rtAudio = std::make_unique(RtAudio::LINUX_ALSA); + else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE)) + m_rtAudio = std::make_unique(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::UNIX_JACK); + else if (m_api == G_SYS_API_PULSE && hasAPI(RtAudio::LINUX_PULSE)) + m_rtAudio = std::make_unique(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::WINDOWS_DS); + else if (m_api == G_SYS_API_ASIO && hasAPI(RtAudio::WINDOWS_ASIO)) + m_rtAudio = std::make_unique(RtAudio::WINDOWS_ASIO); + else if (m_api == G_SYS_API_WASAPI && hasAPI(RtAudio::WINDOWS_WASAPI)) + m_rtAudio = std::make_unique(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::MACOSX_CORE); #endif @@ -232,15 +94,19 @@ int openDevice() return 0; } + m_rtAudio->setErrorCallback([](RtAudioErrorType type, const std::string& msg) { + u::log::print("[KA] RtAudio error %d: %s\n", type, msg.c_str()); + }); + u::log::print("[KA] Opening device out=%d, in=%d, samplerate=%d\n", - conf::conf.soundDeviceOut, conf::conf.soundDeviceIn, conf::conf.samplerate); + conf.soundDeviceOut, conf.soundDeviceIn, conf.samplerate); - devices_ = fetchDevices_(); - printDevices_(devices_); + m_devices = fetchDevices(); + printDevices(m_devices); /* Abort here if devices found are zero. */ - if (devices_.size() == 0) + if (m_devices.size() == 0) { closeDevice(); return 0; @@ -249,59 +115,76 @@ int openDevice() RtAudio::StreamParameters outParams; RtAudio::StreamParameters inParams; - outParams.deviceId = conf::conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? rtSystem_->getDefaultOutputDevice() : conf::conf.soundDeviceOut; - outParams.nChannels = conf::conf.channelsOutCount; - outParams.firstChannel = conf::conf.channelsOutStart; + outParams.deviceId = conf.soundDeviceOut == G_DEFAULT_SOUNDDEV_OUT ? m_rtAudio->getDefaultOutputDevice() : conf.soundDeviceOut; + outParams.nChannels = conf.channelsOutCount; + outParams.firstChannel = conf.channelsOutStart; /* Input device can be disabled. Unlike the output, here we are using all channels and let the user choose which one to record from in the configuration panel. */ - if (conf::conf.soundDeviceIn != -1) + if (conf.soundDeviceIn != -1) { - inParams.deviceId = conf::conf.soundDeviceIn; - inParams.nChannels = conf::conf.channelsInCount; - inParams.firstChannel = conf::conf.channelsInStart; - inputEnabled_ = true; + inParams.deviceId = conf.soundDeviceIn; + inParams.nChannels = conf.channelsInCount; + inParams.firstChannel = conf.channelsInStart; + m_inputEnabled = true; } else - inputEnabled_ = false; + m_inputEnabled = false; RtAudio::StreamOptions options; options.streamName = G_APP_NAME; options.numberOfBuffers = 4; // TODO - wtf? - realBufsize_ = conf::conf.buffersize; + m_realBufferSize = conf.buffersize; + m_realSampleRate = conf.samplerate; + m_channelsOutCount = conf.channelsOutCount; + m_channelsInCount = conf.channelsInCount; #ifdef WITH_AUDIO_JACK - if (api_ == G_SYS_API_JACK) + /* If JACK, use its own sample rate, not the one coming from the conf + object. */ + + if (m_api == G_SYS_API_JACK) { - conf::conf.samplerate = devices_[conf::conf.soundDeviceOut].sampleRates[0]; - u::log::print("[KA] JACK in use, samplerate=%d\n", conf::conf.samplerate); + assert(m_devices.size() > 0); + assert(m_devices[0].sampleRates.size() > 0); + + m_realSampleRate = m_devices[0].sampleRates[0]; + u::log::print("[KA] JACK in use, samplerate=%d\n", m_realSampleRate); } #endif - try + m_callbackInfo = { + /* kernelAudio = */ this, + /* ready = */ true, + /* withJack = */ getAPI() == G_SYS_API_JACK, + /* outBuf = */ nullptr, // filled later on in audio callback + /* inBuf = */ nullptr, // filled later on in audio callback + /* bufferSize = */ 0, // filled later on in audio callback + /* channelsOutCount = */ m_channelsOutCount, + /* channelsInCount = */ m_channelsInCount}; + + RtAudioErrorType res = m_rtAudio->openStream( + &outParams, // output params + conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected + RTAUDIO_FLOAT32, // audio format + m_realSampleRate, // sample rate + &m_realBufferSize, // buffer size in byte + &audioCallback, // audio callback + &m_callbackInfo, // user data passed to callback + &options); + + if (res == RtAudioErrorType::RTAUDIO_NO_ERROR) { - rtSystem_->openStream( - &outParams, // output params - conf::conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected - RTAUDIO_FLOAT32, // audio format - conf::conf.samplerate, // sample rate - &realBufsize_, // buffer size in byte - &callback_, // audio callback - nullptr, // user data (unused) - &options); - - model::get().kernel.audioReady = true; - model::swap(model::SwapType::NONE); + m_ready = true; return 1; } - catch (RtAudioError& e) + else { - u::log::print("[KA] rtSystem_ init error: %s\n", e.getMessage()); closeDevice(); return 0; } @@ -309,61 +192,59 @@ int openDevice() /* -------------------------------------------------------------------------- */ -int startStream() +int KernelAudio::startStream() { - try + if (m_rtAudio->startStream() == RtAudioErrorType::RTAUDIO_NO_ERROR) { - rtSystem_->startStream(); - u::log::print("[KA] latency = %lu\n", rtSystem_->getStreamLatency()); + u::log::print("[KA] Start stream - latency = %lu\n", m_rtAudio->getStreamLatency()); return 1; } - catch (RtAudioError& e) - { - u::log::print("[KA] Start stream error: %s\n", e.getMessage()); - return 0; - } + return 0; } /* -------------------------------------------------------------------------- */ -int stopStream() +int KernelAudio::stopStream() { - try + if (m_rtAudio->stopStream() == RtAudioErrorType::RTAUDIO_NO_ERROR) { - rtSystem_->stopStream(); + u::log::print("[KA] Stop stream\n"); return 1; } - catch (RtAudioError& /*e*/) - { - u::log::print("[KA] Stop stream error\n"); - return 0; - } + return 0; } /* -------------------------------------------------------------------------- */ -int closeDevice() +void KernelAudio::closeDevice() { - if (rtSystem_->isStreamOpen()) - { - rtSystem_->stopStream(); - rtSystem_->closeStream(); - delete rtSystem_; - rtSystem_ = nullptr; - } - return 1; + if (!m_rtAudio->isStreamOpen()) + return; + m_rtAudio->stopStream(); + m_rtAudio->closeStream(); + m_rtAudio.reset(nullptr); +} + +/* -------------------------------------------------------------------------- */ + +bool KernelAudio::isReady() const +{ + return m_ready; } /* -------------------------------------------------------------------------- */ -unsigned getRealBufSize() { return realBufsize_; } -bool isInputEnabled() { return inputEnabled_; } +int KernelAudio::getBufferSize() const { return static_cast(m_realBufferSize); } +int KernelAudio::getSampleRate() const { return m_realSampleRate; } +int KernelAudio::getChannelsOutCount() const { return m_channelsOutCount; } +int KernelAudio::getChannelsInCount() const { return m_channelsInCount; } +bool KernelAudio::isInputEnabled() const { return m_inputEnabled; } /* -------------------------------------------------------------------------- */ -Device getDevice(const char* name) +m::KernelAudio::Device KernelAudio::getDevice(const char* name) const { - for (Device device : devices_) + for (Device device : m_devices) if (name == device.name) return device; return {0, false}; @@ -371,14 +252,23 @@ Device getDevice(const char* name) /* -------------------------------------------------------------------------- */ -const std::vector& getDevices() +const std::vector& 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(m_rtAudio->HACK__getJackClient()); +} +#endif + +/* -------------------------------------------------------------------------- */ + +bool KernelAudio::hasAPI(int API) const { std::vector APIs; RtAudio::getCompiledApi(APIs); @@ -388,20 +278,20 @@ bool hasAPI(int API) return false; } -int getAPI() { return api_; } +int KernelAudio::getAPI() const { return m_api; } /* -------------------------------------------------------------------------- */ -void logCompiledAPIs() +void KernelAudio::logCompiledAPIs() { std::vector APIs; RtAudio::getCompiledApi(APIs); u::log::print("[KA] Compiled RtAudio APIs: %d\n", APIs.size()); - for (const RtAudio::Api& api_ : APIs) + for (const RtAudio::Api& m_api : APIs) { - switch (api_) + switch (m_api) { case RtAudio::Api::LINUX_ALSA: u::log::print(" ALSA\n"); @@ -436,65 +326,64 @@ void logCompiledAPIs() /* -------------------------------------------------------------------------- */ -#ifdef WITH_AUDIO_JACK - -JackState jackTransportQuery() +m::KernelAudio::Device KernelAudio::fetchDevice(size_t deviceIndex) const { - if (api_ != G_SYS_API_JACK) - return {}; + RtAudio::DeviceInfo info = m_rtAudio->getDeviceInfo(deviceIndex); - jack_position_t position; - jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position); + if (!info.probed) + { + u::log::print("[KA] Can't probe device %d\n", deviceIndex); + return {deviceIndex}; + } return { - ts != JackTransportStopped, - position.beats_per_minute, - position.frame}; -} - -/* -------------------------------------------------------------------------- */ - -void jackStart() -{ - if (api_ == G_SYS_API_JACK) - jack_transport_start(jackGetHandle_()); + deviceIndex, + true, + info.name, + static_cast(info.outputChannels), + static_cast(info.inputChannels), + static_cast(info.duplexChannels), + info.isDefaultOutput, + info.isDefaultInput, + u::vector::cast(info.sampleRates)}; } /* -------------------------------------------------------------------------- */ -void jackSetPosition(uint32_t frame) +std::vector 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 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& 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(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 diff --git a/src/core/kernelAudio.h b/src/core/kernelAudio.h index 498eeba..706d662 100644 --- a/src/core/kernelAudio.h +++ b/src/core/kernelAudio.h @@ -27,66 +27,95 @@ #ifndef G_KERNELAUDIO_H #define G_KERNELAUDIO_H -#include +#include "core/conf.h" +#include "deps/rtaudio/RtAudio.h" +#include +#include #include #include #ifdef WITH_AUDIO_JACK -#include -#include -#include +#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 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 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& 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& getDevices() const; #ifdef WITH_AUDIO_JACK + jack_client_t* getJackHandle() const; +#endif + + /* onAudioCallback + Main callback invoked on each audio block. */ + + std::function 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 fetchDevices() const; + void printDevices(const std::vector& devices) const; + +#ifdef WITH_AUDIO_JACK + JackTransport m_jackTransport; #endif -} // namespace giada::m::kernelAudio + std::vector m_devices; + std::unique_ptr 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 diff --git a/src/core/kernelMidi.cpp b/src/core/kernelMidi.cpp index 93306b0..254cee8 100644 --- a/src/core/kernelMidi.cpp +++ b/src/core/kernelMidi.cpp @@ -24,55 +24,33 @@ * * -------------------------------------------------------------------------- */ -#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 +#include -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* msg, void* /*data*/) +constexpr auto OUTPUT_NAME = "Giada MIDI output"; +constexpr auto INPUT_NAME = "Giada MIDI input"; + +/* -------------------------------------------------------------------------- */ + +std::vector 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; isize(); 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((iValue >> 24) & 0xFF), + static_cast((iValue >> 16) & 0xFF), + static_cast((iValue >> 8) & 0xFF)}; } /* -------------------------------------------------------------------------- */ -void sendMidiLightningInitMsgs_() +uint32_t join_(int b1, int b2, int b3) { - for (const midimap::Message& m : midimap::midimap.initCommands) - { - if (m.value != 0x0 && m.channel != -1) - { - u::log::print("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", m.channel, m.value); - MidiEvent e(m.value); - e.setChannel(m.channel); - send(e.getRaw()); - } - } + return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00); } } // namespace @@ -80,110 +58,52 @@ void sendMidiLightningInitMsgs_() /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void setApi(int api) +KernelMidi::KernelMidi() +: onMidiReceived(nullptr) { - api_ = api; - u::log::print("[KM] using system 0x%x\n", api_); } /* -------------------------------------------------------------------------- */ -int openOutDevice(int port) +bool KernelMidi::openOutDevice(int api, int port) { - try - { - midiOut_ = new RtMidiOut((RtMidi::Api)api_, "Giada MIDI Output"); - status_ = true; - } - catch (RtMidiError& error) - { - u::log::print("[KM] MIDI out device error: %s\n", error.getMessage()); - status_ = false; - return 0; - } - - /* print output ports */ - - numOutPorts_ = midiOut_->getPortCount(); - u::log::print("[KM] %d output MIDI ports found\n", numOutPorts_); - for (unsigned i = 0; i < numOutPorts_; i++) - u::log::print(" %d) %s\n", i, getOutPortName(i)); + if (port == -1) + return false; - /* try to open a port, if enabled */ - - if (port != -1 && numOutPorts_ > 0) - { - try - { - midiOut_->openPort(port, getOutPortName(port)); - u::log::print("[KM] MIDI out port %d open\n", port); - - /* TODO - it shold send midiLightning message only if there is a map loaded - and available in midimap:: */ - - sendMidiLightningInitMsgs_(); - return 1; - } - catch (RtMidiError& error) - { - u::log::print("[KM] unable to open MIDI out port %d: %s\n", port, error.getMessage()); - status_ = false; - return 0; - } - } - else - return 2; + m_midiOut = makeDevice(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(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 APIs; RtMidi::getCompiledApi(APIs); @@ -195,50 +115,27 @@ bool hasAPI(int API) /* -------------------------------------------------------------------------- */ -std::string getOutPortName(unsigned p) -{ - try - { - return midiOut_->getPortName(p); - } - catch (RtMidiError& /*error*/) - { - return ""; - } -} - -std::string getInPortName(unsigned p) -{ - try - { - return midiIn_->getPortName(p); - } - catch (RtMidiError& /*error*/) - { - return ""; - } -} +std::string KernelMidi::getOutPortName(unsigned p) const { return getPortName(*m_midiOut, p); } +std::string KernelMidi::getInPortName(unsigned p) const { return getPortName(*m_midiIn, p); } /* -------------------------------------------------------------------------- */ -void send(uint32_t data) +void KernelMidi::send(uint32_t data) { - if (!status_) + if (m_midiOut == nullptr) return; - std::vector msg(1, getB1(data)); - msg.push_back(getB2(data)); - msg.push_back(getB3(data)); + std::vector 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 msg(1, b1); @@ -248,53 +145,78 @@ void send(int b1, int b2, int b3) if (b3 != -1) msg.push_back(b3); - midiOut_->sendMessage(&msg); + m_midiOut->sendMessage(&msg); u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3); } /* -------------------------------------------------------------------------- */ -void sendMidiLightning(uint32_t learnt, const midimap::Message& m) +unsigned KernelMidi::countOutPorts() const { return m_midiOut != nullptr ? m_midiOut->getPortCount() : 0; } +unsigned KernelMidi::countInPorts() const { return m_midiIn != nullptr ? m_midiIn->getPortCount() : 0; } + +/* -------------------------------------------------------------------------- */ + +void KernelMidi::s_callback(double /*t*/, std::vector* msg, void* data) { - // Skip lightning message if not defined in midi map + static_cast(data)->callback(msg); +} + +/* -------------------------------------------------------------------------- */ - if (!midimap::isDefined(m)) +void KernelMidi::callback(std::vector* 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 +std::unique_ptr KernelMidi::makeDevice(int api, int port, std::string name) const +{ + try + { + auto device = std::make_unique(static_cast(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 KernelMidi::makeDevice(int, int, std::string) const; +template std::unique_ptr 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 diff --git a/src/core/kernelMidi.h b/src/core/kernelMidi.h index e3f2dfd..5489a22 100644 --- a/src/core/kernelMidi.h +++ b/src/core/kernelMidi.h @@ -27,63 +27,62 @@ #ifndef G_KERNELMIDI_H #define G_KERNELMIDI_H -#include "midiMapConf.h" +#include "midiMapper.h" +#include #include +#include +#include #include -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 onMidiReceived; -std::string getInPortName(unsigned p); -std::string getOutPortName(unsigned p); +private: + template + std::unique_ptr makeDevice(int api, int port, std::string name) const; -unsigned countInPorts(); -unsigned countOutPorts(); + static void s_callback(double, std::vector*, void*); + void callback(std::vector*); -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 m_midiOut; + std::unique_ptr m_midiIn; +}; +} // namespace giada::m #endif diff --git a/src/core/metronome.cpp b/src/core/metronome.cpp index 4220777..1ed5cd7 100644 --- a/src/core/metronome.cpp +++ b/src/core/metronome.cpp @@ -25,7 +25,7 @@ * -------------------------------------------------------------------------- */ #include "metronome.h" -#include "core/audioBuffer.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" namespace giada::m { @@ -38,7 +38,7 @@ void Metronome::trigger(Click c, Frame o) /* -------------------------------------------------------------------------- */ -void Metronome::render(AudioBuffer& outBuf) +void Metronome::render(mcl::AudioBuffer& outBuf) { const float* data = m_click == Click::BEAT ? beat : bar; for (Frame f = m_offset; f < outBuf.countFrames() && m_rendering; f++) diff --git a/src/core/metronome.h b/src/core/metronome.h index 9a6986d..7c8c2a7 100644 --- a/src/core/metronome.h +++ b/src/core/metronome.h @@ -29,9 +29,12 @@ #include "core/types.h" -namespace giada::m +namespace mcl { class AudioBuffer; +} +namespace giada::m +{ class Metronome { public: @@ -41,12 +44,12 @@ public: BAR }; - void render(AudioBuffer& outBuf); + void render(mcl::AudioBuffer& outBuf); void trigger(Click c, Frame o); bool running = false; - private: +private: static constexpr Frame CLICK_SIZE = 38; static constexpr float beat[CLICK_SIZE] = { diff --git a/src/core/midiDispatcher.cpp b/src/core/midiDispatcher.cpp index 559894a..239e234 100644 --- a/src/core/midiDispatcher.cpp +++ b/src/core/midiDispatcher.cpp @@ -31,7 +31,7 @@ #include "core/model/model.h" #include "core/plugins/plugin.h" #include "core/plugins/pluginHost.h" -#include "core/recManager.h" +#include "core/recorder.h" #include "core/types.h" #include "glue/events.h" #include "glue/plugin.h" @@ -40,38 +40,129 @@ #include #include -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 f) +{ + m_learnCb = [=](m::MidiEvent e) { learnChannel(e, param, channelId, f); }; +} + +void MidiDispatcher::startMasterLearn(int param, std::function f) +{ + m_learnCb = [=](m::MidiEvent e) { learnMaster(e, param, f); }; +} -std::function signalCb_ = nullptr; -std::function learnCb_ = nullptr; +#ifdef WITH_VST + +void MidiDispatcher::startPluginLearn(std::size_t paramIndex, ID pluginId, std::function 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 f) +{ + learnMaster(MidiEvent(), param, f); // Empty event (0x0) +} + +void MidiDispatcher::clearChannelLearn(int param, ID channelId, std::function f) +{ + learnChannel(MidiEvent(), param, channelId, f); // Empty event (0x0) +} + +#ifdef WITH_VST + +void MidiDispatcher::clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function 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& plugins, const MidiEvent& midiEvent) +void MidiDispatcher::processPlugins(const std::vector& plugins, const MidiEvent& midiEvent) { uint32_t pure = midiEvent.getRawNoVelocity(); float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, 1.0f); @@ -98,11 +189,11 @@ void processPlugins_(const std::vector& plugins, const MidiEvent& midiE /* -------------------------------------------------------------------------- */ -void processChannels_(const MidiEvent& midiEvent) +void MidiDispatcher::processChannels(const MidiEvent& midiEvent) { uint32_t pure = midiEvent.getRawNoVelocity(); - for (const channel::Data& c : model::get().channels) + for (const Channel& c : m_model.get().channels) { /* Do nothing on this channel if MIDI in is disabled or filtered out for @@ -162,7 +253,7 @@ void processChannels_(const MidiEvent& midiEvent) #ifdef WITH_VST /* Process learned plugins parameters. */ - processPlugins_(c.plugins, midiEvent); + processPlugins(c.plugins, midiEvent); #endif /* Redirect raw MIDI message (pure + velocity) to plug-ins in armed @@ -174,10 +265,10 @@ void processChannels_(const MidiEvent& midiEvent) /* -------------------------------------------------------------------------- */ -void processMaster_(const MidiEvent& midiEvent) +void MidiDispatcher::processMaster(const MidiEvent& midiEvent) { const uint32_t pure = midiEvent.getRawNoVelocity(); - const model::MidiIn& midiIn = model::get().midiIn; + const model::MidiIn& midiIn = m_model.get().midiIn; if (pure == midiIn.rewind) { @@ -232,14 +323,14 @@ void processMaster_(const MidiEvent& midiEvent) /* -------------------------------------------------------------------------- */ -void learnChannel_(MidiEvent e, int param, ID channelId, std::function doneCb) +void MidiDispatcher::learnChannel(MidiEvent e, int param, ID channelId, std::function doneCb) { - if (!isChannelMidiInAllowed_(channelId, e.getChannel())) + if (!isChannelMidiInAllowed(channelId, e.getChannel())) return; uint32_t raw = e.getRawNoVelocity(); - channel::Data& ch = model::get().getChannel(channelId); + Channel& ch = m_model.get().getChannel(channelId); switch (param) { @@ -281,15 +372,17 @@ void learnChannel_(MidiEvent e, int param, ID channelId, std::function d break; } - model::swap(model::SwapType::SOFT); + m_model.swap(model::SwapType::SOFT); stopLearn(); doneCb(); } -void learnMaster_(MidiEvent e, int param, std::function doneCb) +/* -------------------------------------------------------------------------- */ + +void MidiDispatcher::learnMaster(MidiEvent e, int param, std::function doneCb) { - if (!isMasterMidiInAllowed_(e.getChannel())) + if (!isMasterMidiInAllowed(e.getChannel())) return; uint32_t raw = e.getRawNoVelocity(); @@ -297,47 +390,48 @@ void learnMaster_(MidiEvent e, int param, std::function doneCb) switch (param) { case G_MIDI_IN_REWIND: - model::get().midiIn.rewind = raw; + m_model.get().midiIn.rewind = raw; break; case G_MIDI_IN_START_STOP: - model::get().midiIn.startStop = raw; + m_model.get().midiIn.startStop = raw; break; case G_MIDI_IN_ACTION_REC: - model::get().midiIn.actionRec = raw; + m_model.get().midiIn.actionRec = raw; break; case G_MIDI_IN_INPUT_REC: - model::get().midiIn.inputRec = raw; + m_model.get().midiIn.inputRec = raw; break; case G_MIDI_IN_METRONOME: - model::get().midiIn.metronome = raw; + m_model.get().midiIn.metronome = raw; break; case G_MIDI_IN_VOLUME_IN: - model::get().midiIn.volumeIn = raw; + m_model.get().midiIn.volumeIn = raw; break; case G_MIDI_IN_VOLUME_OUT: - model::get().midiIn.volumeOut = raw; + m_model.get().midiIn.volumeOut = raw; break; case G_MIDI_IN_BEAT_DOUBLE: - model::get().midiIn.beatDouble = raw; + m_model.get().midiIn.beatDouble = raw; break; case G_MIDI_IN_BEAT_HALF: - model::get().midiIn.beatHalf = raw; + m_model.get().midiIn.beatHalf = raw; break; } - model::swap(model::SwapType::SOFT); + m_model.swap(model::SwapType::SOFT); stopLearn(); doneCb(); } +/* -------------------------------------------------------------------------- */ + #ifdef WITH_VST -void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function doneCb) +void MidiDispatcher::learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function doneCb) { - model::DataLock lock(model::SwapType::NONE); - - Plugin* plugin = model::find(pluginId); + model::DataLock lock = m_model.lockData(model::SwapType::NONE); + Plugin* plugin = m_model.findShared(pluginId); assert(plugin != nullptr); assert(paramIndex < plugin->midiInParams.size()); @@ -349,108 +443,4 @@ void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::functio } #endif - -/* -------------------------------------------------------------------------- */ - -void triggerSignalCb_() -{ - if (signalCb_ == nullptr) - return; - signalCb_(); - signalCb_ = nullptr; -} -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void startChannelLearn(int param, ID channelId, std::function f) -{ - learnCb_ = [=](m::MidiEvent e) { learnChannel_(e, param, channelId, f); }; -} - -void startMasterLearn(int param, std::function f) -{ - learnCb_ = [=](m::MidiEvent e) { learnMaster_(e, param, f); }; -} - -#ifdef WITH_VST - -void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function f) -{ - learnCb_ = [=](m::MidiEvent e) { learnPlugin_(e, paramIndex, pluginId, f); }; -} - -#endif - -void stopLearn() -{ - learnCb_ = nullptr; -} - -/* -------------------------------------------------------------------------- */ - -void clearMasterLearn(int param, std::function f) -{ - learnMaster_(MidiEvent(), param, f); // Empty event (0x0) -} - -void clearChannelLearn(int param, ID channelId, std::function f) -{ - learnChannel_(MidiEvent(), param, channelId, f); // Empty event (0x0) -} - -#ifdef WITH_VST - -void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function 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 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 f) -{ - signalCb_ = f; -} -} // namespace giada::m::midiDispatcher +} // namespace giada::m diff --git a/src/core/midiDispatcher.h b/src/core/midiDispatcher.h index 531aef7..b7d0a1d 100644 --- a/src/core/midiDispatcher.h +++ b/src/core/midiDispatcher.h @@ -27,27 +27,80 @@ #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 #include -namespace giada::m::midiDispatcher +namespace giada::m { -void startChannelLearn(int param, ID channelId, std::function f); -void startMasterLearn(int param, std::function f); -void stopLearn(); -void clearMasterLearn(int param, std::function f); -void clearChannelLearn(int param, ID channelId, std::function f); +class MidiDispatcher +{ +public: + MidiDispatcher(model::Model&); + + void startChannelLearn(int param, ID channelId, std::function f); + void startMasterLearn(int param, std::function f); + void stopLearn(); + void clearMasterLearn(int param, std::function f); + void clearChannelLearn(int param, ID channelId, std::function f); #ifdef WITH_VST -void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function f); -void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function f); + void startPluginLearn(std::size_t paramIndex, ID pluginId, std::function f); + void clearPluginLearn(std::size_t paramIndex, ID pluginId, std::function 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 onDispatch; + + /* onEventReceived + Callback fired when a MIDI event has been received (passed in by the Event + Dispatcher). */ + + std::function 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 doneCb); + void learnMaster(MidiEvent e, int param, std::function doneCb); + +#ifdef WITH_VST + void processPlugins(const std::vector& plugins, const MidiEvent& midiEvent); + void learnPlugin(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function 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 m_learnCb; -void setSignalCallback(std::function f); -} // namespace giada::m::midiDispatcher + model::Model& m_model; +}; +} // namespace giada::m #endif diff --git a/src/core/midiEvent.cpp b/src/core/midiEvent.cpp index 4607111..22aef56 100644 --- a/src/core/midiEvent.cpp +++ b/src/core/midiEvent.cpp @@ -42,27 +42,27 @@ constexpr int FLOAT_FACTOR = 10000; /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -MidiEvent::MidiEvent(uint32_t raw) +MidiEvent::MidiEvent(uint32_t raw, int delta) : m_status((raw & 0xF0000000) >> 24) , m_channel((raw & 0x0F000000) >> 24) , m_note((raw & 0x00FF0000) >> 16) , m_velocity((raw & 0x0000FF00) >> 8) -, m_delta(0) // not used +, m_delta(delta) { } /* -------------------------------------------------------------------------- */ /* static_cast to avoid ambiguity with MidiEvent(float). */ -MidiEvent::MidiEvent(int byte1, int byte2, int byte3) -: MidiEvent(static_cast((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00))) +MidiEvent::MidiEvent(int byte1, int byte2, int byte3, int delta) +: MidiEvent(static_cast((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(v * FLOAT_FACTOR); } diff --git a/src/core/midiEvent.h b/src/core/midiEvent.h index dfdede8..48e967a 100644 --- a/src/core/midiEvent.h +++ b/src/core/midiEvent.h @@ -46,14 +46,14 @@ public: MidiEvent() = default; - MidiEvent(uint32_t raw); - MidiEvent(int byte1, int byte2, int byte3); + MidiEvent(uint32_t raw, int delta = 0); + MidiEvent(int byte1, int byte2, int byte3, int delta = 0); /* MidiEvent (4) A constructor that takes a float parameter. Useful to build ENVELOPE events for automations, volume and pitch. */ - MidiEvent(float v); + MidiEvent(float v, int delta = 0); int getStatus() const; int getChannel() const; @@ -83,7 +83,7 @@ public: void fixVelocityZero(); - private: +private: int m_status; int m_channel; int m_note; diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp deleted file mode 100644 index 240a8de..0000000 --- a/src/core/midiMapConf.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 -#include -#include -#include -#include - -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 maps; - -/* -------------------------------------------------------------------------- */ - -void init() -{ - midimapsPath = u::fs::getHomePath() + G_SLASH + "midimaps" + G_SLASH; - - /* scan dir of midi maps and load the filenames into <>maps. */ - - u::log::print("[midiMapConf::init] scanning midimaps directory '%s'...\n", - midimapsPath); - - if (!std::filesystem::exists(midimapsPath)) - { - u::log::print("[midiMapConf::init] unable to scan midimaps directory!\n"); - return; - } - - for (const auto& d : std::filesystem::directory_iterator(midimapsPath)) - { - // TODO - check if is a valid midimap file (verify headers) - if (!d.is_regular_file()) - continue; - u::log::print("[midiMapConf::init] found midimap '%s'\n", d.path().filename().string()); - maps.push_back(d.path().filename().string()); - } - - u::log::print("[midiMapConf::init] total midimaps found: %d\n", maps.size()); -} - -/* -------------------------------------------------------------------------- */ - -void setDefault() -{ - midimap = MidiMap(); -} - -/* -------------------------------------------------------------------------- */ - -bool isDefined(const Message& m) -{ - return m.offset != -1; -} - -/* -------------------------------------------------------------------------- */ - -int read(const std::string& file) -{ - if (file.empty()) - { - u::log::print("[midiMapConf::read] midimap not specified, nothing to do\n"); - return MIDIMAP_NOT_SPECIFIED; - } - - u::log::print("[midiMapConf::read] reading midimap file '%s'\n", file); - - std::ifstream ifs(midimapsPath + file); - if (!ifs.good()) - return MIDIMAP_UNREADABLE; - - nl::json j = nl::json::parse(ifs); - - midimap.brand = j[MIDIMAP_KEY_BRAND]; - midimap.device = j[MIDIMAP_KEY_DEVICE]; - - if (!readInitCommands_(j)) - return MIDIMAP_UNREADABLE; - if (readCommand_(j, midimap.muteOn, MIDIMAP_KEY_MUTE_ON)) - parse_(midimap.muteOn); - if (readCommand_(j, midimap.muteOff, MIDIMAP_KEY_MUTE_OFF)) - parse_(midimap.muteOff); - if (readCommand_(j, midimap.soloOn, MIDIMAP_KEY_SOLO_ON)) - parse_(midimap.soloOn); - if (readCommand_(j, midimap.soloOff, MIDIMAP_KEY_SOLO_OFF)) - parse_(midimap.soloOff); - if (readCommand_(j, midimap.waiting, MIDIMAP_KEY_WAITING)) - parse_(midimap.waiting); - if (readCommand_(j, midimap.playing, MIDIMAP_KEY_PLAYING)) - parse_(midimap.playing); - if (readCommand_(j, midimap.stopping, MIDIMAP_KEY_STOPPING)) - parse_(midimap.stopping); - if (readCommand_(j, midimap.stopped, MIDIMAP_KEY_STOPPED)) - parse_(midimap.stopped); - if (readCommand_(j, midimap.playingInaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE)) - parse_(midimap.playingInaudible); - - return MIDIMAP_READ_OK; -} -} // namespace midimap -} // namespace m -} // namespace giada diff --git a/src/core/midiMapConf.h b/src/core/midiMapConf.h deleted file mode 100644 index 546d2a0..0000000 --- a/src/core/midiMapConf.h +++ /dev/null @@ -1,106 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_MIDIMAPCONF_H -#define G_MIDIMAPCONF_H - -#include -#include - -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 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 maps; - -/* -------------------------------------------------------------------------- */ - -/* init -Parses the midi maps folders and find the available maps. */ - -void init(); - -/* setDefault -Sets default values in case no maps are available/chosen. */ - -void setDefault(); - -/* isDefined -Checks whether a specific message has been defined within midi map file. */ - -bool isDefined(const Message& msg); - -/* read -Reads a midi map from file 'file'. */ - -int read(const std::string& file); -} // namespace midimap -} // namespace m -} // namespace giada - -#endif diff --git a/src/core/midiMapper.cpp b/src/core/midiMapper.cpp new file mode 100644 index 0000000..a11bd38 --- /dev/null +++ b/src/core/midiMapper.cpp @@ -0,0 +1,278 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 +#include +#include +#include +#include +#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 +MidiMapper::MidiMapper(KernelMidiI& k) +: m_kernelMidi(k) +{ +} + +/* -------------------------------------------------------------------------- */ + +template +const std::vector& MidiMapper::getMapFilesFound() const +{ + return m_mapFiles; +} + +/* -------------------------------------------------------------------------- */ + +template +void MidiMapper::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 +int MidiMapper::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 +bool MidiMapper::isMessageDefined(const MidiMap::Message& m) const +{ + return m.offset != -1; +} + +/* -------------------------------------------------------------------------- */ + +template +void MidiMapper::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 +void MidiMapper::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 +void MidiMapper::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 +bool MidiMapper::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 +bool MidiMapper::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; +#ifdef WITH_TESTS +template class MidiMapper; +#endif +} // namespace giada::m \ No newline at end of file diff --git a/src/core/midiMapper.h b/src/core/midiMapper.h new file mode 100644 index 0000000..694ffe5 --- /dev/null +++ b/src/core/midiMapper.h @@ -0,0 +1,148 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_MIDIMAPPER_H +#define G_MIDIMAPPER_H + +#include "deps/json/single_include/nlohmann/json.hpp" +#include +#include + +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 initCommands; + Message muteOn; + Message muteOff; + Message soloOn; + Message soloOff; + Message waiting; + Message playing; + Message stopping; + Message stopped; + Message playingInaudible; +}; + +template +class MidiMapper final +{ +public: + MidiMapper(KernelMidiI&); + + /* getMapFilesFound + Returns a reference to the list of midimaps found. */ + + const std::vector& 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 m_mapFiles; +}; + +extern template class MidiMapper; +#ifdef WITH_TESTS +extern template class MidiMapper; +#endif +} // namespace giada::m + +#endif diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index 6e292ee..8c5f81d 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -25,357 +25,308 @@ * -------------------------------------------------------------------------- */ #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 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 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& 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 f) { signalCb_ = f; } -void setEndOfRecCallback(std::function 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 diff --git a/src/core/mixer.h b/src/core/mixer.h index b143274..204845c 100644 --- a/src/core/mixer.h +++ b/src/core/mixer.h @@ -29,112 +29,180 @@ #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 -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 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 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 onSignalTresholdReached; -bool isChannelAudible(const channel::Data& c); + /* onEndOfRecording + Callback fired when the audio recording session has ended. */ -float getPeakOut(); -float getPeakIn(); + std::function 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& 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 diff --git a/src/core/mixerHandler.cpp b/src/core/mixerHandler.cpp index 057e700..bbf46c9 100644 --- a/src/core/mixerHandler.cpp +++ b/src/core/mixerHandler.cpp @@ -26,25 +26,11 @@ #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" @@ -55,375 +41,370 @@ #include #include -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 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 -std::vector getChannelsIf_(F f) -{ - std::vector out; - for (channel::Data& ch : model::get().channels) - if (f(ch)) - out.push_back(&ch); - return out; -} + m_model.get().channels.clear(); -std::vector 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 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 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 = 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* 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()); - setupChannelPostRecording_(ch); + if (old != nullptr) + m_model.removeShared(*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 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(); + 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()); + } -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()); + } +#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* 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(*old); + ch.samplePlayer->loadWave(ch, nullptr); + m_model.swap(model::SwapType::HARD); - recManager::refreshInputRecMode(); + if (wave != nullptr) + m_model.removeShared(*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&& w) +void MixerHandler::freeAllChannels() { - model::add(std::move(w)); + assert(onChannelsAltered != nullptr); - Wave& wave = model::back(); - 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(); - 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 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); - 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); +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(); - - 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 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); +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 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 MixerHandler::getChannelsIf(std::function f) { - return anyChannel_([](const channel::Data& ch) { return ch.canInputRec(); }); + std::vector out; + for (Channel& ch : m_model.get().channels) + if (f(ch)) + out.push_back(&ch); + return out; } -bool hasActionRecordableChannels() +std::vector 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 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 = 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()); + 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 diff --git a/src/core/mixerHandler.h b/src/core/mixerHandler.h index 4da3053..92a9de5 100644 --- a/src/core/mixerHandler.h +++ b/src/core/mixerHandler.h @@ -27,105 +27,162 @@ #ifndef G_MIXER_HANDLER_H #define G_MIXER_HANDLER_H +#include "core/plugins/pluginManager.h" +#include "core/waveManager.h" #include "types.h" +#include #include #include +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&& 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 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 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 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(Frame)> onChannelRecorded; -bool hasActions(); +private: + bool forAnyChannel(std::function f) const; -/* hasAudioData -True if at least one Sample Channel has some audio recorded in it. */ + std::vector getChannelsIf(std::function f); + std::vector getRecordableChannels(); + std::vector getOverdubbableChannels(); -bool hasAudioData(); + void setupChannelPostRecording(Channel& ch, Frame currentFrame); -float getInVol(); -float getOutVol(); -bool getInToOut(); -} // namespace giada::m::mh + /* recordChannel + Records the current Mixer audio input data into an empty channel. */ + + void recordChannel(Channel& ch, Frame recordedFrames, Frame currentFrame); + + /* overdubChannel + Records the current Mixer audio input data into a channel with an existing + Wave, overdub mode. */ + + void overdubChannel(Channel& ch, Frame currentFrame); + + model::Model& m_model; + Mixer& m_mixer; +}; +} // namespace giada::m #endif diff --git a/src/core/model/mixer.cpp b/src/core/model/mixer.cpp new file mode 100644 index 0000000..e667c3f --- /dev/null +++ b/src/core/model/mixer.cpp @@ -0,0 +1,102 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "core/model/mixer.h" + +namespace giada::m::model +{ +Mixer::Shared& Mixer::Shared::operator=(const Mixer::Shared& o) +{ + if (this == &o) + return *this; + active.store(o.active.load()); + peakOutL.store(0.0f); + peakOutR.store(0.0f); + peakInL.store(0.0f); + peakInR.store(0.0f); + inputTracker.store(0); + return *this; +} + +/* -------------------------------------------------------------------------- */ + +bool Mixer::a_isActive() const +{ + return shared->active.load() == true; +} + +/* -------------------------------------------------------------------------- */ + +Frame Mixer::a_getInputTracker() const +{ + return shared->inputTracker.load(); +} + +/* -------------------------------------------------------------------------- */ + +void Mixer::a_setActive(bool isActive) const +{ + shared->active.store(isActive); +} + +/* -------------------------------------------------------------------------- */ + +void Mixer::a_setInputTracker(Frame f) const +{ + shared->inputTracker.store(f); +} + +/* -------------------------------------------------------------------------- */ + +Peak Mixer::a_getPeakOut() const +{ + return {shared->peakOutL.load(), shared->peakOutR.load()}; +} + +Peak Mixer::a_getPeakIn() const +{ + return {shared->peakInL.load(), shared->peakInR.load()}; +} + +/* -------------------------------------------------------------------------- */ + +void Mixer::a_setPeakOut(Peak p) const +{ + shared->peakOutL.store(p.left); + shared->peakOutR.store(p.right); +} + +void Mixer::a_setPeakIn(Peak p) const +{ + shared->peakInL.store(p.left); + shared->peakInR.store(p.right); +} + +/* -------------------------------------------------------------------------- */ + +mcl::AudioBuffer& Mixer::getRecBuffer() const { return shared->recBuffer; } +mcl::AudioBuffer& Mixer::getInBuffer() const { return shared->inBuffer; } +} // namespace giada::m::model diff --git a/src/core/model/mixer.h b/src/core/model/mixer.h new file mode 100644 index 0000000..a1377b1 --- /dev/null +++ b/src/core/model/mixer.h @@ -0,0 +1,90 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 + +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 active = false; + WeakAtomic peakOutL = 0.0f; + WeakAtomic peakOutR = 0.0f; + WeakAtomic peakInL = 0.0f; + WeakAtomic peakInR = 0.0f; + WeakAtomic 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 diff --git a/src/core/model/model.cpp b/src/core/model/model.cpp index 5a69aec..3d203bb 100644 --- a/src/core/model/model.cpp +++ b/src/core/model/model.cpp @@ -30,29 +30,12 @@ #include "core/channels/channelManager.h" #endif +using namespace mcl; + namespace giada::m::model { namespace { -struct State -{ - Clock::State clock; - Mixer::State mixer; - std::vector> channels; -}; - -struct Data -{ - std::vector> channels; - std::vector> waves; - recorder::ActionMap actions; -#ifdef WITH_VST - std::vector> plugins; -#endif -}; - -/* -------------------------------------------------------------------------- */ - template auto getIter_(const std::vector>& source, ID id) { @@ -81,231 +64,231 @@ void remove_(D& dest, T& ref) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -std::function onSwap_ = nullptr; - -Swapper 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(const_cast(this)->getChannel(id)); + return const_cast(const_cast(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 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 -T& getAll() +T& Model::getAllShared() { #ifdef WITH_VST if constexpr (std::is_same_v) - return data.plugins; + return m_shared.plugins; #endif if constexpr (std::is_same_v) - return data.waves; - if constexpr (std::is_same_v) - return data.actions; - if constexpr (std::is_same_v) - return data.channels; - if constexpr (std::is_same_v) - return state.channels; + return m_shared.waves; + if constexpr (std::is_same_v) + return m_shared.actions; + if constexpr (std::is_same_v) + return m_shared.channelsShared; assert(false); } #ifdef WITH_VST -template PluginPtrs& getAll(); +template PluginPtrs& Model::getAllShared(); #endif -template WavePtrs& getAll(); -template Actions& getAll(); -template ChannelBufferPtrs& getAll(); -template ChannelStatePtrs& getAll(); +template WavePtrs& Model::getAllShared(); +template Actions::Map& Model::getAllShared(); +template ChannelSharedPtrs& Model::getAllShared(); /* -------------------------------------------------------------------------- */ template -T* find(ID id) +T* Model::findShared(ID id) { #ifdef WITH_VST if constexpr (std::is_same_v) - return get_(data.plugins, id); + return get_(m_shared.plugins, id); #endif if constexpr (std::is_same_v) - return get_(data.waves, id); + return get_(m_shared.waves, id); assert(false); } #ifdef WITH_VST -template Plugin* find(ID id); +template Plugin* Model::findShared(ID id); #endif -template Wave* find(ID id); +template Wave* Model::findShared(ID id); /* -------------------------------------------------------------------------- */ template -void add(T obj) +void Model::addShared(T obj) { #ifdef WITH_VST if constexpr (std::is_same_v) - data.plugins.push_back(std::move(obj)); + m_shared.plugins.push_back(std::move(obj)); #endif if constexpr (std::is_same_v) - data.waves.push_back(std::move(obj)); - if constexpr (std::is_same_v) - data.channels.push_back(std::move(obj)); - if constexpr (std::is_same_v) - state.channels.push_back(std::move(obj)); + m_shared.waves.push_back(std::move(obj)); + if constexpr (std::is_same_v) + m_shared.channelsShared.push_back(std::move(obj)); } #ifdef WITH_VST -template void add(PluginPtr p); +template void Model::addShared(PluginPtr p); #endif -template void add(WavePtr p); -template void add(ChannelBufferPtr p); -template void add(ChannelStatePtr p); +template void Model::addShared(WavePtr p); +template void Model::addShared(ChannelSharedPtr p); /* -------------------------------------------------------------------------- */ template -void remove(const T& ref) +void Model::removeShared(const T& ref) { #ifdef WITH_VST if constexpr (std::is_same_v) - remove_(data.plugins, ref); + remove_(m_shared.plugins, ref); #endif if constexpr (std::is_same_v) - remove_(data.waves, ref); + remove_(m_shared.waves, ref); } #ifdef WITH_VST -template void remove(const Plugin& t); +template void Model::removeShared(const Plugin& t); #endif -template void remove(const Wave& t); +template void Model::removeShared(const Wave& t); /* -------------------------------------------------------------------------- */ template -T& back() +T& Model::backShared() { #ifdef WITH_VST if constexpr (std::is_same_v) - return *data.plugins.back().get(); + return *m_shared.plugins.back().get(); #endif if constexpr (std::is_same_v) - return *data.waves.back().get(); - if constexpr (std::is_same_v) - return *state.channels.back().get(); - if constexpr (std::is_same_v) - return *data.channels.back().get(); + return *m_shared.waves.back().get(); + if constexpr (std::is_same_v) + return *m_shared.channelsShared.back().get(); } #ifdef WITH_VST -template Plugin& back(); +template Plugin& Model::backShared(); #endif -template Wave& back(); -template channel::State& back(); -template channel::Buffer& back(); +template Wave& Model::backShared(); +template Channel::Shared& Model::backShared(); /* -------------------------------------------------------------------------- */ template -void clear() +void Model::clearShared() { #ifdef WITH_VST if constexpr (std::is_same_v) - data.plugins.clear(); + m_shared.plugins.clear(); #endif if constexpr (std::is_same_v) - data.waves.clear(); + m_shared.waves.clear(); } #ifdef WITH_VST -template void clear(); +template void Model::clearShared(); #endif -template void clear(); +template void Model::clearShared(); /* -------------------------------------------------------------------------- */ #ifdef G_DEBUG_MODE -void debug() +void Model::debug() { puts("======== SYSTEM STATUS ========"); - puts("model::layout"); + puts("model::layout.channels"); int i = 0; - for (const channel::Data& c : get().channels) + for (const Channel& c : get().channels) { - printf("\t%d) - ID=%d name='%s' type=%d columnID=%d state=%p\n", - i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.state); + printf("\t%d) - ID=%d name='%s' type=%d columnID=%d shared=%p\n", + i++, c.id, c.name.c_str(), (int)c.type, c.columnId, (void*)&c.shared); #ifdef WITH_VST if (c.plugins.size() > 0) { @@ -319,7 +302,7 @@ void debug() puts("model::state.channels"); i = 0; - for (const auto& c : state.channels) + for (const auto& c : m_shared.channelsShared) { printf("\t%d) - %p\n", i++, (void*)c.get()); } @@ -327,12 +310,12 @@ void debug() puts("model::data.waves"); i = 0; - for (const auto& w : data.waves) + for (const auto& w : m_shared.waves) printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w.get(), w->id, w->getPath().c_str()); puts("model::data.actions"); - for (const auto& [frame, actions] : getAll()) + for (const auto& [frame, actions] : getAllShared()) { printf("\tframe: %d\n", frame); for (const Action& a : actions) @@ -345,7 +328,7 @@ void debug() puts("model::data.plugins"); i = 0; - for (const auto& p : data.plugins) + for (const auto& p : m_shared.plugins) printf("\t%d) %p - ID=%d\n", i++, (void*)p.get(), p->id); #endif diff --git a/src/core/model/model.h b/src/core/model/model.h index d6dcfd0..6b7fe33 100644 --- a/src/core/model/model.h +++ b/src/core/model/model.h @@ -24,32 +24,24 @@ * * -------------------------------------------------------------------------- */ -#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 namespace giada::m::model { -struct Kernel -{ - bool audioReady = false; - bool midiReady = false; -}; - -struct Recorder -{ - bool isRecordingAction = false; - bool isRecordingInput = false; -}; - struct MidiIn { bool enabled = false; @@ -65,53 +57,16 @@ struct MidiIn uint32_t metronome = 0x0; }; -struct Clock -{ - struct State - { - WeakAtomic currentFrameWait = 0; - WeakAtomic currentFrame = 0; - WeakAtomic 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 active = false; - WeakAtomic peakOut = 0.0f; - WeakAtomic 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 channels; + Sequencer sequencer; + Mixer mixer; + Recorder recorder; + MidiIn midiIn; + std::vector channels; /* locked If locked, Mixer won't process channels. This is used to allow editing the @@ -120,18 +75,18 @@ struct Layout bool locked = false; }; -/* Lock +/* LayoutLock Alias for a REALTIME scoped lock provided by the Swapper class. Use this in the real-time thread to lock the Layout. */ -using Lock = Swapper::RtLock; +using LayoutLock = mcl::AtomicSwapper::RtLock; /* SwapType Type of Layout change. Hard: the structure has changed (e.g. add a new channel); Soft: a property has changed (e.g. change volume); None: something has changed but we don't care. -Used by model listeners to determine the type of change that occured in the +Used by model listeners to determine the type of change that occurred in the layout. */ enum class SwapType @@ -141,95 +96,125 @@ enum class SwapType NONE }; +#ifdef WITH_VST +using PluginPtr = std::unique_ptr; +#endif +using WavePtr = std::unique_ptr; +using ChannelSharedPtr = std::unique_ptr; + +#ifdef WITH_VST +using PluginPtrs = std::vector; +#endif +using WavePtrs = std::vector; +using ChannelSharedPtrs = std::vector; + /* -------------------------------------------------------------------------- */ -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 f); + void swap(SwapType t); -bool isLocked(); + template + T& getAllShared(); -/* -------------------------------------------------------------------------- */ + /* findShared + Finds something in the shared data given an ID. Returns nullptr if the + object is not found. */ -/* Model utilities */ + template + T* findShared(ID id); -#ifdef WITH_VST -using PluginPtr = std::unique_ptr; -#endif -using WavePtr = std::unique_ptr; -using ChannelBufferPtr = std::unique_ptr; -using ChannelStatePtr = std::unique_ptr; + /* addShared + Adds some shared data (by moving it). */ -#ifdef WITH_VST -using PluginPtrs = std::vector; -#endif -using WavePtrs = std::vector; -using Actions = recorder::ActionMap; -using ChannelBufferPtrs = std::vector; -using ChannelStatePtrs = std::vector; + template + void addShared(T); -// TODO - are ID-based objects still necessary? + template + void removeShared(const T&); -template -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 + T& backShared(); -template -T* find(ID id); + template + void clearShared(); -template -void add(T); +#ifdef G_DEBUG_MODE + void debug(); +#endif -template -void remove(const T&); + /* onSwap + Optional callback fired when the layout has been swapped. Useful for + listening to model changes. */ -template -T& back(); + std::function onSwap = nullptr; -template -void clear(); +private: + struct Shared + { + Sequencer::Shared sequencerShared; + Mixer::Shared mixerShared; + Recorder::Shared recorderShared; + std::vector> channelsShared; -#ifdef G_DEBUG_MODE -void debug(); + std::vector> waves; + Actions::Map actions; +#ifdef WITH_VST + std::vector> plugins; #endif + }; + + mcl::AtomicSwapper m_layout; + Shared m_shared; +}; + +/* -------------------------------------------------------------------------- */ + +class DataLock +{ +public: + DataLock(Model&, SwapType t); + ~DataLock(); + +private: + Model& m_model; + SwapType m_swapType; +}; } // namespace giada::m::model #endif diff --git a/src/core/model/recorder.cpp b/src/core/model/recorder.cpp new file mode 100644 index 0000000..d7835b6 --- /dev/null +++ b/src/core/model/recorder.cpp @@ -0,0 +1,50 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "core/model/recorder.h" + +namespace giada::m::model +{ +bool Recorder::a_isRecordingAction() const +{ + return shared->isRecordingAction.load(); +} + +bool Recorder::a_isRecordingInput() const +{ + return shared->isRecordingInput.load(); +} + +void Recorder::a_setRecordingAction(bool b) const +{ + shared->isRecordingAction.store(b); +} + +void Recorder::a_setRecordingInput(bool b) const +{ + shared->isRecordingInput.store(b); +} +} // namespace giada::m::model diff --git a/src/core/model/recorder.h b/src/core/model/recorder.h new file mode 100644 index 0000000..d4112ce --- /dev/null +++ b/src/core/model/recorder.h @@ -0,0 +1,55 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 isRecordingAction = false; + WeakAtomic isRecordingInput = false; + }; + + Shared* shared = nullptr; +}; +} // namespace giada::m::model + +#endif diff --git a/src/core/model/sequencer.cpp b/src/core/model/sequencer.cpp new file mode 100644 index 0000000..d2a5b2f --- /dev/null +++ b/src/core/model/sequencer.cpp @@ -0,0 +1,84 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "core/model/sequencer.h" + +namespace giada::m::model +{ +bool Sequencer::isActive() const +{ + return status == SeqStatus::RUNNING || status == SeqStatus::WAITING; +} + +/* -------------------------------------------------------------------------- */ + +bool Sequencer::canQuantize() const +{ + return quantize > 0 && status == SeqStatus::RUNNING; +} + +/* -------------------------------------------------------------------------- */ + +bool Sequencer::isRunning() const +{ + return status == SeqStatus::RUNNING; +} + +/* -------------------------------------------------------------------------- */ + +bool Sequencer::a_isOnBar() const +{ + const int currentFrame = shared->currentFrame.load(); + + if (status == SeqStatus::WAITING || currentFrame == 0) + return false; + return currentFrame % framesInBar == 0; +} + +/* -------------------------------------------------------------------------- */ + +bool Sequencer::a_isOnBeat() const +{ + return shared->currentFrame.load() % framesInBeat == 0; +} + +/* -------------------------------------------------------------------------- */ + +bool Sequencer::a_isOnFirstBeat() const +{ + return shared->currentFrame.load() == 0; +} + +/* -------------------------------------------------------------------------- */ + +Frame Sequencer::a_getCurrentFrame() const { return shared->currentFrame.load(); } +Frame Sequencer::a_getCurrentBeat() const { return shared->currentBeat.load(); } + +/* -------------------------------------------------------------------------- */ + +void Sequencer::a_setCurrentFrame(Frame f) const { shared->currentFrame.store(f); } +void Sequencer::a_setCurrentBeat(Frame f) const { shared->currentBeat.store(f); } +} // namespace giada::m::model diff --git a/src/core/model/sequencer.h b/src/core/model/sequencer.h new file mode 100644 index 0000000..5526f3b --- /dev/null +++ b/src/core/model/sequencer.h @@ -0,0 +1,89 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 currentFrame = 0; + WeakAtomic currentBeat = 0; + }; + + Shared* shared = nullptr; +}; +} // namespace giada::m::model + +#endif diff --git a/src/core/model/storage.cpp b/src/core/model/storage.cpp index 15eb15b..d2e5e73 100644 --- a/src/core/model/storage.cpp +++ b/src/core/model/storage.cpp @@ -27,32 +27,36 @@ #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 +extern giada::m::Engine g_engine; + namespace giada::m::model { namespace { -void loadChannels_(const std::vector& channels, int samplerate) +void loadChannels_(const std::vector& channels, int samplerate) { - float samplerateRatio = conf::conf.samplerate / static_cast(samplerate); + float samplerateRatio = g_engine.kernelAudio.getSampleRate() / static_cast(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& pactions) +void loadActions_(const std::vector& pactions) { - getAll() = std::move(recorderHandler::deserializeActions(pactions)); + g_engine.model.getAllShared() = std::move(g_engine.actionRecorder.deserializeActions(pactions)); } } // namespace @@ -60,36 +64,39 @@ void loadActions_(const std::vector& pactions) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void store(patch::Patch& patch) +void store(Patch::Data& patch) { - const Layout& layout = get(); + const Layout& layout = g_engine.model.get(); - patch.bars = layout.clock.bars; - patch.beats = layout.clock.beats; - patch.bpm = layout.clock.bpm; - patch.quantize = layout.clock.quantize; - patch.metronome = sequencer::isMetronomeOn(); // TODO - add bool metronome to Layout - patch.samplerate = conf::conf.samplerate; + patch.bars = layout.sequencer.bars; + patch.beats = layout.sequencer.beats; + patch.bpm = layout.sequencer.bpm; + patch.quantize = layout.sequencer.quantize; + patch.metronome = g_engine.sequencer.isMetronomeOn(); // TODO - addShared bool metronome to Layout + patch.samplerate = g_engine.kernelAudio.getSampleRate(); #ifdef WITH_VST - for (const auto& p : getAll()) - patch.plugins.push_back(pluginManager::serializePlugin(*p)); + patch.plugins.clear(); + for (const auto& p : g_engine.model.getAllShared()) + patch.plugins.push_back(g_engine.pluginManager.serializePlugin(*p)); #endif - patch.actions = recorderHandler::serializeActions(getAll()); + patch.actions = g_engine.actionRecorder.serializeActions(g_engine.model.getAllShared()); - for (const auto& w : getAll()) - patch.waves.push_back(waveManager::serializeWave(*w)); + patch.waves.clear(); + for (const auto& w : g_engine.model.getAllShared()) + patch.waves.push_back(g_engine.waveManager.serializeWave(*w)); - for (const channel::Data& c : layout.channels) - patch.channels.push_back(channelManager::serializeChannel(c)); + patch.channels.clear(); + for (const Channel& c : layout.channels) + patch.channels.push_back(g_engine.channelManager.serializeChannel(c)); } /* -------------------------------------------------------------------------- */ -void store(conf::Conf& conf) +void store(Conf::Data& conf) { - const Layout& layout = get(); + const Layout& layout = g_engine.model.get(); conf.midiInEnabled = layout.midiIn.enabled; conf.midiInFilter = layout.midiIn.filter; @@ -106,61 +113,61 @@ void store(conf::Conf& conf) /* -------------------------------------------------------------------------- */ -void load(const patch::Patch& patch) +void load(const Patch::Data& patch) { - DataLock lock; + DataLock lock = g_engine.model.lockData(); /* Clear and re-initialize channels first. */ - get().channels = {}; - getAll().clear(); - getAll().clear(); + g_engine.model.get().channels = {}; + g_engine.model.getAllShared().clear(); /* Load external data first: plug-ins and waves. */ #ifdef WITH_VST - getAll().clear(); - for (const patch::Plugin& pplugin : patch.plugins) - getAll().push_back(pluginManager::deserializePlugin(pplugin, patch.version)); + g_engine.model.getAllShared().clear(); + for (const Patch::Plugin& pplugin : patch.plugins) + g_engine.model.getAllShared().push_back(g_engine.pluginManager.deserializePlugin( + pplugin, patch.version, g_engine.kernelAudio.getSampleRate(), g_engine.kernelAudio.getBufferSize(), g_engine.sequencer)); #endif - getAll().clear(); - for (const patch::Wave& pwave : patch.waves) + g_engine.model.getAllShared().clear(); + for (const Patch::Wave& pwave : patch.waves) { - std::unique_ptr w = waveManager::deserializeWave(pwave, conf::conf.samplerate, - conf::conf.rsmpQuality); + std::unique_ptr w = g_engine.waveManager.deserializeWave(pwave, g_engine.kernelAudio.getSampleRate(), + g_engine.conf.data.rsmpQuality); if (w != nullptr) - getAll().push_back(std::move(w)); + g_engine.model.getAllShared().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 diff --git a/src/core/model/storage.h b/src/core/model/storage.h index c163abe..e69d140 100644 --- a/src/core/model/storage.h +++ b/src/core/model/storage.h @@ -27,20 +27,15 @@ #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 diff --git a/src/core/patch.cpp b/src/core/patch.cpp index 4ababcd..2db30e4 100644 --- a/src/core/patch.cpp +++ b/src/core/patch.cpp @@ -33,15 +33,11 @@ namespace nl = nlohmann; -namespace giada -{ -namespace m -{ -namespace patch +namespace giada::m { namespace { -void readCommons_(const nl::json& j) +void readCommons_(Patch::Data& patch, const nl::json& j) { patch.name = j.value(PATCH_KEY_NAME, G_DEFAULT_PATCH_NAME); patch.bars = j.value(PATCH_KEY_BARS, G_DEFAULT_BARS); @@ -55,12 +51,12 @@ void readCommons_(const nl::json& j) /* -------------------------------------------------------------------------- */ -void readColumns_(const nl::json& j) +void readColumns_(Patch::Data& patch, const nl::json& j) { ID id = 0; for (const auto& jcol : j[PATCH_KEY_COLUMNS]) { - Column c; + Patch::Column c; c.id = jcol.value(PATCH_KEY_COLUMN_ID, ++id); c.width = jcol.value(PATCH_KEY_COLUMN_WIDTH, G_DEFAULT_COLUMN_WIDTH); patch.columns.push_back(c); @@ -71,7 +67,7 @@ void readColumns_(const nl::json& j) #ifdef WITH_VST -void readPlugins_(const nl::json& j) +void readPlugins_(Patch::Data& patch, const nl::json& j) { if (!j.contains(PATCH_KEY_PLUGINS)) return; @@ -79,12 +75,12 @@ void readPlugins_(const nl::json& j) ID id = 0; for (const auto& jplugin : j[PATCH_KEY_PLUGINS]) { - Plugin p; + Patch::Plugin p; p.id = jplugin.value(PATCH_KEY_PLUGIN_ID, ++id); p.path = jplugin.value(PATCH_KEY_PLUGIN_PATH, ""); p.bypass = jplugin.value(PATCH_KEY_PLUGIN_BYPASS, false); - if (patch.version < Version{0, 17, 0}) + if (patch.version < Patch::Version{0, 17, 0}) for (const auto& jparam : jplugin[PATCH_KEY_PLUGIN_PARAMS]) p.params.push_back(jparam); else @@ -101,7 +97,7 @@ void readPlugins_(const nl::json& j) /* -------------------------------------------------------------------------- */ -void readWaves_(const nl::json& j, const std::string& basePath) +void readWaves_(Patch::Data& patch, const nl::json& j, const std::string& basePath) { if (!j.contains(PATCH_KEY_WAVES)) return; @@ -109,16 +105,16 @@ void readWaves_(const nl::json& j, const std::string& basePath) ID id = 0; for (const auto& jwave : j[PATCH_KEY_WAVES]) { - Wave w; + Patch::Wave w; w.id = jwave.value(PATCH_KEY_WAVE_ID, ++id); - w.path = basePath + jwave.value(PATCH_KEY_WAVE_PATH, ""); + w.path = basePath + G_SLASH + jwave.value(PATCH_KEY_WAVE_PATH, ""); patch.waves.push_back(w); } } /* -------------------------------------------------------------------------- */ -void readActions_(const nl::json& j) +void readActions_(Patch::Data& patch, const nl::json& j) { if (!j.contains(PATCH_KEY_ACTIONS)) return; @@ -126,7 +122,7 @@ void readActions_(const nl::json& j) ID id = 0; for (const auto& jaction : j[PATCH_KEY_ACTIONS]) { - Action a; + Patch::Action a; a.id = jaction.value(G_PATCH_KEY_ACTION_ID, ++id); a.channelId = jaction.value(G_PATCH_KEY_ACTION_CHANNEL, 0); a.frame = jaction.value(G_PATCH_KEY_ACTION_FRAME, 0); @@ -139,16 +135,16 @@ void readActions_(const nl::json& j) /* -------------------------------------------------------------------------- */ -void readChannels_(const nl::json& j) +void readChannels_(Patch::Data& patch, const nl::json& j) { if (!j.contains(PATCH_KEY_CHANNELS)) return; - ID defaultId = mixer::PREVIEW_CHANNEL_ID; + ID defaultId = Mixer::PREVIEW_CHANNEL_ID; for (const auto& jchannel : j[PATCH_KEY_CHANNELS]) { - Channel c; + Patch::Channel c; c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++defaultId); c.type = static_cast(jchannel.value(PATCH_KEY_CHANNEL_TYPE, 1)); c.volume = jchannel.value(PATCH_KEY_CHANNEL_VOLUME, G_DEFAULT_VOL); @@ -203,11 +199,11 @@ void readChannels_(const nl::json& j) #ifdef WITH_VST -void writePlugins_(nl::json& j) +void writePlugins_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_PLUGINS] = nl::json::array(); - for (const Plugin& p : patch.plugins) + for (const Patch::Plugin& p : patch.plugins) { nl::json jplugin; @@ -229,11 +225,11 @@ void writePlugins_(nl::json& j) /* -------------------------------------------------------------------------- */ -void writeColumns_(nl::json& j) +void writeColumns_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_COLUMNS] = nl::json::array(); - for (const Column& column : patch.columns) + for (const Patch::Column& column : patch.columns) { nl::json jcolumn; jcolumn[PATCH_KEY_COLUMN_ID] = column.id; @@ -244,11 +240,11 @@ void writeColumns_(nl::json& j) /* -------------------------------------------------------------------------- */ -void writeActions_(nl::json& j) +void writeActions_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_ACTIONS] = nl::json::array(); - for (const Action& a : patch.actions) + for (const Patch::Action& a : patch.actions) { nl::json jaction; jaction[G_PATCH_KEY_ACTION_ID] = a.id; @@ -263,11 +259,11 @@ void writeActions_(nl::json& j) /* -------------------------------------------------------------------------- */ -void writeWaves_(nl::json& j) +void writeWaves_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_WAVES] = nl::json::array(); - for (const Wave& w : patch.waves) + for (const Patch::Wave& w : patch.waves) { nl::json jwave; jwave[PATCH_KEY_WAVE_ID] = w.id; @@ -279,7 +275,7 @@ void writeWaves_(nl::json& j) /* -------------------------------------------------------------------------- */ -void writeCommons_(nl::json& j) +void writeCommons_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_HEADER] = "GIADAPTC"; j[PATCH_KEY_VERSION_MAJOR] = G_VERSION_MAJOR; @@ -297,13 +293,12 @@ void writeCommons_(nl::json& j) /* -------------------------------------------------------------------------- */ -void writeChannels_(nl::json& j) +void writeChannels_(const Patch::Data& patch, nl::json& j) { j[PATCH_KEY_CHANNELS] = nl::json::array(); - for (const Channel& c : patch.channels) + for (const Patch::Channel& c : patch.channels) { - nl::json jchannel; jchannel[PATCH_KEY_CHANNEL_ID] = c.id; @@ -358,16 +353,16 @@ void writeChannels_(nl::json& j) /* -------------------------------------------------------------------------- */ -void modernize_() +void modernize_(Patch::Data& patch) { - for (Channel& c : patch.channels) + for (Patch::Channel& c : patch.channels) { /* 0.16.3 Make sure that ChannelType is correct: ID 1, 2 are MASTER channels, ID 3 is PREVIEW channel. */ - if (c.id == mixer::MASTER_OUT_CHANNEL_ID || c.id == mixer::MASTER_IN_CHANNEL_ID) + if (c.id == Mixer::MASTER_OUT_CHANNEL_ID || c.id == Mixer::MASTER_IN_CHANNEL_ID) c.type = ChannelType::MASTER; - else if (c.id == mixer::PREVIEW_CHANNEL_ID) + else if (c.id == Mixer::PREVIEW_CHANNEL_ID) c.type = ChannelType::PREVIEW; /* 0.16.4 @@ -390,16 +385,12 @@ void modernize_() /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Patch patch; - -/* -------------------------------------------------------------------------- */ - -bool Version::operator==(const Version& o) const +bool Patch::Version::operator==(const Version& o) const { return major == o.major && minor == o.minor && patch == o.patch; } -bool Version::operator<(const Version& o) const +bool Patch::Version::operator<(const Version& o) const { if (major < o.major) return true; @@ -410,26 +401,28 @@ bool Version::operator<(const Version& o) const return false; } +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void init() +void Patch::reset() { - patch = Patch(); + data = Data(); } /* -------------------------------------------------------------------------- */ -bool write(const std::string& file) +bool Patch::write(const std::string& file) const { nl::json j; - writeCommons_(j); - writeColumns_(j); - writeChannels_(j); - writeActions_(j); - writeWaves_(j); + writeCommons_(data, j); + writeColumns_(data, j); + writeChannels_(data, j); + writeActions_(data, j); + writeWaves_(data, j); #ifdef WITH_VST - writePlugins_(j); + writePlugins_(data, j); #endif std::ofstream ofs(file); @@ -442,7 +435,7 @@ bool write(const std::string& file) /* -------------------------------------------------------------------------- */ -int read(const std::string& file, const std::string& basePath) +int Patch::read(const std::string& file, const std::string& basePath) { std::ifstream ifs(file); if (!ifs.good()) @@ -453,24 +446,24 @@ int read(const std::string& file, const std::string& basePath) if (j[PATCH_KEY_HEADER] != "GIADAPTC") return G_PATCH_INVALID; - patch.version = { + data.version = { static_cast(j[PATCH_KEY_VERSION_MAJOR]), static_cast(j[PATCH_KEY_VERSION_MINOR]), static_cast(j[PATCH_KEY_VERSION_PATCH])}; - if (patch.version < Version{0, 16, 0}) + if (data.version < Version{0, 16, 0}) return G_PATCH_UNSUPPORTED; try { - readCommons_(j); - readColumns_(j); + readCommons_(data, j); + readColumns_(data, j); #ifdef WITH_VST - readPlugins_(j); + readPlugins_(data, j); #endif - readWaves_(j, basePath); - readActions_(j); - readChannels_(j); - modernize_(); + readWaves_(data, j, basePath); + readActions_(data, j); + readChannels_(data, j); + modernize_(data); } catch (nl::json::exception& e) { @@ -480,6 +473,4 @@ int read(const std::string& file, const std::string& basePath) return G_PATCH_OK; } -} // namespace patch -} // namespace m -} // namespace giada +} // namespace giada::m diff --git a/src/core/patch.h b/src/core/patch.h index 0a28cab..a9e22f1 100644 --- a/src/core/patch.h +++ b/src/core/patch.h @@ -33,147 +33,141 @@ #include #include -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 pluginIds; + std::vector 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 params; // TODO - to be removed in 0.18.0 - std::string state; - std::vector midiInParams; -}; + struct Plugin + { + ID id; + std::string path; + bool bypass; + std::vector params; // TODO - to be removed in 0.18.0 + std::string state; + std::vector 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 columns; - std::vector channels; - std::vector actions; - std::vector 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 columns; + std::vector channels; + std::vector actions; + std::vector waves; #ifdef WITH_VST - std::vector plugins; + std::vector 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 diff --git a/src/core/plugins/plugin.cpp b/src/core/plugins/plugin.cpp index 5e70112..31f75c4 100644 --- a/src/core/plugins/plugin.cpp +++ b/src/core/plugins/plugin.cpp @@ -26,9 +26,8 @@ #ifdef WITH_VST -#include "plugin.h" +#include "core/plugins/plugin.h" #include "core/const.h" -#include "core/plugins/pluginManager.h" #include "utils/log.h" #include "utils/time.h" #include @@ -48,13 +47,13 @@ Plugin::Plugin(ID id, const std::string& UID) /* -------------------------------------------------------------------------- */ -Plugin::Plugin(ID id, std::unique_ptr plugin, double samplerate, - int buffersize) +Plugin::Plugin(ID id, std::unique_ptr plugin, + std::unique_ptr playHead, double samplerate, int buffersize) : id(id) , valid(true) , onEditorResize(nullptr) , m_plugin(std::move(plugin)) -, m_playHead(std::make_unique()) +, m_playHead(std::move(playHead)) , m_bypass(false) , m_hasEditor(m_plugin->hasEditor()) { @@ -90,18 +89,6 @@ Plugin::Plugin(ID id, std::unique_ptr plugin, double /* -------------------------------------------------------------------------- */ -Plugin::Plugin(const Plugin& o) -: id(o.id) -, midiInParams(o.midiInParams) -, valid(o.valid) -, onEditorResize(o.onEditorResize) -, m_plugin(std::move(pluginManager::makePlugin(o)->m_plugin)) -, m_bypass(o.m_bypass.load()) -{ -} - -/* -------------------------------------------------------------------------- */ - Plugin::~Plugin() { if (!valid) diff --git a/src/core/plugins/plugin.h b/src/core/plugins/plugin.h index 665d2bb..d65136b 100644 --- a/src/core/plugins/plugin.h +++ b/src/core/plugins/plugin.h @@ -41,11 +41,23 @@ namespace giada::m class Plugin : private juce::ComponentListener { public: + /* Plugin (1) + Constructs an invalid plug-in. */ + Plugin(ID id, const std::string& UID); - Plugin(ID id, std::unique_ptr p, double samplerate, int buffersize); - Plugin(const Plugin& o); + + /* Plugin (2) + Constructs a valid and working plug-in. */ + + Plugin(ID id, std::unique_ptr, std::unique_ptr, + double samplerate, int buffersize); + + Plugin(const Plugin& o) = delete; + Plugin(Plugin&& o) = delete; + Plugin& operator=(const Plugin&) = delete; + Plugin& operator=(Plugin&&) = delete; + ~Plugin(); - /* TODO - mark is as non-copiable/movable*/ /* getUniqueId Returns a string-based UID. */ @@ -124,7 +136,7 @@ private: int countMainOutChannels() const; std::unique_ptr m_plugin; - std::unique_ptr m_playHead; + std::unique_ptr m_playHead; juce::AudioBuffer m_buffer; std::atomic m_bypass; diff --git a/src/core/plugins/pluginHost.cpp b/src/core/plugins/pluginHost.cpp index 4ffdbed..6533ad1 100644 --- a/src/core/plugins/pluginHost.cpp +++ b/src/core/plugins/pluginHost.cpp @@ -28,75 +28,38 @@ #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 -namespace giada::m::pluginHost +namespace giada::m { -namespace +PluginHost::Info::Info(const Sequencer& s, int sampleRate) +: m_sequencer(s) +, m_sampleRate(sampleRate) { -std::vector plugins_; -juce::MessageManager* messageManager_; -juce::AudioBuffer 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& plugins, juce::MidiBuffer& events) -{ - for (Plugin* p : plugins) - { - if (!p->valid || p->isSuspended() || p->isBypassed()) - continue; - p->process(audioBuffer_, events); - } - events.clear(); -} -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -bool Info::getCurrentPosition(CurrentPositionInfo& result) +bool PluginHost::Info::getCurrentPosition(CurrentPositionInfo& result) { - result.bpm = clock::getBpm(); - result.timeInSamples = clock::getCurrentFrame(); - result.timeInSeconds = clock::getCurrentSecond(); - result.isPlaying = clock::isRunning(); + result.bpm = m_sequencer.getBpm(); + result.timeInSamples = m_sequencer.getCurrentFrame(); + result.timeInSeconds = m_sequencer.getCurrentSecond(m_sampleRate); + result.isPlaying = m_sequencer.isRunning(); return true; } /* -------------------------------------------------------------------------- */ -bool Info::canControlTransport() +bool PluginHost::Info::canControlTransport() { return false; } @@ -105,27 +68,25 @@ bool Info::canControlTransport() /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void close() +PluginHost::PluginHost(model::Model& m) +: m_model(m) { - messageManager_->deleteInstance(); - model::clear(); } /* -------------------------------------------------------------------------- */ -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& plugins, +void PluginHost::processStack(mcl::AudioBuffer& outBuf, const std::vector& plugins, juce::MidiBuffer* events) { - assert(outBuf.countFrames() == audioBuffer_.getNumSamples()); + assert(outBuf.countFrames() == m_audioBuffer.getNumSamples()); /* If events are null: Audio stack processing (master in, master out or sample channels. No need for MIDI events. @@ -134,102 +95,109 @@ void processStack(AudioBuffer& outBuf, const std::vector& plugins, if (events == nullptr) { - giadaToJuceTempBuf_(outBuf); + giadaToJuceTempBuf(outBuf); juce::MidiBuffer dummyEvents; // empty - processPlugins_(plugins, dummyEvents); + processPlugins(plugins, dummyEvents); } else { - audioBuffer_.clear(); - processPlugins_(plugins, *events); + m_audioBuffer.clear(); + processPlugins(plugins, *events); } - juceToGiadaOutBuf_(outBuf); + juceToGiadaOutBuf(outBuf); } /* -------------------------------------------------------------------------- */ -void addPlugin(std::unique_ptr p, ID channelId) +const Plugin& PluginHost::addPlugin(std::unique_ptr p) { - model::add(std::move(p)); - - const Plugin& pluginRef = model::back(); - - /* 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(&pluginRef)); - model::swap(model::SwapType::HARD); + m_model.addShared(std::move(p)); + return m_model.backShared(); } /* -------------------------------------------------------------------------- */ -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& plugins) { - std::vector& 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& plugins) +void PluginHost::freePlugins(const std::vector& plugins) { - // TODO - channels??? for (const Plugin* p : plugins) - model::remove(*p); + m_model.removeShared(*p); } /* -------------------------------------------------------------------------- */ -std::vector clonePlugins(const std::vector& plugins) +void PluginHost::freeAllPlugins() { - std::vector out; - for (const Plugin* p : plugins) - { - model::add(pluginManager::makePlugin(*p)); - out.push_back(&model::back()); - } - return out; + m_model.clearShared(); } /* -------------------------------------------------------------------------- */ -void setPluginParameter(ID pluginId, int paramIndex, float value) +void PluginHost::setPluginParameter(ID pluginId, int paramIndex, float value) { - model::find(pluginId)->setParameter(paramIndex, value); + m_model.findShared(pluginId)->setParameter(paramIndex, value); } /* -------------------------------------------------------------------------- */ -void setPluginProgram(ID pluginId, int programIndex) +void PluginHost::setPluginProgram(ID pluginId, int programIndex) { - model::find(pluginId)->setCurrentProgram(programIndex); + m_model.findShared(pluginId)->setCurrentProgram(programIndex); } /* -------------------------------------------------------------------------- */ -void toggleBypass(ID pluginId) +void PluginHost::toggleBypass(ID pluginId) { - Plugin& plugin = *model::find(pluginId); + Plugin& plugin = *m_model.findShared(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& 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 diff --git a/src/core/plugins/pluginHost.h b/src/core/plugins/pluginHost.h index cff690e..e247e84 100644 --- a/src/core/plugins/pluginHost.h +++ b/src/core/plugins/pluginHost.h @@ -33,64 +33,99 @@ #include "deps/juce-config.h" #include +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 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& 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 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& 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& plugins); -void freePlugins(const std::vector& plugins); + /* freePlugin. + Unloads plugin from memory. */ -/* clonePlugins -Clones all the plug-ins in the 'plugins' vector. */ + void freePlugin(const m::Plugin& plugin); -std::vector clonePlugins(const std::vector& 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& 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& plugins, juce::MidiBuffer& events); + + model::Model& m_model; + + juce::AudioBuffer m_audioBuffer; +}; +} // namespace giada::m #endif diff --git a/src/core/plugins/pluginManager.cpp b/src/core/plugins/pluginManager.cpp index 482c4be..7e5cbb7 100644 --- a/src/core/plugins/pluginManager.cpp +++ b/src/core/plugins/pluginManager.cpp @@ -26,10 +26,8 @@ #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" @@ -38,69 +36,29 @@ #include "utils/string.h" #include -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 unknownPluginList_; - -/* missingPlugins -If some plugins from any stack are missing. */ - -bool missingPlugins_; - -std::unique_ptr makeInvalidPlugin_(const std::string& pid, ID id) -{ - missingPlugins_ = true; - unknownPluginList_.push_back(pid); - return std::make_unique(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(conf::conf.pluginSortMethod)); + sortPlugins(sortMethod); } /* -------------------------------------------------------------------------- */ -int scanDirs(const std::string& dirs, const std::function& cb) +int PluginManager::scanDirs(const std::string& dirs, const std::function& 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 dirVec = u::string::split(dirs, ";"); @@ -108,10 +66,9 @@ int scanDirs(const std::string& dirs, const std::function& cb) for (const std::string& dir : dirVec) searchPath.add(juce::File(dir)); - for (int i = 0; i < formatManager_.getNumFormats(); i++) + for (int i = 0; i < m_formatManager.getNumFormats(); i++) { - - juce::PluginDirectoryScanner scanner(knownPluginList_, *formatManager_.getFormat(i), searchPath, + juce::PluginDirectoryScanner scanner(m_knownPluginList, *m_formatManager.getFormat(i), searchPath, /*recursive=*/true, juce::File()); juce::String name; @@ -122,15 +79,15 @@ int scanDirs(const std::string& dirs, const std::function& cb) } } - u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", knownPluginList_.getNumTypes()); - return knownPluginList_.getNumTypes(); + u::log::print("[pluginManager::scanDir] %d plugin(s) found\n", m_knownPluginList.getNumTypes()); + return m_knownPluginList.getNumTypes(); } /* -------------------------------------------------------------------------- */ -bool saveList(const std::string& filepath) +bool PluginManager::saveList(const std::string& filepath) const { - bool out = knownPluginList_.createXml()->writeTo(juce::File(filepath)); + bool out = m_knownPluginList.createXml()->writeTo(juce::File(filepath)); if (!out) u::log::print("[pluginManager::saveList] unable to save plugin list to %s\n", filepath); return out; @@ -138,65 +95,72 @@ bool saveList(const std::string& filepath) /* -------------------------------------------------------------------------- */ -bool loadList(const std::string& filepath) +bool PluginManager::loadList(const std::string& filepath) { std::unique_ptr elem(juce::XmlDocument::parse(juce::File(filepath))); if (elem == nullptr) return false; - knownPluginList_.recreateFromXml(*elem); + m_knownPluginList.recreateFromXml(*elem); return true; } /* -------------------------------------------------------------------------- */ -std::unique_ptr makePlugin(const std::string& pid, ID id) +std::unique_ptr 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 pd = knownPluginList_.getTypeForIdentifierString(pid); + const std::unique_ptr 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 pi = formatManager_.createPluginInstance(*pd, samplerate_, buffersize_, error); + std::unique_ptr 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(pluginId_.generate(id), std::move(pi), samplerate_, buffersize_); + return std::make_unique( + m_pluginId.generate(id), + std::move(pi), + std::make_unique(sequencer, sampleRate), + sampleRate, bufferSize); } /* -------------------------------------------------------------------------- */ -std::unique_ptr makePlugin(int index) +std::unique_ptr 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 makePlugin(const Plugin& src) +std::unique_ptr PluginManager::makePlugin(const Plugin& src, int sampleRate, + int bufferSize, const Sequencer& sequencer) { - std::unique_ptr p = makePlugin(src.getUniqueId()); + std::unique_ptr p = makePlugin(src.getUniqueId(), sampleRate, bufferSize, sequencer); for (int i = 0; i < src.getNumParameters(); i++) p->setParameter(i, src.getParameter(i)); @@ -206,9 +170,9 @@ std::unique_ptr makePlugin(const Plugin& src) /* -------------------------------------------------------------------------- */ -const patch::Plugin serializePlugin(const Plugin& p) +const Patch::Plugin PluginManager::serializePlugin(const Plugin& p) const { - patch::Plugin pp; + Patch::Plugin pp; pp.id = p.id; pp.path = p.getUniqueId(); pp.bypass = p.isBypassed(); @@ -222,16 +186,17 @@ const patch::Plugin serializePlugin(const Plugin& p) /* -------------------------------------------------------------------------- */ -std::unique_ptr deserializePlugin(const patch::Plugin& p, patch::Version version) +std::unique_ptr PluginManager::deserializePlugin(const Patch::Plugin& p, + Patch::Version version, int sampleRate, int bufferSize, const Sequencer& sequencer) { - std::unique_ptr plugin = makePlugin(p.path, p.id); + std::unique_ptr plugin = makePlugin(p.path, sampleRate, bufferSize, sequencer, p.id); if (!plugin->valid) return plugin; // Return invalid version /* Fill plug-in parameters. */ plugin->setBypass(p.bypass); - if (version < patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0 + if (version < Patch::Version{0, 17, 0}) // TODO - to be removed in 0.18.0 for (unsigned j = 0; j < p.params.size(); j++) plugin->setParameter(j, p.params.at(j)); else @@ -254,12 +219,12 @@ std::unique_ptr deserializePlugin(const patch::Plugin& p, patch::Version /* -------------------------------------------------------------------------- */ -std::vector hydratePlugins(std::vector pluginIds) +std::vector PluginManager::hydratePlugins(std::vector pluginIds, model::Model& model) { std::vector out; for (ID id : pluginIds) { - Plugin* plugin = model::find(id); + Plugin* plugin = model.findShared(id); if (plugin != nullptr) out.push_back(plugin); } @@ -268,74 +233,82 @@ std::vector hydratePlugins(std::vector pluginIds) /* -------------------------------------------------------------------------- */ -int countAvailablePlugins() +int PluginManager::countAvailablePlugins() const { - return knownPluginList_.getNumTypes(); + return m_knownPluginList.getNumTypes(); } /* -------------------------------------------------------------------------- */ -int countUnknownPlugins() +std::vector PluginManager::getPluginsInfo() const { - return unknownPluginList_.size(); -} + std::vector 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 PluginManager::makeInvalidPlugin(const std::string& pid, ID id) +{ + m_missingPlugins = true; + m_unknownPluginList.push_back(pid); + return std::make_unique(m_pluginId.generate(id), pid); // Invalid plug-in +} +} // namespace giada::m #endif // #ifdef WITH_VST diff --git a/src/core/plugins/pluginManager.h b/src/core/plugins/pluginManager.h index d44a02d..8b16ae4 100644 --- a/src/core/plugins/pluginManager.h +++ b/src/core/plugins/pluginManager.h @@ -29,6 +29,8 @@ #ifndef G_PLUGIN_MANAGER_H #define G_PLUGIN_MANAGER_H +#include "core/idManager.h" +#include "core/patch.h" #include "deps/juce-config.h" #include "plugin.h" @@ -37,77 +39,111 @@ namespace giada::m::patch struct Plugin; struct Version; } // namespace giada::m::patch -namespace giada::m::pluginManager -{ -enum class SortMethod : int + +namespace giada::m::model { - NAME = 0, - CATEGORY, - MANUFACTURER, - FORMAT -}; +class Model; +} -struct PluginInfo +namespace giada::m { - std::string uid; - std::string name; - std::string category; - std::string manufacturerName; - std::string format; - bool isInstrument; -}; +class Sequencer; +class PluginManager final +{ +public: + enum class SortMethod : int + { + NAME = 0, + CATEGORY, + MANUFACTURER, + FORMAT + }; + + struct PluginInfo + { + std::string uid; + std::string name; + std::string category; + std::string manufacturerName; + std::string format; + bool isInstrument; + bool exists; + bool isKnown; + }; + + /* getPluginsInfo + Returns a vector of PluginInfo objects containing all plug-ins, known and + unknown, scanned so far. */ -void init(int samplerate, int buffersize); + std::vector 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& 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& cb); -std::unique_ptr makePlugin(const std::string& pid, ID id = 0); -std::unique_ptr makePlugin(int index); -std::unique_ptr 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 deserializePlugin(const patch::Plugin& p, patch::Version version); -std::vector hydratePlugins(std::vector pluginIds); + std::unique_ptr makePlugin(const std::string& pid, int sampleRate, int bufferSize, const Sequencer&, ID id = 0); + std::unique_ptr makePlugin(int index, int sampleRate, int bufferSize, const Sequencer&); + std::unique_ptr 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 deserializePlugin(const Patch::Plugin& p, Patch::Version version, int sampleRate, int bufferSize, const Sequencer&); + std::vector hydratePlugins(std::vector pluginIds, model::Model& model); -std::string getUnknownPluginInfo(int index); + void sortPlugins(SortMethod sortMethod); -bool doesPluginExist(const std::string& pid); +private: + std::unique_ptr 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 m_unknownPluginList; + + /* missingPlugins + If some plugins from any stack are missing. */ + + bool m_missingPlugins; +}; +} // namespace giada::m #endif diff --git a/src/core/quantizer.cpp b/src/core/quantizer.cpp index aa6cdd7..f21c374 100644 --- a/src/core/quantizer.cpp +++ b/src/core/quantizer.cpp @@ -25,7 +25,6 @@ * -------------------------------------------------------------------------- */ #include "quantizer.h" -#include "core/clock.h" #include namespace giada::m diff --git a/src/core/quantizer.h b/src/core/quantizer.h index 88760b8..490e57e 100644 --- a/src/core/quantizer.h +++ b/src/core/quantizer.h @@ -67,7 +67,7 @@ public: bool hasBeenTriggered() const; - private: +private: std::map> m_callbacks; int m_performId = -1; }; diff --git a/src/core/queue.h b/src/core/queue.h index 0e95d6f..7b07395 100644 --- a/src/core/queue.h +++ b/src/core/queue.h @@ -30,9 +30,7 @@ #include #include -namespace giada -{ -namespace m +namespace giada::m { /* Queue Single producer, single consumer lock-free queue. */ @@ -48,6 +46,9 @@ public: } Queue(const Queue&) = delete; + Queue(Queue&&) = delete; + Queue& operator=(const Queue&) = delete; + Queue& operator=(Queue&&) = delete; bool pop(T& item) { @@ -73,7 +74,7 @@ public: return true; } - private: +private: std::size_t increment(std::size_t i) const { return (i + 1) % size; @@ -83,7 +84,6 @@ public: std::atomic m_head; std::atomic m_tail; }; -} // namespace m -} // namespace giada +} // namespace giada::m #endif diff --git a/src/core/recManager.cpp b/src/core/recManager.cpp deleted file mode 100644 index f14e46d..0000000 --- a/src/core/recManager.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 channels = recorderHandler::consolidate(); - - /* Enable reading actions for Channels that have just been filled with - actions. Start reading right away, without checking whether - conf::treatRecsAsLoops is enabled or not. Same thing for MIDI channels. */ - - for (ID id : channels) - { - channel::Data& ch = model::get().getChannel(id); - ch.state->readActions.store(true); - if (ch.type == ChannelType::MIDI) - ch.state->playStatus.store(ChannelStatus::PLAY); - } - model::swap(model::SwapType::HARD); -} - -/* -------------------------------------------------------------------------- */ - -void toggleActionRec(RecTriggerMode m) -{ - isRecordingAction() ? stopActionRec() : startActionRec(m); -} - -/* -------------------------------------------------------------------------- */ - -bool startInputRec(RecTriggerMode triggerMode, InputRecMode inputMode) -{ - if (!canRec_() || !mh::hasInputRecordableChannels()) - return false; - - if (triggerMode == RecTriggerMode::SIGNAL || inputMode == InputRecMode::FREE) - clock::rewind(); - - if (inputMode == InputRecMode::FREE) - mixer::setEndOfRecCallback([inputMode] { stopInputRec(inputMode); }); - - if (triggerMode == RecTriggerMode::NORMAL) - { - startInputRec_(); - setRecordingInput_(true); - G_DEBUG("Start input rec, NORMAL mode"); - } - else - { - clock::setStatus(ClockStatus::WAITING); - mixer::setSignalCallback([] { - startInputRec_(); - setRecordingInput_(true); - }); - G_DEBUG("Start input rec, SIGNAL mode"); - } - - return true; -} - -/* -------------------------------------------------------------------------- */ - -void stopInputRec(InputRecMode recMode) -{ - setRecordingInput_(false); - - Frame recordedFrames = mixer::stopInputRec(); - - /* When recording in RIGID mode, the amount of recorded frames is always - equal to the current loop length. */ - - if (recMode == InputRecMode::RIGID) - recordedFrames = clock::getFramesInLoop(); - - G_DEBUG("Stop input rec, recordedFrames=" << recordedFrames); - - /* If you stop the Input Recorder in SIGNAL mode before any actual - recording: just clean up everything and return. */ - - if (clock::getStatus() == ClockStatus::WAITING) - { - clock::setStatus(ClockStatus::STOPPED); - mixer::setSignalCallback(nullptr); - return; - } - - /* Finalize recordings. InputRecMode::FREE requires some adjustments. */ - - mh::finalizeInputRec(recordedFrames); - - if (recMode == InputRecMode::FREE) - { - clock::rewind(); - clock::setBpm(clock::calcBpmFromRec(recordedFrames)); - mixer::setEndOfRecCallback(nullptr); - refreshInputRecMode(); // Back to RIGID mode if necessary - } -} - -/* -------------------------------------------------------------------------- */ - -bool toggleInputRec(RecTriggerMode m, InputRecMode i) -{ - if (isRecordingInput()) - { - stopInputRec(i); - return true; - } - return startInputRec(m, i); -} - -/* -------------------------------------------------------------------------- */ - -bool canEnableRecOnSignal() { return !clock::isRunning(); } -bool canEnableFreeInputRec() { return !mh::hasAudioData(); } - -void refreshInputRecMode() -{ - if (!canEnableFreeInputRec()) - conf::conf.inputRecMode = InputRecMode::RIGID; -} -} // namespace giada::m::recManager \ No newline at end of file diff --git a/src/core/recManager.h b/src/core/recManager.h deleted file mode 100644 index 1dd4971..0000000 --- a/src/core/recManager.h +++ /dev/null @@ -1,65 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 diff --git a/src/core/recorder.cpp b/src/core/recorder.cpp index bfee13a..d131ae7 100644 --- a/src/core/recorder.cpp +++ b/src/core/recorder.cpp @@ -25,335 +25,203 @@ * -------------------------------------------------------------------------- */ #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 -#include -#include +#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 f) +bool Recorder::isRecordingAction() const { - model::DataLock lock; - - ActionMap& map = model::getAll(); - 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()); -} -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void init() -{ - actionId_ = IdManager(); - clearAll(); -} - -/* -------------------------------------------------------------------------- */ - -void clearAll() -{ - model::DataLock lock; - model::getAll().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 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()) + 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 channels = actionRecorder.consolidate(); - model::DataLock lock; - model::getAll() = 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(), 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(), id); - Action* pprev = findAction_(model::getAll(), prevId); - Action* pnext = findAction_(model::getAll(), 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()) - 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()[frame].push_back(a); - updateMapPointers_(model::getAll()); + 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& 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(); +/* -------------------------------------------------------------------------- */ - 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(); - - 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* getActionsOnFrame(Frame frame) +void Recorder::setRecordingInput(bool v) { - if (model::getAll().count(frame) == 0) - return nullptr; - return &model::getAll().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 getActionsOnChannel(ID channelId) +void Recorder::startActionRecOnCallback() { - std::vector 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 f) +void Recorder::startInputRec() { - for (auto& [_, actions] : model::getAll()) - 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 diff --git a/src/core/recorder.h b/src/core/recorder.h index d20b4ad..f98a49b 100644 --- a/src/core/recorder.h +++ b/src/core/recorder.h @@ -24,123 +24,65 @@ * * -------------------------------------------------------------------------- */ -#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 -#include -#include -#include -namespace giada::m::recorder +namespace giada::m::model { -using ActionMap = std::map>; +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 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& 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 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* 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 getActionsOnChannel(ID channelId); + bool canRecordActions() const; -/* getClosestAction -Given a frame 'f' returns the closest action. */ + void prepareActionRec(RecTriggerMode); + void startActionRec(); + void startActionRecOnCallback(); + void stopActionRec(ActionRecorder&); -Action getClosestAction(ID channelId, Frame f, int type); + bool prepareInputRec(RecTriggerMode, InputRecMode); + void startInputRec(); + void startInputRecOnCallback(); + void stopInputRec(InputRecMode, int sampleRate); -/* getNewActionId -Returns a new action ID, internally generated. */ +private: + void setRecordingAction(bool v); + void setRecordingInput(bool v); -ID getNewActionId(); -} // namespace giada::m::recorder + model::Model& m_model; + Sequencer& m_sequencer; + MixerHandler& m_mixerHandler; +}; +} // namespace giada::m #endif diff --git a/src/core/recorderHandler.cpp b/src/core/recorderHandler.cpp deleted file mode 100644 index 60ab6db..0000000 --- a/src/core/recorderHandler.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 -#include -#include -#include - -namespace giada::m::recorderHandler -{ -namespace -{ -constexpr int MAX_LIVE_RECS_CHUNK = 128; - -std::vector 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(a1).nextId = a2.id; - const_cast(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(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 actions; - std::unordered_map 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 consolidate() -{ - consolidate_(); - recorder::rec(recs_); - - std::unordered_set 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& 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(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 serializeActions(const recorder::ActionMap& actions) -{ - std::vector out; - for (const auto& kv : actions) - { - for (const Action& a : kv.second) - { - out.push_back({ - a.id, - a.channelId, - a.frame, - a.event.getRaw(), - a.prevId, - a.nextId, - }); - } - } - return out; -} -} // namespace giada::m::recorderHandler \ No newline at end of file diff --git a/src/core/recorderHandler.h b/src/core/recorderHandler.h deleted file mode 100644 index 90f7eee..0000000 --- a/src/core/recorderHandler.h +++ /dev/null @@ -1,90 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_RECORDER_HANDLER_H -#define G_RECORDER_HANDLER_H - -#include "core/midiEvent.h" -#include "core/recorder.h" -#include "core/types.h" -#include - -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 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& as); -std::vector serializeActions(const recorder::ActionMap& as); -} // namespace giada::m::recorderHandler - -#endif diff --git a/src/core/sequencer.cpp b/src/core/sequencer.cpp index 6425e7f..fa60209 100644 --- a/src/core/sequencer.cpp +++ b/src/core/sequencer.cpp @@ -24,73 +24,120 @@ * * -------------------------------------------------------------------------- */ -#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(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(sampleRate)); +} + /* -------------------------------------------------------------------------- */ -Quantizer quantizer; +Frame Sequencer::quantize(Frame f) const +{ + if (!canQuantize()) + return f; + return u::math::quantize(f, m_quantizerStep) % getFramesInLoop(); // No overflow +} /* -------------------------------------------------------------------------- */ -void init() +void Sequencer::reset(int sampleRate) { - quantizer.schedule(Q_ACTION_REWIND, rewindQ_); - clock::rewind(); + model::Sequencer& s = m_model.get().sequencer; + + s.bars = G_DEFAULT_BARS; + s.beats = G_DEFAULT_BEATS; + s.bpm = G_DEFAULT_BPM; + s.quantize = G_DEFAULT_QUANTIZE; + recomputeFrames(sampleRate); // Model swap is done here, no need to call it twice + rewind(); } /* -------------------------------------------------------------------------- */ -void react(const eventDispatcher::EventBuffer& events) +void Sequencer::react(const EventDispatcher::EventBuffer& events) { - for (const eventDispatcher::Event& e : events) + for (const EventDispatcher::Event& e : events) { - if (e.type == eventDispatcher::EventType::SEQUENCER_START) + if (e.type == EventDispatcher::EventType::SEQUENCER_START) { - start(); + if (!m_jackTransport.start()) + rawStart(); break; } - if (e.type == eventDispatcher::EventType::SEQUENCER_STOP) + if (e.type == EventDispatcher::EventType::SEQUENCER_STOP) { - stop(); + if (!m_jackTransport.stop()) + rawStop(); break; } - if (e.type == eventDispatcher::EventType::SEQUENCER_REWIND) + if (e.type == EventDispatcher::EventType::SEQUENCER_REWIND) { - rewind(); + if (!m_jackTransport.setPosition(0)) + rawRewind(); break; } } @@ -98,15 +145,21 @@ void react(const eventDispatcher::EventBuffer& events) /* -------------------------------------------------------------------------- */ -const EventBuffer& advance(Frame bufferSize) +const Sequencer::EventBuffer& Sequencer::advance(Frame bufferSize, const ActionRecorder& actionRecorder) { - eventBuffer_.clear(); + m_eventBuffer.clear(); + + const model::Sequencer& sequencer = m_model.get().sequencer; - const Frame start = clock::getCurrentFrame(); + const Frame start = sequencer.a_getCurrentFrame(); const Frame end = start + bufferSize; - const Frame framesInLoop = clock::getFramesInLoop(); - const Frame framesInBar = clock::getFramesInBar(); - const Frame framesInBeat = clock::getFramesInBeat(); + const Frame framesInLoop = sequencer.framesInLoop; + const Frame framesInBar = sequencer.framesInBar; + const Frame framesInBeat = sequencer.framesInBeat; + const Frame nextFrame = end % framesInLoop; + const int nextBeat = nextFrame / framesInBeat; + + /* Process events in the current block. */ for (Frame i = start, local = 0; i < end; i++, local++) { @@ -115,51 +168,58 @@ const EventBuffer& advance(Frame bufferSize) if (global == 0) { - eventBuffer_.push_back({EventType::FIRST_BEAT, global, local}); - metronome_.trigger(Metronome::Click::BEAT, local); + m_eventBuffer.push_back({EventType::FIRST_BEAT, global, local}); + m_metronome.trigger(Metronome::Click::BEAT, local); } else if (global % framesInBar == 0) { - eventBuffer_.push_back({EventType::BAR, global, local}); - metronome_.trigger(Metronome::Click::BAR, local); + m_eventBuffer.push_back({EventType::BAR, global, local}); + m_metronome.trigger(Metronome::Click::BAR, local); } else if (global % framesInBeat == 0) { - metronome_.trigger(Metronome::Click::BEAT, local); + m_metronome.trigger(Metronome::Click::BEAT, local); } - const std::vector* as = recorder::getActionsOnFrame(global); + const std::vector* 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(start, end), clock::getQuantizerStep()); + /* Advance this and quantizer after the event parsing. */ + + sequencer.a_setCurrentFrame(nextFrame); + sequencer.a_setCurrentBeat(nextBeat); + quantizer.advance(Range(start, end), getQuantizerStep()); - return eventBuffer_; + return m_eventBuffer; } /* -------------------------------------------------------------------------- */ -void render(AudioBuffer& outBuf) +void Sequencer::render(mcl::AudioBuffer& outBuf) { - if (metronome_.running) - metronome_.render(outBuf); + if (m_metronome.running) + m_metronome.render(outBuf); } /* -------------------------------------------------------------------------- */ -void rawStart() +void Sequencer::rawStart() { - switch (clock::getStatus()) + assert(onAboutStart != nullptr); + + const SeqStatus status = getStatus(); + + onAboutStart(status); + + switch (status) { - case ClockStatus::STOPPED: - clock::setStatus(ClockStatus::RUNNING); + case SeqStatus::STOPPED: + setStatus(SeqStatus::RUNNING); break; - case ClockStatus::WAITING: - clock::setStatus(ClockStatus::RUNNING); - recManager::stopActionRec(); + case SeqStatus::WAITING: + setStatus(SeqStatus::RUNNING); break; default: break; @@ -168,68 +228,140 @@ void rawStart() /* -------------------------------------------------------------------------- */ -void rawStop() +void Sequencer::rawStop() { - clock::setStatus(ClockStatus::STOPPED); + assert(onAboutStop != nullptr); - /* If recordings (both input and action) are active deactivate them, but - store the takes. RecManager takes care of it. */ - - if (recManager::isRecordingAction()) - recManager::stopActionRec(); - else if (recManager::isRecordingInput()) - recManager::stopInputRec(conf::conf.inputRecMode); + onAboutStop(); + setStatus(SeqStatus::STOPPED); } /* -------------------------------------------------------------------------- */ -void rawRewind() +void Sequencer::rawRewind() { - if (clock::canQuantize()) + if (canQuantize()) quantizer.trigger(Q_ACTION_REWIND); else - rewindQ_(/*delta=*/0); + rewindQ(/*delta=*/0); } /* -------------------------------------------------------------------------- */ -void start() +void Sequencer::rewind() { -#ifdef WITH_AUDIO_JACK - if (kernelAudio::getAPI() == G_SYS_API_JACK) - kernelAudio::jackStart(); - else -#endif - rawStart(); + const model::Sequencer& c = m_model.get().sequencer; + + c.a_setCurrentFrame(0); + c.a_setCurrentBeat(0); } /* -------------------------------------------------------------------------- */ -void stop() +bool Sequencer::isMetronomeOn() const { return m_metronome.running; } +void Sequencer::toggleMetronome() { m_metronome.running = !m_metronome.running; } +void Sequencer::setMetronome(bool v) { m_metronome.running = v; } + +/* -------------------------------------------------------------------------- */ + +void Sequencer::rewindQ(Frame delta) { -#ifdef WITH_AUDIO_JACK - if (kernelAudio::getAPI() == G_SYS_API_JACK) - kernelAudio::jackStop(); - else -#endif - rawStop(); + rewind(); + m_eventBuffer.push_back({EventType::REWIND, 0, delta}); } /* -------------------------------------------------------------------------- */ -void rewind() +void Sequencer::recomputeFrames(int sampleRate) { -#ifdef WITH_AUDIO_JACK - if (kernelAudio::getAPI() == G_SYS_API_JACK) - kernelAudio::jackSetPosition(0); - else -#endif - rawRewind(); + model::Sequencer& s = m_model.get().sequencer; + + s.framesInLoop = static_cast((sampleRate * (60.0f / s.bpm)) * s.beats); + s.framesInBar = static_cast(s.framesInLoop / (float)s.bars); + s.framesInBeat = static_cast(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 diff --git a/src/core/sequencer.h b/src/core/sequencer.h index 50498c5..e68ca64 100644 --- a/src/core/sequencer.h +++ b/src/core/sequencer.h @@ -28,74 +28,186 @@ #define G_SEQUENCER_H #include "core/eventDispatcher.h" +#include "core/metronome.h" #include "core/quantizer.h" #include -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* 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* actions = nullptr; + }; + + using EventBuffer = RingBuffer; + + 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; + /* 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 onAboutStart; + std::function onAboutStop; + std::function onBpmChange; + +private: + /* rewindQ + Rewinds sequencer, quantized mode. */ + + void rewindQ(Frame delta); + + model::Model& m_model; + Synchronizer& m_synchronizer; + JackTransport& m_jackTransport; + + /* m_eventBuffer + Buffer of events found in each block sent to channels for event parsing. + This is filled during react(). */ + + EventBuffer m_eventBuffer; + + Metronome m_metronome; + + /* m_quantizerStep + Tells how many frames to wait to perform a quantized action. */ + + int m_quantizerStep; +}; +} // namespace giada::m #endif diff --git a/src/core/swapper.h b/src/core/swapper.h deleted file mode 100644 index e29faed..0000000 --- a/src/core/swapper.h +++ /dev/null @@ -1,141 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_SWAPPER_H -#define G_SWAPPER_H - -#include -#include - -namespace giada -{ -/* Swapper -A template class that performs atomic double buffering on type T. */ - -template -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); - } - - /* 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 m_data; - std::atomic m_bits{0}; - int m_index{0}; -}; -} // namespace giada - -#endif \ No newline at end of file diff --git a/src/core/synchronizer.cpp b/src/core/synchronizer.cpp new file mode 100644 index 0000000..1019858 --- /dev/null +++ b/src/core/synchronizer.cpp @@ -0,0 +1,221 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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((m_conf.samplerate / m_conf.midiTCfps) * G_MAX_IO_CHANS); // stereo values +} + +/* -------------------------------------------------------------------------- */ + +void Synchronizer::sendMIDIsync(const model::Sequencer& sequencer) +{ + /* Sending MIDI sync while waiting is meaningless. */ + + if (sequencer.status == SeqStatus::WAITING) + return; + + const int currentFrame = sequencer.a_getCurrentFrame(); + + /* TODO - only Master (_M) is implemented so far. */ + + if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M) + { + if (currentFrame % (sequencer.framesInBeat / 24) == 0) + m_kernelMidi.send(MIDI_CLOCK, -1, -1); + return; + } + + if (m_conf.midiSync == G_MIDI_SYNC_MTC_M) + { + + /* check if a new timecode frame has passed. If so, send MIDI TC + * quarter frames. 8 quarter frames, divided in two branches: + * 1-4 and 5-8. We check timecode frame's parity: if even, send + * range 1-4, if odd send 5-8. */ + + if (currentFrame % m_midiTCrate != 0) // no timecode frame passed + return; + + /* frame low nibble + * frame high nibble + * seconds low nibble + * seconds high nibble */ + + if (m_midiTCframes % 2 == 0) + { + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes & 0x0F) | 0x00, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCframes >> 4) | 0x10, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds & 0x0F) | 0x20, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCseconds >> 4) | 0x30, -1); + } + + /* minutes low nibble + * minutes high nibble + * hours low nibble + * hours high nibble SMPTE frame rate */ + + else + { + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes & 0x0F) | 0x40, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTCminutes >> 4) | 0x50, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours & 0x0F) | 0x60, -1); + m_kernelMidi.send(MIDI_MTC_QUARTER, (m_midiTChours >> 4) | 0x70, -1); + } + + m_midiTCframes++; + + /* check if total timecode frames are greater than timecode fps: + * if so, a second has passed */ + + if (m_midiTCframes > m_conf.midiTCfps) + { + m_midiTCframes = 0; + m_midiTCseconds++; + if (m_midiTCseconds >= 60) + { + m_midiTCminutes++; + m_midiTCseconds = 0; + if (m_midiTCminutes >= 60) + { + m_midiTChours++; + m_midiTCminutes = 0; + } + } + //u::log::print("%d:%d:%d:%d\n", m_midiTChours, m_midiTCminutes, m_midiTCseconds, m_midiTCframes); + } + } +} + +/* -------------------------------------------------------------------------- */ + +void Synchronizer::sendMIDIrewind() +{ + m_midiTCframes = 0; + m_midiTCseconds = 0; + m_midiTCminutes = 0; + m_midiTChours = 0; + + /* For cueing the slave to a particular start point, Quarter Frame messages + are not used. Instead, an MTC Full Frame message should be sent. The Full + Frame is a SysEx message that encodes the entire SMPTE time in one message. */ + + if (m_conf.midiSync == G_MIDI_SYNC_MTC_M) + { + m_kernelMidi.send(MIDI_SYSEX, 0x7F, 0x00); // send msg on channel 0 + m_kernelMidi.send(0x01, 0x01, 0x00); // hours 0 + m_kernelMidi.send(0x00, 0x00, 0x00); // mins, secs, frames 0 + m_kernelMidi.send(MIDI_EOX, -1, -1); // end of sysex + } + else if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M) + m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0); +} + +/* -------------------------------------------------------------------------- */ + +void Synchronizer::sendMIDIstart() +{ + if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M) + { + m_kernelMidi.send(MIDI_START, -1, -1); + m_kernelMidi.send(MIDI_POSITION_PTR, 0, 0); + } +} + +/* -------------------------------------------------------------------------- */ + +void Synchronizer::sendMIDIstop() +{ + if (m_conf.midiSync == G_MIDI_SYNC_CLOCK_M) + m_kernelMidi.send(MIDI_STOP, -1, -1); +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_AUDIO_JACK + +void Synchronizer::recvJackSync(const JackTransport::State& state) +{ + assert(onJackRewind != nullptr); + assert(onJackChangeBpm != nullptr); + assert(onJackStart != nullptr); + assert(onJackStop != nullptr); + + JackTransport::State jackStateCurr = state; + + if (jackStateCurr != m_jackStatePrev) + { + if (jackStateCurr.frame != m_jackStatePrev.frame && jackStateCurr.frame == 0) + { + G_DEBUG("JackState received - rewind to frame 0"); + onJackRewind(); + } + + // jackStateCurr.bpm == 0 if JACK doesn't send that info + if (jackStateCurr.bpm != m_jackStatePrev.bpm && jackStateCurr.bpm > 1.0f) + { + G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm); + onJackChangeBpm(jackStateCurr.bpm); + } + + if (jackStateCurr.running != m_jackStatePrev.running) + { + G_DEBUG("JackState received - running=" << jackStateCurr.running); + jackStateCurr.running ? onJackStart() : onJackStop(); + } + } + + m_jackStatePrev = jackStateCurr; +} + +#endif +} // namespace giada::m diff --git a/src/core/synchronizer.h b/src/core/synchronizer.h new file mode 100644 index 0000000..20f3bee --- /dev/null +++ b/src/core/synchronizer.h @@ -0,0 +1,110 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 + +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 onJackRewind; + std::function onJackChangeBpm; + std::function onJackStart; + std::function 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 diff --git a/src/core/types.h b/src/core/types.h index ea8bab0..14df0aa 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -45,7 +45,7 @@ enum class Thread #ifdef _WIN32 #undef VOID #endif -enum class ClockStatus +enum class SeqStatus { STOPPED, WAITING, @@ -84,7 +84,8 @@ enum class SamplePlayerMode : int SINGLE_BASIC, SINGLE_PRESS, SINGLE_RETRIG, - SINGLE_ENDLESS + SINGLE_ENDLESS, + SINGLE_BASIC_PAUSE }; enum class RecTriggerMode : int @@ -104,6 +105,15 @@ enum class EventType : int AUTO = 0, MANUAL }; + +/* Peak +Audio peak information for two In/Out channels. */ + +struct Peak +{ + float left; + float right; +}; } // namespace giada #endif diff --git a/src/core/wave.cpp b/src/core/wave.cpp index 38ae59c..f29054a 100644 --- a/src/core/wave.cpp +++ b/src/core/wave.cpp @@ -82,8 +82,8 @@ bool Wave::isEdited() const { return m_edited; } /* -------------------------------------------------------------------------- */ -AudioBuffer& Wave::getBuffer() { return m_buffer; } -const AudioBuffer& Wave::getBuffer() const { return m_buffer; } +mcl::AudioBuffer& Wave::getBuffer() { return m_buffer; } +const mcl::AudioBuffer& Wave::getBuffer() const { return m_buffer; } /* -------------------------------------------------------------------------- */ @@ -117,7 +117,7 @@ void Wave::setPath(const std::string& p, int wid) /* -------------------------------------------------------------------------- */ -void Wave::replaceData(AudioBuffer&& b) +void Wave::replaceData(mcl::AudioBuffer&& b) { m_buffer = std::move(b); } diff --git a/src/core/wave.h b/src/core/wave.h index 8f2f697..52c5705 100644 --- a/src/core/wave.h +++ b/src/core/wave.h @@ -27,8 +27,8 @@ #ifndef G_WAVE_H #define G_WAVE_H -#include "core/audioBuffer.h" #include "core/types.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" #include namespace giada::m @@ -54,8 +54,8 @@ public: /* getBuffer Returns a (non-)const reference to the underlying audio buffer. */ - AudioBuffer& getBuffer(); - const AudioBuffer& getBuffer() const; + mcl::AudioBuffer& getBuffer(); + const mcl::AudioBuffer& getBuffer() const; /* setPath Sets new path 'p'. If 'id' != -1 inserts a numeric id next to the file @@ -70,19 +70,19 @@ public: /* replaceData Replaces internal audio buffer with 'b' by moving it. */ - void replaceData(AudioBuffer&& b); + void replaceData(mcl::AudioBuffer&& b); void alloc(Frame size, int channels, int rate, int bits, const std::string& path); ID id; private: - AudioBuffer m_buffer; - int m_rate; - int m_bits; - bool m_logical; // memory only (a take) - bool m_edited; // edited via editor - std::string m_path; // E.g. /path/to/my/sample.wav + mcl::AudioBuffer m_buffer; + int m_rate; + int m_bits; + bool m_logical; // memory only (a take) + bool m_edited; // edited via editor + std::string m_path; // E.g. /path/to/my/sample.wav }; } // namespace giada::m diff --git a/src/core/waveFx.cpp b/src/core/waveFx.cpp index edaf4f4..d68e5a1 100644 --- a/src/core/waveFx.cpp +++ b/src/core/waveFx.cpp @@ -26,6 +26,7 @@ #include "waveFx.h" #include "const.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" #include "utils/log.h" #include "wave.h" #include @@ -86,7 +87,7 @@ int monoToStereo(Wave& w) if (w.getBuffer().countChannels() >= G_MAX_IO_CHANS) return G_RES_OK; - AudioBuffer newData; + mcl::AudioBuffer newData; newData.alloc(w.getBuffer().countFrames(), G_MAX_IO_CHANS); for (int i = 0; i < newData.countFrames(); i++) @@ -124,7 +125,7 @@ void cut(Wave& w, int a, int b) int newSize = w.getBuffer().countFrames() - (b - a); - AudioBuffer newData; + mcl::AudioBuffer newData; newData.alloc(newSize, w.getBuffer().countChannels()); u::log::print("[wfx::cut] cutting from %d to %d\n", a, b); @@ -154,7 +155,7 @@ void trim(Wave& w, Frame a, Frame b) Frame newSize = b - a; - AudioBuffer newData; + mcl::AudioBuffer newData; newData.alloc(newSize, w.getBuffer().countChannels()); u::log::print("[wfx::trim] trimming from %d to %d (area = %d)\n", a, b, b - a); @@ -173,7 +174,7 @@ void paste(const Wave& src, Wave& des, Frame a) { assert(src.getBuffer().countChannels() == des.getBuffer().countChannels()); - AudioBuffer newData; + mcl::AudioBuffer newData; newData.alloc(src.getBuffer().countFrames() + des.getBuffer().countFrames(), des.getBuffer().countChannels()); /* |---original data---|///paste data///|---original data---| diff --git a/src/core/waveFx.h b/src/core/waveFx.h index 9c2c1f0..c0db661 100644 --- a/src/core/waveFx.h +++ b/src/core/waveFx.h @@ -33,6 +33,7 @@ namespace giada::m { class Wave; } + namespace giada::m::wfx { /* Windows fix */ diff --git a/src/core/waveManager.cpp b/src/core/waveManager.cpp index eee58c7..6cfe414 100644 --- a/src/core/waveManager.cpp +++ b/src/core/waveManager.cpp @@ -26,8 +26,8 @@ #include "waveManager.h" #include "const.h" +#include "deps/mcl-audio-buffer/src/audioBuffer.hpp" #include "idManager.h" -#include "model/model.h" #include "patch.h" #include "utils/fs.h" #include "utils/log.h" @@ -37,14 +37,10 @@ #include #include -namespace giada::m::waveManager +namespace giada::m { namespace { -IdManager waveId_; - -/* -------------------------------------------------------------------------- */ - int getBits_(const SF_INFO& header) { if (header.format & SF_FORMAT_PCM_S8) @@ -63,20 +59,60 @@ int getBits_(const SF_INFO& header) return 64; return 0; } + +/* -------------------------------------------------------------------------- */ + +std::string makeWavePath_(const std::string& base, const m::Wave& w, int k) +{ + return base + G_SLASH + w.getBasename(/*ext=*/false) + "-" + std::to_string(k) + w.getExtension(); +} + +/* -------------------------------------------------------------------------- */ + +bool isWavePathUnique_(const m::Wave& skip, const std::string& path, + const std::vector>& 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>& waves) { - waveId_ = IdManager(); + std::string path = base + G_SLASH + w.getBasename(/*ext=*/true); + if (isWavePathUnique_(w, path, waves)) + return path; + + // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp()) + int k = 0; + path = makeWavePath_(base, w, k); + while (!isWavePathUnique_(w, path, waves)) + path = makeWavePath_(base, w, k++); + + return path; +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +void WaveManager::reset() +{ + m_waveId = IdManager(); } /* -------------------------------------------------------------------------- */ -Result createFromFile(const std::string& path, ID id, int samplerate, int quality) +WaveManager::Result WaveManager::createFromFile(const std::string& path, ID id, + int samplerate, int quality) { if (path == "" || u::fs::isDir(path)) { @@ -102,9 +138,9 @@ Result createFromFile(const std::string& path, ID id, int samplerate, int qualit return {G_RES_ERR_WRONG_DATA}; } - waveId_.set(id); + m_waveId.set(id); - std::unique_ptr wave = std::make_unique(waveId_.generate(id)); + std::unique_ptr wave = std::make_unique(m_waveId.generate(id)); wave->alloc(header.frames, header.channels, header.samplerate, getBits_(header), path); if (sf_readf_float(fileIn, wave->getBuffer()[0], header.frames) != header.frames) @@ -130,10 +166,10 @@ Result createFromFile(const std::string& path, ID id, int samplerate, int qualit /* -------------------------------------------------------------------------- */ -std::unique_ptr createEmpty(int frames, int channels, int samplerate, +std::unique_ptr WaveManager::createEmpty(int frames, int channels, int samplerate, const std::string& name) { - std::unique_ptr wave = std::make_unique(waveId_.generate()); + std::unique_ptr wave = std::make_unique(m_waveId.generate()); wave->alloc(frames, channels, samplerate, G_DEFAULT_BIT_DEPTH, name); wave->setLogical(true); @@ -145,12 +181,12 @@ std::unique_ptr createEmpty(int frames, int channels, int samplerate, /* -------------------------------------------------------------------------- */ -std::unique_ptr createFromWave(const Wave& src, int a, int b) +std::unique_ptr WaveManager::createFromWave(const Wave& src, int a, int b) { int channels = src.getBuffer().countChannels(); int frames = b - a; - std::unique_ptr wave = std::make_unique(waveId_.generate()); + std::unique_ptr wave = std::make_unique(m_waveId.generate()); wave->alloc(frames, channels, src.getRate(), src.getBits(), src.getPath()); wave->getBuffer().set(src.getBuffer(), frames); wave->setLogical(true); @@ -162,31 +198,24 @@ std::unique_ptr createFromWave(const Wave& src, int a, int b) /* -------------------------------------------------------------------------- */ -std::unique_ptr deserializeWave(const patch::Wave& w, int samplerate, int quality) +std::unique_ptr 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(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(ceil(w.getBuffer().countFrames() * ratio)); - AudioBuffer newData; + mcl::AudioBuffer newData; newData.alloc(newSizeFrames, w.getBuffer().countChannels()); SRC_DATA src_data; @@ -213,7 +242,7 @@ int resample(Wave& w, int quality, int samplerate) /* -------------------------------------------------------------------------- */ -int save(const Wave& w, const std::string& path) +int WaveManager::save(const Wave& w, const std::string& path) { SF_INFO header; header.samplerate = w.getRate(); @@ -235,4 +264,4 @@ int save(const Wave& w, const std::string& path) return G_RES_OK; } -} // namespace giada::m::waveManager +} // namespace giada::m diff --git a/src/core/waveManager.h b/src/core/waveManager.h index 36fdf20..d0142ea 100644 --- a/src/core/waveManager.h +++ b/src/core/waveManager.h @@ -27,65 +27,72 @@ #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 #include 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>& waves); + +/* -------------------------------------------------------------------------- */ + +class WaveManager final { - int status; - std::unique_ptr wave = nullptr; -}; -/* init -Initializes internal data. */ +public: + struct Result + { + int status; + std::unique_ptr 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 createEmpty(int frames, int channels, int samplerate, - const std::string& name); + std::unique_ptr 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 createFromWave(const Wave& src, int a, int b); + std::unique_ptr 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 deserializeWave(const patch::Wave& w, int samplerate, int quality); -const patch::Wave serializeWave(const Wave& w); -Wave* hydrateWave(ID waveId); + std::unique_ptr 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 diff --git a/src/core/weakAtomic.h b/src/core/weakAtomic.h index d3ae7df..f2bca23 100644 --- a/src/core/weakAtomic.h +++ b/src/core/weakAtomic.h @@ -28,6 +28,7 @@ #define G_WEAK_ATOMIC_H #include +#include namespace giada { @@ -38,25 +39,25 @@ public: WeakAtomic() = default; WeakAtomic(T t) - : m_value(t) + : m_atomic(t) + , m_value(t) { } WeakAtomic(const WeakAtomic& o) - : m_value(o.m_value.load(std::memory_order_relaxed)) + : m_atomic(o.load()) + , m_value(o.m_value) { } - WeakAtomic(WeakAtomic&& o) - : m_value(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed) - { - } + WeakAtomic(WeakAtomic&& o) = delete; WeakAtomic& operator=(const WeakAtomic& o) { if (this == &o) return *this; - m_value.store(o.m_value.load(std::memory_order_relaxed), std::memory_order_relaxed); + store(o.load()); + m_value = o.m_value; return *this; } @@ -64,16 +65,22 @@ public: T load() const { - return m_value.load(std::memory_order_relaxed); + return m_atomic.load(std::memory_order_relaxed); } void store(T t) { - return m_value.store(t, std::memory_order_relaxed); + m_atomic.store(t, std::memory_order_relaxed); + if (onChange != nullptr && t != m_value) + onChange(t); + m_value = t; } - private: - std::atomic m_value; + std::function onChange = nullptr; + +private: + std::atomic m_atomic; + T m_value; }; } // namespace giada diff --git a/src/glue/actionEditor.cpp b/src/glue/actionEditor.cpp index 86a498d..248a604 100644 --- a/src/glue/actionEditor.cpp +++ b/src/glue/actionEditor.cpp @@ -24,17 +24,20 @@ * * -------------------------------------------------------------------------- */ -#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 +extern giada::m::Engine g_engine; + namespace giada::c::actionEditor { namespace @@ -57,18 +60,16 @@ First action ever? Add actions at boundaries. */ void recordFirstEnvelopeAction_(ID channelId, Frame frame, int value) { - namespace mr = m::recorder; - // TODO - use MidiEvent(float) m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, G_MAX_VELOCITY); m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value); - const m::Action a1 = mr::rec(channelId, 0, e1); - const m::Action a2 = mr::rec(channelId, frame, e2); - const m::Action a3 = mr::rec(channelId, m::clock::getFramesInLoop() - 1, e1); + const m::Action a1 = g_engine.actionRecorder.rec(channelId, 0, e1); + const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2); + const m::Action a3 = g_engine.actionRecorder.rec(channelId, g_engine.sequencer.getFramesInLoop() - 1, e1); - mr::updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin) - mr::updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id); - mr::updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end) + g_engine.actionRecorder.updateSiblings(a1.id, /*prev=*/a3.id, /*next=*/a2.id); // Circular loop (begin) + g_engine.actionRecorder.updateSiblings(a2.id, /*prev=*/a1.id, /*next=*/a3.id); + g_engine.actionRecorder.updateSiblings(a3.id, /*prev=*/a2.id, /*next=*/a1.id); // Circular loop (end) } /* -------------------------------------------------------------------------- */ @@ -79,9 +80,7 @@ Vertical envelope points are forbidden. */ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value) { - namespace mr = m::recorder; - - const m::Action a1 = mr::getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE); + const m::Action a1 = g_engine.actionRecorder.getClosestAction(channelId, frame, m::MidiEvent::ENVELOPE); const m::Action a3 = a1.next != nullptr ? *a1.next : m::Action{}; assert(a1.isValid()); @@ -93,9 +92,9 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value) // TODO - use MidiEvent(float) m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value); - const m::Action a2 = mr::rec(channelId, frame, e2); + const m::Action a2 = g_engine.actionRecorder.rec(channelId, frame, e2); - mr::updateSiblings(a2.id, a1.id, a3.id); + g_engine.actionRecorder.updateSiblings(a2.id, a1.id, a3.id); } /* -------------------------------------------------------------------------- */ @@ -103,7 +102,7 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value) bool isSinglePressMode_(ID channelId) { /* TODO - use m::model getChannel utils (to be added) */ - return m::model::get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS; + return g_engine.model.get().getChannel(channelId).samplePlayer->mode == SamplePlayerMode::SINGLE_PRESS; } } // namespace @@ -111,7 +110,7 @@ bool isSinglePressMode_(ID channelId) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -SampleData::SampleData(const m::samplePlayer::Data& s) +SampleData::SampleData(const m::SamplePlayer& s) : channelMode(s.mode) , isLoopMode(s.isAnyLoopMode()) { @@ -119,37 +118,52 @@ SampleData::SampleData(const m::samplePlayer::Data& s) /* -------------------------------------------------------------------------- */ -Data::Data(const m::channel::Data& c) +Data::Data(const m::Channel& c) : channelId(c.id) , channelName(c.name) -, actions(m::recorder::getActionsOnChannel(c.id)) +, framesInSeq(g_engine.sequencer.getFramesInSeq()) +, framesInBeat(g_engine.sequencer.getFramesInBeat()) +, framesInBar(g_engine.sequencer.getFramesInBar()) +, framesInLoop(g_engine.sequencer.getFramesInLoop()) +, actions(g_engine.actionRecorder.getActionsOnChannel(c.id)) { if (c.type == ChannelType::SAMPLE) sample = std::make_optional(c.samplePlayer.value()); } +/* -------------------------------------------------------------------------- */ + +Frame Data::getCurrentFrame() const +{ + return g_engine.sequencer.getCurrentFrame(); +} + +/* -------------------------------------------------------------------------- */ + +bool Data::isChannelPlaying() const +{ + return g_engine.model.get().getChannel(channelId).isPlaying(); +} + /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ Data getData(ID channelId) { - return Data(m::model::get().getChannel(channelId)); + return Data(g_engine.model.get().getChannel(channelId)); } /* -------------------------------------------------------------------------- */ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2) { - namespace mr = m::recorder; - namespace cr = c::recorder; - if (f2 == 0) f2 = f1 + G_DEFAULT_ACTION_SIZE; /* Avoid frame overflow. */ - Frame overflow = f2 - (m::clock::getFramesInLoop()); + Frame overflow = f2 - (g_engine.sequencer.getFramesInLoop()); if (overflow > 0) { f2 -= overflow; @@ -159,7 +173,7 @@ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2) m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity); m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity); - mr::rec(channelId, f1, f2, e1, e2); + g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2); recorder::updateChannel(channelId, /*updateActionEditor=*/false); } @@ -168,8 +182,6 @@ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2) void deleteMidiAction(ID channelId, const m::Action& a) { - namespace mr = m::recorder; - assert(a.isValid()); assert(a.event.getStatus() == m::MidiEvent::NOTE_ON); @@ -179,10 +191,10 @@ void deleteMidiAction(ID channelId, const m::Action& a) if (a.next != nullptr) { events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN); - mr::deleteAction(a.id, a.next->id); + g_engine.actionRecorder.deleteAction(a.id, a.next->id); } else - mr::deleteAction(a.id); + g_engine.actionRecorder.deleteAction(a.id); recorder::updateChannel(channelId, /*updateActionEditor=*/false); } @@ -192,9 +204,7 @@ void deleteMidiAction(ID channelId, const m::Action& a) void updateMidiAction(ID channelId, const m::Action& a, int note, int velocity, Frame f1, Frame f2) { - namespace mr = m::recorder; - - mr::deleteAction(a.id, a.next->id); + g_engine.actionRecorder.deleteAction(a.id, a.next->id); recordMidiAction(channelId, note, velocity, f1, f2); } @@ -202,20 +212,18 @@ void updateMidiAction(ID channelId, const m::Action& a, int note, int velocity, void recordSampleAction(ID channelId, int type, Frame f1, Frame f2) { - namespace mr = m::recorder; - if (isSinglePressMode_(channelId)) { if (f2 == 0) f2 = f1 + G_DEFAULT_ACTION_SIZE; m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, 0, 0); m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, 0, 0); - mr::rec(channelId, f1, f2, e1, e2); + g_engine.actionRecorder.rec(channelId, f1, f2, e1, e2); } else { m::MidiEvent e1 = m::MidiEvent(type, 0, 0); - mr::rec(channelId, f1, e1); + g_engine.actionRecorder.rec(channelId, f1, e1); } recorder::updateChannel(channelId, /*updateActionEditor=*/false); @@ -226,12 +234,10 @@ void recordSampleAction(ID channelId, int type, Frame f1, Frame f2) void updateSampleAction(ID channelId, const m::Action& a, int type, Frame f1, Frame f2) { - namespace mr = m::recorder; - if (isSinglePressMode_(channelId)) - mr::deleteAction(a.id, a.next->id); + g_engine.actionRecorder.deleteAction(a.id, a.next->id); else - mr::deleteAction(a.id); + g_engine.actionRecorder.deleteAction(a.id); recordSampleAction(channelId, type, f1, f2); } @@ -240,13 +246,10 @@ void updateSampleAction(ID channelId, const m::Action& a, int type, void deleteSampleAction(ID channelId, const m::Action& a) { - namespace mr = m::recorder; - namespace cr = c::recorder; - if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo - mr::deleteAction(a.id, a.next->id); + g_engine.actionRecorder.deleteAction(a.id, a.next->id); else - mr::deleteAction(a.id); + g_engine.actionRecorder.deleteAction(a.id); recorder::updateChannel(channelId, /*updateActionEditor=*/false); } @@ -255,16 +258,13 @@ void deleteSampleAction(ID channelId, const m::Action& a) void recordEnvelopeAction(ID channelId, Frame f, int value) { - namespace mr = m::recorder; - namespace cr = c::recorder; - assert(value >= 0 && value <= G_MAX_VELOCITY); /* First action ever? Add actions at boundaries. Else, find action right before frame 'f' and inject a new action in there. Vertical envelope points are forbidden for now. */ - if (!mr::hasActions(channelId, m::MidiEvent::ENVELOPE)) + if (!g_engine.actionRecorder.hasActions(channelId, m::MidiEvent::ENVELOPE)) recordFirstEnvelopeAction_(channelId, f, value); else recordNonFirstEnvelopeAction_(channelId, f, value); @@ -276,26 +276,17 @@ void recordEnvelopeAction(ID channelId, Frame f, int value) void deleteEnvelopeAction(ID channelId, const m::Action& a) { - namespace mr = m::recorder; - namespace cr = c::recorder; - namespace mrh = m::recorderHandler; - /* Deleting a boundary action wipes out everything. If is volume, remember to restore _i and _d members in channel. */ /* TODO - move this to c::*/ /* TODO - FIX*/ - if (mrh::isBoundaryEnvelopeAction(a)) + if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a)) { if (a.isVolumeEnvelope()) { - /* - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) - { - c.volume_i = 1.0; - c.volume_d = 0.0; - });*/ + // TODO reset all volume vars to 1.0 } - mr::clearActions(channelId, a.event.getStatus()); + g_engine.actionRecorder.clearActions(channelId, a.event.getStatus()); } else { @@ -310,12 +301,12 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a) /* Original status: a1--->a--->a3 Modified status: a1-------->a3 Order is important, here: first update siblings, then delete the action. - Otherwise mr::deleteAction would complain of missing prevId/nextId no - longer found. */ + Otherwise ActionRecorder::deleteAction() would complain of missing + prevId/nextId no longer found. */ - mr::updateSiblings(a1.id, a1prev.id, a3.id); - mr::updateSiblings(a3.id, a1.id, a3next.id); - mr::deleteAction(a.id); + g_engine.actionRecorder.updateSiblings(a1.id, a1prev.id, a3.id); + g_engine.actionRecorder.updateSiblings(a3.id, a1.id, a3next.id); + g_engine.actionRecorder.deleteAction(a.id); } recorder::updateChannel(channelId, /*updateActionEditor=*/false); @@ -325,15 +316,11 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a) void updateEnvelopeAction(ID channelId, const m::Action& a, Frame f, int value) { - namespace mr = m::recorder; - namespace cr = c::recorder; - namespace mrh = m::recorderHandler; - /* Update the action directly if it is a boundary one. Else, delete the previous one and record a new action. */ - if (mrh::isBoundaryEnvelopeAction(a)) - mr::updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value)); + if (g_engine.actionRecorder.isBoundaryEnvelopeAction(a)) + g_engine.actionRecorder.updateEvent(a.id, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value)); else { deleteEnvelopeAction(channelId, a); @@ -345,18 +332,16 @@ void updateEnvelopeAction(ID channelId, const m::Action& a, Frame f, int value) std::vector 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 diff --git a/src/glue/actionEditor.h b/src/glue/actionEditor.h index ab4fab7..899c060 100644 --- a/src/glue/actionEditor.h +++ b/src/glue/actionEditor.h @@ -35,20 +35,15 @@ namespace giada::m { struct Action; -} -namespace giada::m::channel -{ -struct Data; -} -namespace giada::m::samplePlayer -{ -struct Data; -} +class SamplePlayer; +class Channel; +} // namespace giada::m + namespace giada::c::actionEditor { struct SampleData { - SampleData(const m::samplePlayer::Data&); + SampleData(const m::SamplePlayer&); SamplePlayerMode channelMode; bool isLoopMode; @@ -57,10 +52,17 @@ struct SampleData struct Data { Data() = default; - Data(const m::channel::Data&); + Data(const m::Channel&); + + Frame getCurrentFrame() const; + bool isChannelPlaying() const; ID channelId; std::string channelName; + Frame framesInSeq; + Frame framesInBeat; + Frame framesInBar; + Frame framesInLoop; std::vector actions; std::optional sample; diff --git a/src/glue/channel.cpp b/src/glue/channel.cpp index 5e37292..2928b6e 100644 --- a/src/glue/channel.cpp +++ b/src/glue/channel.cpp @@ -25,22 +25,27 @@ * -------------------------------------------------------------------------- */ #include "gui/elems/mainWindow/keyboard/channel.h" -#include "channel.h" -#include "core/clock.h" +#include "core/actions/actionRecorder.h" +#include "core/channels/channelManager.h" #include "core/conf.h" +#include "core/engine.h" #include "core/kernelAudio.h" #include "core/mixer.h" #include "core/mixerHandler.h" #include "core/model/model.h" +#include "core/patch.h" #include "core/plugins/plugin.h" #include "core/plugins/pluginHost.h" -#include "core/recManager.h" +#include "core/plugins/pluginManager.h" #include "core/recorder.h" #include "core/wave.h" #include "core/waveManager.h" +#include "glue/channel.h" +#include "glue/main.h" #include "gui/dialogs/mainWindow.h" #include "gui/dialogs/sampleEditor.h" #include "gui/dialogs/warnings.h" +#include "gui/dispatcher.h" #include "gui/elems/basics/dial.h" #include "gui/elems/basics/input.h" #include "gui/elems/mainWindow/keyboard/channelButton.h" @@ -53,7 +58,8 @@ #include "gui/elems/sampleEditor/volumeTool.h" #include "gui/elems/sampleEditor/waveTools.h" #include "gui/elems/sampleEditor/waveform.h" -#include "main.h" +#include "gui/ui.h" +#include "src/core/actions/actions.h" #include "utils/fs.h" #include "utils/gui.h" #include "utils/log.h" @@ -62,7 +68,8 @@ #include #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::channel { @@ -85,8 +92,8 @@ void printLoadError_(int res) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -// TODO - just pass const channel::Data& -SampleData::SampleData(const m::channel::Data& ch) +// TODO - just pass const Channel& +SampleData::SampleData(const m::Channel& ch) : waveId(ch.samplePlayer->getWaveId()) , mode(ch.samplePlayer->mode) , isLoop(ch.samplePlayer->isAnyLoopMode()) @@ -95,7 +102,7 @@ SampleData::SampleData(const m::channel::Data& ch) { } -Frame SampleData::getTracker() const { return m_channel->state->tracker.load(); } +Frame SampleData::getTracker() const { return m_channel->shared->tracker.load(); } /* TODO - useless methods, turn them into member vars */ Frame SampleData::getBegin() const { return m_channel->samplePlayer->begin; } Frame SampleData::getEnd() const { return m_channel->samplePlayer->end; } @@ -104,7 +111,7 @@ bool SampleData::getOverdubProtection() const { return m_channel->audioReceiver /* -------------------------------------------------------------------------- */ -MidiData::MidiData(const m::channel::Data& m) +MidiData::MidiData(const m::Channel& m) : m_channel(&m) { } @@ -115,8 +122,9 @@ int MidiData::getFilter() const { return m_channel->midiSender->filter; } /* -------------------------------------------------------------------------- */ -Data::Data(const m::channel::Data& c) -: id(c.id) +Data::Data(const m::Channel& c) +: viewDispatcher(g_ui.dispatcher) +, id(c.id) , columnId(c.columnId) #ifdef WITH_VST , plugins(c.plugins) @@ -136,14 +144,14 @@ Data::Data(const m::channel::Data& c) midi = std::make_optional(c); } -ChannelStatus Data::getPlayStatus() const { return m_channel.state->playStatus.load(); } -ChannelStatus Data::getRecStatus() const { return m_channel.state->recStatus.load(); } -bool Data::getReadActions() const { return m_channel.state->readActions.load(); } -bool Data::isRecordingInput() const { return m::recManager::isRecordingInput(); } -bool Data::isRecordingAction() const { return m::recManager::isRecordingAction(); } +ChannelStatus Data::getPlayStatus() const { return m_channel.shared->playStatus.load(); } +ChannelStatus Data::getRecStatus() const { return m_channel.shared->recStatus.load(); } +bool Data::getReadActions() const { return m_channel.shared->readActions.load(); } +bool Data::isRecordingInput() const { return g_engine.recorder.isRecordingInput(); } +bool Data::isRecordingAction() const { return g_engine.recorder.isRecordingAction(); } /* TODO - useless methods, turn them into member vars */ -bool Data::getSolo() const { return m_channel.solo; } -bool Data::getMute() const { return m_channel.mute; } +bool Data::getSolo() const { return m_channel.isSoloed(); } +bool Data::getMute() const { return m_channel.isMuted(); } bool Data::isArmed() const { return m_channel.armed; } /* -------------------------------------------------------------------------- */ @@ -152,13 +160,13 @@ bool Data::isArmed() const { return m_channel.armed; } Data getData(ID channelId) { - return Data(m::model::get().getChannel(channelId)); + return Data(g_engine.model.get().getChannel(channelId)); } std::vector getChannels() { std::vector out; - for (const m::channel::Data& ch : m::model::get().channels) + for (const m::Channel& ch : g_engine.model.get().channels) if (!ch.isInternal()) out.push_back(Data(ch)); return out; @@ -168,46 +176,60 @@ std::vector getChannels() int loadChannel(ID channelId, const std::string& fname) { + m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0, + g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality); + + if (res.status != G_RES_OK) + { + printLoadError_(res.status); + return res.status; + } + /* Save the patch and take the last browser's dir in order to re-use it the next time. */ - m::conf::conf.samplePath = u::fs::dirname(fname); - - int res = m::mh::loadChannel(channelId, fname); - if (res != G_RES_OK) - printLoadError_(res); + g_engine.conf.data.samplePath = u::fs::dirname(fname); + g_engine.mixerHandler.loadChannel(channelId, std::move(res.wave)); - return res; + return G_RES_OK; } /* -------------------------------------------------------------------------- */ void addChannel(ID columnId, ChannelType type) { - m::mh::addChannel(type, columnId); + g_engine.mixerHandler.addChannel(type, columnId, g_engine.kernelAudio.getBufferSize(), g_engine.channelManager); } /* -------------------------------------------------------------------------- */ -void addAndLoadChannel(ID columnId, const std::string& fpath) +void addAndLoadChannel(ID columnId, const std::string& fname) { - int res = m::mh::addAndLoadChannel(columnId, fpath); - if (res != G_RES_OK) - printLoadError_(res); + m::WaveManager::Result res = g_engine.waveManager.createFromFile(fname, /*id=*/0, + g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality); + if (res.status == G_RES_OK) + g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(), + g_engine.channelManager); + else + printLoadError_(res.status); } -void addAndLoadChannels(ID columnId, const std::vector& fpaths) +void addAndLoadChannels(ID columnId, const std::vector& fnames) { - if (fpaths.size() == 1) - return addAndLoadChannel(columnId, fpaths[0]); - bool errors = false; - for (const std::string& f : fpaths) - if (m::mh::addAndLoadChannel(columnId, f) != G_RES_OK) + for (const std::string& f : fnames) + { + m::WaveManager::Result res = g_engine.waveManager.createFromFile(f, /*id=*/0, + g_engine.kernelAudio.getSampleRate(), g_engine.conf.data.rsmpQuality); + if (res.status == G_RES_OK) + g_engine.mixerHandler.addAndLoadChannel(columnId, std::move(res.wave), g_engine.kernelAudio.getBufferSize(), + g_engine.channelManager); + else errors = true; + } if (errors) - v::gdAlert("Some files weren't loaded sucessfully."); + v::gdAlert("Some files weren't loaded successfully."); } /* -------------------------------------------------------------------------- */ @@ -216,9 +238,16 @@ void deleteChannel(ID channelId) { if (!v::gdConfirmWin("Warning", "Delete channel: are you sure?")) return; - u::gui::closeAllSubwindows(); - m::recorder::clearChannel(channelId); - m::mh::deleteChannel(channelId); + g_ui.closeAllSubwindows(); + +#ifdef WITH_VST + const std::vector plugins = g_engine.model.get().getChannel(channelId).plugins; +#endif + g_engine.mixerHandler.deleteChannel(channelId); + g_engine.actionRecorder.clearChannel(channelId); +#ifdef WITH_VST + g_engine.pluginHost.freePlugins(plugins); +#endif } /* -------------------------------------------------------------------------- */ @@ -227,58 +256,66 @@ void freeChannel(ID channelId) { if (!v::gdConfirmWin("Warning", "Free channel: are you sure?")) return; - u::gui::closeAllSubwindows(); - m::recorder::clearChannel(channelId); - m::mh::freeChannel(channelId); + g_ui.closeAllSubwindows(); + g_engine.actionRecorder.clearChannel(channelId); + g_engine.mixerHandler.freeChannel(channelId); } /* -------------------------------------------------------------------------- */ void setInputMonitor(ID channelId, bool value) { - m::model::get().getChannel(channelId).audioReceiver->inputMonitor = value; - m::model::swap(m::model::SwapType::SOFT); + g_engine.model.get().getChannel(channelId).audioReceiver->inputMonitor = value; + g_engine.model.swap(m::model::SwapType::SOFT); } /* -------------------------------------------------------------------------- */ void setOverdubProtection(ID channelId, bool value) { - m::channel::Data& ch = m::model::get().getChannel(channelId); + m::Channel& ch = g_engine.model.get().getChannel(channelId); ch.audioReceiver->overdubProtection = value; if (value == true && ch.armed) ch.armed = false; - m::model::swap(m::model::SwapType::SOFT); + g_engine.model.swap(m::model::SwapType::SOFT); } /* -------------------------------------------------------------------------- */ void cloneChannel(ID channelId) { - m::mh::cloneChannel(channelId); + g_engine.actionRecorder.cloneActions(channelId, g_engine.channelManager.getNextId()); +#ifdef WITH_VST + g_engine.mixerHandler.cloneChannel(channelId, g_engine.patch.data.samplerate, + g_engine.kernelAudio.getBufferSize(), g_engine.channelManager, g_engine.waveManager, + g_engine.sequencer, g_engine.pluginManager); +#else + g_engine.mixerHandler.cloneChannel(channelId, g_engine.kernelAudio.getBufferSize(), + g_engine.channelManager, g_engine.waveManager); +#endif } /* -------------------------------------------------------------------------- */ void setSamplePlayerMode(ID channelId, SamplePlayerMode mode) { - m::model::get().getChannel(channelId).samplePlayer->mode = mode; - m::model::swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method - u::gui::refreshActionEditor(); + g_engine.model.get().getChannel(channelId).samplePlayer->mode = mode; + g_engine.model.swap(m::model::SwapType::HARD); // TODO - SOFT should be enough, fix geChannel refresh method + g_ui.refreshSubWindow(WID_ACTION_EDITOR); } /* -------------------------------------------------------------------------- */ void setHeight(ID channelId, Pixel p) { - m::model::get().getChannel(channelId).height = p; - m::model::swap(m::model::SwapType::SOFT); + g_engine.model.get().getChannel(channelId).height = p; + g_engine.model.swap(m::model::SwapType::SOFT); } /* -------------------------------------------------------------------------- */ void setName(ID channelId, const std::string& name) { - m::mh::renameChannel(channelId, name); + g_engine.mixerHandler.renameChannel(channelId, name); } } // namespace giada::c::channel diff --git a/src/glue/channel.h b/src/glue/channel.h index cddf96c..0a380b6 100644 --- a/src/glue/channel.h +++ b/src/glue/channel.h @@ -38,12 +38,18 @@ namespace giada::m { class Plugin; } + +namespace giada::v +{ +class Dispatcher; +} + namespace giada::c::channel { struct SampleData { SampleData() = delete; - SampleData(const m::channel::Data&); + SampleData(const m::Channel&); Frame getTracker() const; Frame getBegin() const; @@ -56,25 +62,25 @@ struct SampleData bool isLoop; float pitch; - private: - const m::channel::Data* m_channel; +private: + const m::Channel* m_channel; }; struct MidiData { MidiData() = delete; - MidiData(const m::channel::Data&); + MidiData(const m::Channel&); bool isOutputEnabled() const; int getFilter() const; - private: - const m::channel::Data* m_channel; +private: + const m::Channel* m_channel; }; struct Data { - Data(const m::channel::Data&); + Data(const m::Channel&); bool getMute() const; bool getSolo() const; @@ -85,6 +91,8 @@ struct Data bool isRecordingInput() const; bool isRecordingAction() const; + v::Dispatcher& viewDispatcher; + ID id; ID columnId; #ifdef WITH_VST @@ -101,8 +109,8 @@ struct Data std::optional sample; std::optional midi; - private: - const m::channel::Data& m_channel; +private: + const m::Channel& m_channel; }; /* getChannels diff --git a/src/glue/config.cpp b/src/glue/config.cpp index 9232c0f..088d0f7 100644 --- a/src/glue/config.cpp +++ b/src/glue/config.cpp @@ -24,11 +24,26 @@ * * -------------------------------------------------------------------------- */ -#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 + +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::config { @@ -36,7 +51,7 @@ namespace { AudioDeviceData getAudioDeviceData_(DeviceType type, size_t index, int channelsCount, int channelsStart) { - for (const m::kernelAudio::Device& device : m::kernelAudio::getDevices()) + for (const m::KernelAudio::Device& device : g_engine.kernelAudio.getDevices()) if (device.index == index) return AudioDeviceData(type, device, channelsCount, channelsStart); return AudioDeviceData(); @@ -47,7 +62,7 @@ AudioDeviceData getAudioDeviceData_(DeviceType type, size_t index, int channelsC /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -AudioDeviceData::AudioDeviceData(DeviceType type, const m::kernelAudio::Device& device, +AudioDeviceData::AudioDeviceData(DeviceType type, const m::KernelAudio::Device& device, int channelsCount, int channelsStart) : type(type) , index(device.index) @@ -100,39 +115,39 @@ AudioData getAudioData() #if defined(G_OS_LINUX) - if (m::kernelAudio::hasAPI(RtAudio::LINUX_ALSA)) + if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_ALSA)) audioData.apis[G_SYS_API_ALSA] = "ALSA"; - if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK)) + if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK)) audioData.apis[G_SYS_API_JACK] = "Jack"; - if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE)) + if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE)) audioData.apis[G_SYS_API_PULSE] = "PulseAudio"; #elif defined(G_OS_FREEBSD) - if (m::kernelAudio::hasAPI(RtAudio::UNIX_JACK)) + if (g_engine.kernelAudio.hasAPI(RtAudio::UNIX_JACK)) audioData.apis[G_SYS_API_JACK] = "Jack"; - if (m::kernelAudio::hasAPI(RtAudio::LINUX_PULSE)) + if (g_engine.kernelAudio.hasAPI(RtAudio::LINUX_PULSE)) audioData.apis[G_SYS_API_PULSE] = "PulseAudio"; #elif defined(G_OS_WINDOWS) - if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_DS)) + if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_DS)) audioData.apis[G_SYS_API_DS] = "DirectSound"; - if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_ASIO)) + if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_ASIO)) audioData.apis[G_SYS_API_ASIO] = "ASIO"; - if (m::kernelAudio::hasAPI(RtAudio::WINDOWS_WASAPI)) + if (g_engine.kernelAudio.hasAPI(RtAudio::WINDOWS_WASAPI)) audioData.apis[G_SYS_API_WASAPI] = "WASAPI"; #elif defined(G_OS_MAC) - if (m::kernelAudio::hasAPI(RtAudio::MACOSX_CORE)) + if (g_engine.kernelAudio.hasAPI(RtAudio::MACOSX_CORE)) audioData.apis[G_SYS_API_CORE] = "CoreAudio"; #endif - std::vector devices = m::kernelAudio::getDevices(); + std::vector devices = g_engine.kernelAudio.getDevices(); - for (const m::kernelAudio::Device& device : devices) + for (const m::KernelAudio::Device& device : devices) { if (device.maxOutputChannels > 0) audioData.outputDevices.push_back(AudioDeviceData(DeviceType::OUTPUT, device, G_MAX_IO_CHANS, 0)); @@ -140,37 +155,177 @@ AudioData getAudioData() audioData.inputDevices.push_back(AudioDeviceData(DeviceType::INPUT, device, 1, 0)); } - audioData.api = m::conf::conf.soundSystem; - audioData.bufferSize = m::conf::conf.buffersize; - audioData.sampleRate = m::conf::conf.samplerate; - audioData.limitOutput = m::conf::conf.limitOutput; - audioData.recTriggerLevel = m::conf::conf.recTriggerLevel; - audioData.resampleQuality = m::conf::conf.rsmpQuality; + audioData.api = g_engine.conf.data.soundSystem; + audioData.bufferSize = g_engine.conf.data.buffersize; + audioData.sampleRate = g_engine.conf.data.samplerate; + audioData.limitOutput = g_engine.conf.data.limitOutput; + audioData.recTriggerLevel = g_engine.conf.data.recTriggerLevel; + audioData.resampleQuality = g_engine.conf.data.rsmpQuality; audioData.outputDevice = getAudioDeviceData_(DeviceType::OUTPUT, - m::conf::conf.soundDeviceOut, m::conf::conf.channelsOutCount, - m::conf::conf.channelsOutStart); + g_engine.conf.data.soundDeviceOut, g_engine.conf.data.channelsOutCount, + g_engine.conf.data.channelsOutStart); audioData.inputDevice = getAudioDeviceData_(DeviceType::INPUT, - m::conf::conf.soundDeviceIn, m::conf::conf.channelsInCount, - m::conf::conf.channelsInStart); + g_engine.conf.data.soundDeviceIn, g_engine.conf.data.channelsInCount, + g_engine.conf.data.channelsInStart); return audioData; } /* -------------------------------------------------------------------------- */ +MidiData getMidiData() +{ + MidiData midiData; + +#if defined(G_OS_LINUX) + + if (g_engine.kernelMidi.hasAPI(RtMidi::LINUX_ALSA)) + midiData.apis[G_MIDI_API_ALSA] = "ALSA"; + if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK)) + midiData.apis[G_MIDI_API_JACK] = "JACK"; + +#elif defined(G_OS_FREEBSD) + + if (g_engine.kernelMidi.hasAPI(RtMidi::UNIX_JACK)) + midiData.apis[G_MIDI_API_JACK] = "JACK"; + +#elif defined(G_OS_WINDOWS) + + if (g_engine.kernelMidi.hasAPI(RtMidi::WINDOWS_MM)) + midiData.apis[G_MIDI_API_MM] = "Multimedia MIDI"; + +#elif defined(G_OS_MAC) + + if (g_engine.kernelMidi.hasAPI(RtMidi::MACOSX_CORE)) + midiData.apis[G_MIDI_API_CORE] = "OSX Core MIDI"; + +#endif + + midiData.syncModes[G_MIDI_SYNC_NONE] = "(disabled)"; + midiData.syncModes[G_MIDI_SYNC_CLOCK_M] = "MIDI Clock (master)"; + midiData.syncModes[G_MIDI_SYNC_MTC_M] = "MTC (master)"; + + midiData.midiMaps = g_engine.midiMapper.getMapFilesFound(); + midiData.midiMap = u::vector::indexOf(midiData.midiMaps, g_engine.conf.data.midiMapPath); + + for (unsigned i = 0; i < g_engine.kernelMidi.countOutPorts(); i++) + midiData.outPorts.push_back(g_engine.kernelMidi.getOutPortName(i)); + for (unsigned i = 0; i < g_engine.kernelMidi.countInPorts(); i++) + midiData.inPorts.push_back(g_engine.kernelMidi.getInPortName(i)); + + midiData.api = g_engine.conf.data.midiSystem; + midiData.syncMode = g_engine.conf.data.midiSync; + midiData.outPort = g_engine.conf.data.midiPortOut; + midiData.inPort = g_engine.conf.data.midiPortIn; + + return midiData; +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +PluginData getPluginData() +{ + PluginData pluginData; + pluginData.numAvailablePlugins = g_engine.pluginManager.countAvailablePlugins(); + pluginData.pluginPath = g_engine.conf.data.pluginPath; + return pluginData; +} + +#endif + +/* -------------------------------------------------------------------------- */ + +MiscData getMiscData() +{ + MiscData miscData; + miscData.logMode = g_engine.conf.data.logMode; + miscData.showTooltips = g_engine.conf.data.showTooltips; + return miscData; +} +/* -------------------------------------------------------------------------- */ + void save(const AudioData& data) { - m::conf::conf.soundSystem = data.api; - m::conf::conf.soundDeviceOut = data.outputDevice.index; - m::conf::conf.soundDeviceIn = data.inputDevice.index; - m::conf::conf.channelsOutCount = data.outputDevice.channelsCount; - m::conf::conf.channelsOutStart = data.outputDevice.channelsStart; - m::conf::conf.channelsInCount = data.inputDevice.channelsCount; - m::conf::conf.channelsInStart = data.inputDevice.channelsStart; - m::conf::conf.limitOutput = data.limitOutput; - m::conf::conf.rsmpQuality = data.resampleQuality; - m::conf::conf.buffersize = data.bufferSize; - m::conf::conf.recTriggerLevel = data.recTriggerLevel; - m::conf::conf.samplerate = data.sampleRate; + g_engine.conf.data.soundSystem = data.api; + g_engine.conf.data.soundDeviceOut = data.outputDevice.index; + g_engine.conf.data.soundDeviceIn = data.inputDevice.index; + g_engine.conf.data.channelsOutCount = data.outputDevice.channelsCount; + g_engine.conf.data.channelsOutStart = data.outputDevice.channelsStart; + g_engine.conf.data.channelsInCount = data.inputDevice.channelsCount; + g_engine.conf.data.channelsInStart = data.inputDevice.channelsStart; + g_engine.conf.data.limitOutput = data.limitOutput; + g_engine.conf.data.rsmpQuality = data.resampleQuality; + g_engine.conf.data.buffersize = data.bufferSize; + g_engine.conf.data.recTriggerLevel = data.recTriggerLevel; + g_engine.conf.data.samplerate = data.sampleRate; + g_engine.updateMixerModel(); } + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void save(const PluginData& data) +{ + g_engine.conf.data.pluginPath = data.pluginPath; +} + +#endif + +/* -------------------------------------------------------------------------- */ + +void save(const MidiData& data) +{ + g_engine.conf.data.midiSystem = data.api; + g_engine.conf.data.midiPortOut = data.outPort; + g_engine.conf.data.midiPortIn = data.inPort; + //g_engine.conf.data.midiMapPath = data.midiMap >= 0 && data.midiMap < (int)data.midiMaps.size() ? data.midiMaps[data.midiMap] : ""; + g_engine.conf.data.midiMapPath = u::vector::atOr(data.midiMaps, data.midiMap, ""); + g_engine.conf.data.midiSync = data.syncMode; +} + +/* -------------------------------------------------------------------------- */ + +void save(const MiscData& data) +{ + g_engine.conf.data.logMode = data.logMode; + g_engine.conf.data.showTooltips = data.showTooltips; + Fl_Tooltip::enable(g_engine.conf.data.showTooltips); +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void scanPlugins(std::string dir, const std::function& 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(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(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_CONFIG)); + configWin->tabPlugins->rebuild(); +} +#endif } // namespace giada::c::config \ No newline at end of file diff --git a/src/glue/config.h b/src/glue/config.h index fb39817..ae479e3 100644 --- a/src/glue/config.h +++ b/src/glue/config.h @@ -27,15 +27,12 @@ #ifndef G_GLUE_CONFIG_H #define G_GLUE_CONFIG_H +#include "core/kernelAudio.h" #include "core/types.h" #include #include #include -namespace giada::m::kernelAudio -{ -struct Device; -} namespace giada::c::config { enum class DeviceType @@ -47,7 +44,7 @@ enum class DeviceType struct AudioDeviceData { AudioDeviceData() = default; - AudioDeviceData(DeviceType t, const m::kernelAudio::Device&, int channelsCount, int channelsStart); + AudioDeviceData(DeviceType t, const m::KernelAudio::Device&, int channelsCount, int channelsStart); DeviceType type = DeviceType::OUTPUT; int index = -1; @@ -82,14 +79,62 @@ struct AudioData int resampleQuality; }; -/* getAudioData -Returns viewModel object filled with data. */ +struct MidiData +{ + std::map apis; + std::map syncModes; + std::vector midiMaps; + std::vector outPorts; + std::vector 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& 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 diff --git a/src/glue/events.cpp b/src/glue/events.cpp index a6bd909..30a1043 100644 --- a/src/glue/events.cpp +++ b/src/glue/events.cpp @@ -25,16 +25,17 @@ * -------------------------------------------------------------------------- */ #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" @@ -51,23 +52,25 @@ #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 #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::events { namespace { -void pushEvent_(m::eventDispatcher::Event e, Thread t) +void pushEvent_(m::EventDispatcher::Event e, Thread t) { bool res = true; if (t == Thread::MAIN) - res = m::eventDispatcher::UIevents.push(e); + res = g_engine.eventDispatcher.UIevents.push(e); else if (t == Thread::MIDI) - res = m::eventDispatcher::MidiEvents.push(e); + res = g_engine.eventDispatcher.MidiEvents.push(e); else assert(false); @@ -84,17 +87,17 @@ void pressChannel(ID channelId, int velocity, Thread t) { m::MidiEvent e; e.setVelocity(velocity); - pushEvent_({m::eventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t); + pushEvent_({m::EventDispatcher::EventType::KEY_PRESS, 0, channelId, velocity}, t); } void releaseChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::KEY_RELEASE, 0, channelId, {}}, t); } void killChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::KEY_KILL, 0, channelId, {}}, t); } /* -------------------------------------------------------------------------- */ @@ -103,14 +106,14 @@ void setChannelVolume(ID channelId, float v, Thread t) { v = std::clamp(v, 0.0f, G_MAX_VOLUME); - pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, channelId, v}, t); sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.volumeTool->update(v); }); if (t != Thread::MAIN) { Fl::lock(); - G_MainWin->keyboard->getChannel(channelId)->vol->value(v); + g_ui.mainWindow->keyboard->getChannel(channelId)->vol->value(v); Fl::unlock(); } } @@ -121,7 +124,7 @@ void setChannelPitch(ID channelId, float v, Thread t) { v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH); - pushEvent_({m::eventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_PITCH, 0, channelId, v}, t); sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); }); } @@ -133,7 +136,7 @@ void sendChannelPan(ID channelId, float v) v = std::clamp(v, 0.0f, G_MAX_PAN); /* Pan event is currently triggered only by the main thread. */ - pushEvent_({m::eventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_PAN, 0, channelId, v}, Thread::MAIN); sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); }); } @@ -142,67 +145,67 @@ void sendChannelPan(ID channelId, float v) void toggleMuteChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_MUTE, 0, channelId, {}}, t); } void toggleSoloChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_SOLO, 0, channelId, {}}, t); } /* -------------------------------------------------------------------------- */ void toggleArmChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_ARM, 0, channelId, {}}, t); } void toggleReadActionsChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, channelId, {}}, t); } void killReadActionsChannel(ID channelId, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_KILL_READ_ACTIONS, 0, channelId, {}}, t); } /* -------------------------------------------------------------------------- */ void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t) { - pushEvent_({m::eventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t); + pushEvent_({m::EventDispatcher::EventType::MIDI, 0, channelId, m::Action{0, channelId, 0, e}}, t); } /* -------------------------------------------------------------------------- */ void toggleMetronome() { - m::sequencer::toggleMetronome(); + g_engine.sequencer.toggleMetronome(); } /* -------------------------------------------------------------------------- */ void setMasterInVolume(float v, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_IN_CHANNEL_ID, v}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_IN_CHANNEL_ID, v}, t); if (t != Thread::MAIN) { Fl::lock(); - G_MainWin->mainIO->setInVol(v); + g_ui.mainWindow->mainIO->setInVol(v); Fl::unlock(); } } void setMasterOutVolume(float v, Thread t) { - pushEvent_({m::eventDispatcher::EventType::CHANNEL_VOLUME, 0, m::mixer::MASTER_OUT_CHANNEL_ID, v}, t); + pushEvent_({m::EventDispatcher::EventType::CHANNEL_VOLUME, 0, m::Mixer::MASTER_OUT_CHANNEL_ID, v}, t); if (t != Thread::MAIN) { Fl::lock(); - G_MainWin->mainIO->setOutVol(v); + g_ui.mainWindow->mainIO->setOutVol(v); Fl::unlock(); } } @@ -211,47 +214,56 @@ void setMasterOutVolume(float v, Thread t) void multiplyBeats() { - main::setBeats(m::clock::getBeats() * 2, m::clock::getBars()); + main::setBeats(g_engine.sequencer.getBeats() * 2, g_engine.sequencer.getBars()); } void divideBeats() { - main::setBeats(m::clock::getBeats() / 2, m::clock::getBars()); + main::setBeats(g_engine.sequencer.getBeats() / 2, g_engine.sequencer.getBars()); } /* -------------------------------------------------------------------------- */ void startSequencer(Thread t) { - pushEvent_({m::eventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t); - m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL; + pushEvent_({m::EventDispatcher::EventType::SEQUENCER_START, 0, 0, {}}, t); } void stopSequencer(Thread t) { - pushEvent_({m::eventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t); + pushEvent_({m::EventDispatcher::EventType::SEQUENCER_STOP, 0, 0, {}}, t); } void toggleSequencer(Thread t) { - m::clock::isRunning() ? stopSequencer(t) : startSequencer(t); + g_engine.sequencer.isRunning() ? stopSequencer(t) : startSequencer(t); } void rewindSequencer(Thread t) { - pushEvent_({m::eventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t); + pushEvent_({m::EventDispatcher::EventType::SEQUENCER_REWIND, 0, 0, {}}, t); } /* -------------------------------------------------------------------------- */ void toggleActionRecording() { - m::recManager::toggleActionRec(m::conf::conf.recTriggerMode); + if (!g_engine.kernelAudio.isReady()) + return; + if (g_engine.recorder.isRecordingAction()) + g_engine.recorder.stopActionRec(g_engine.actionRecorder); + else + g_engine.recorder.prepareActionRec(g_engine.conf.data.recTriggerMode); } void toggleInputRecording() { - m::recManager::toggleInputRec(m::conf::conf.recTriggerMode, m::conf::conf.inputRecMode); + if (!g_engine.kernelAudio.isReady() || !g_engine.kernelAudio.isInputEnabled() || !g_engine.mixerHandler.hasInputRecordableChannels()) + return; + if (g_engine.recorder.isRecordingInput()) + g_engine.recorder.stopInputRec(g_engine.conf.data.inputRecMode, g_engine.kernelAudio.getSampleRate()); + else + g_engine.recorder.prepareInputRec(g_engine.conf.data.recTriggerMode, g_engine.conf.data.inputRecMode); } /* -------------------------------------------------------------------------- */ @@ -259,7 +271,7 @@ void toggleInputRecording() #ifdef WITH_VST void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui) { - m::pluginHost::setPluginParameter(pluginId, paramIndex, value); + g_engine.pluginHost.setPluginParameter(pluginId, paramIndex, value); c::plugin::updateWindow(pluginId, gui); } #endif diff --git a/src/glue/events.h b/src/glue/events.h index 64a3f5b..6816afa 100644 --- a/src/glue/events.h +++ b/src/glue/events.h @@ -38,6 +38,7 @@ namespace giada::m { class MidiEvent; } + namespace giada::c::events { /* Channel* diff --git a/src/glue/io.cpp b/src/glue/io.cpp index aad617f..4555a6d 100644 --- a/src/glue/io.cpp +++ b/src/glue/io.cpp @@ -24,19 +24,18 @@ * * -------------------------------------------------------------------------- */ -#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" @@ -47,13 +46,15 @@ #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 -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::io { @@ -61,8 +62,8 @@ namespace { void rebuildMidiWindows_() { - u::gui::rebuildSubWindow(WID_MIDI_INPUT); - u::gui::rebuildSubWindow(WID_MIDI_OUTPUT); + g_ui.rebuildSubWindow(WID_MIDI_INPUT); + g_ui.rebuildSubWindow(WID_MIDI_OUTPUT); } } // namespace @@ -70,7 +71,7 @@ void rebuildMidiWindows_() /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Channel_InputData::Channel_InputData(const m::channel::Data& c) +Channel_InputData::Channel_InputData(const m::Channel& c) : channelId(c.id) , channelType(c.type) , enabled(c.midiLearner.enabled) @@ -101,7 +102,7 @@ Channel_InputData::Channel_InputData(const m::channel::Data& c) /* -------------------------------------------------------------------------- */ -MidiChannel_OutputData::MidiChannel_OutputData(const m::midiSender::Data& s) +MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s) : enabled(s.enabled) , filter(s.filter) { @@ -109,7 +110,7 @@ MidiChannel_OutputData::MidiChannel_OutputData(const m::midiSender::Data& s) /* -------------------------------------------------------------------------- */ -Channel_OutputData::Channel_OutputData(const m::channel::Data& c) +Channel_OutputData::Channel_OutputData(const m::Channel& c) : channelId(c.id) , lightningEnabled(c.midiLighter.enabled) , lightningPlaying(c.midiLighter.playing.getValue()) @@ -143,29 +144,29 @@ Master_InputData::Master_InputData(const m::model::MidiIn& midiIn) Channel_InputData channel_getInputData(ID channelId) { - return Channel_InputData(m::model::get().getChannel(channelId)); + return Channel_InputData(g_engine.model.get().getChannel(channelId)); } /* -------------------------------------------------------------------------- */ Channel_OutputData channel_getOutputData(ID channelId) { - return Channel_OutputData(m::model::get().getChannel(channelId)); + return Channel_OutputData(g_engine.model.get().getChannel(channelId)); } /* -------------------------------------------------------------------------- */ Master_InputData master_getInputData() { - return Master_InputData(m::model::get().midiIn); + return Master_InputData(g_engine.model.get().midiIn); } /* -------------------------------------------------------------------------- */ void channel_enableMidiLearn(ID channelId, bool v) { - m::model::get().getChannel(channelId).midiLearner.enabled = v; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).midiLearner.enabled = v; + g_engine.model.swap(m::model::SwapType::NONE); rebuildMidiWindows_(); } @@ -173,8 +174,8 @@ void channel_enableMidiLearn(ID channelId, bool v) void channel_enableMidiLightning(ID channelId, bool v) { - m::model::get().getChannel(channelId).midiLighter.enabled = v; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).midiLighter.enabled = v; + g_engine.model.swap(m::model::SwapType::NONE); rebuildMidiWindows_(); } @@ -182,8 +183,8 @@ void channel_enableMidiLightning(ID channelId, bool v) void channel_enableMidiOutput(ID channelId, bool v) { - m::model::get().getChannel(channelId).midiSender->enabled = v; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).midiSender->enabled = v; + g_engine.model.swap(m::model::SwapType::NONE); rebuildMidiWindows_(); } @@ -191,49 +192,49 @@ void channel_enableMidiOutput(ID channelId, bool v) void channel_enableVelocityAsVol(ID channelId, bool v) { - m::model::get().getChannel(channelId).samplePlayer->velocityAsVol = v; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).samplePlayer->velocityAsVol = v; + g_engine.model.swap(m::model::SwapType::NONE); } /* -------------------------------------------------------------------------- */ void channel_setMidiInputFilter(ID channelId, int ch) { - m::model::get().getChannel(channelId).midiLearner.filter = ch; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).midiLearner.filter = ch; + g_engine.model.swap(m::model::SwapType::NONE); } void channel_setMidiOutputFilter(ID channelId, int ch) { - m::model::get().getChannel(channelId).midiSender->filter = ch; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().getChannel(channelId).midiSender->filter = ch; + g_engine.model.swap(m::model::SwapType::NONE); } /* -------------------------------------------------------------------------- */ void channel_setKey(ID channelId, int k) { - m::model::get().getChannel(channelId).key = k; - m::model::swap(m::model::SwapType::HARD); + g_engine.model.get().getChannel(channelId).key = k; + g_engine.model.swap(m::model::SwapType::HARD); } /* -------------------------------------------------------------------------- */ void channel_startMidiLearn(int param, ID channelId) { - m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_); + g_engine.midiDispatcher.startChannelLearn(param, channelId, rebuildMidiWindows_); } void master_startMidiLearn(int param) { - m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_); + g_engine.midiDispatcher.startMasterLearn(param, rebuildMidiWindows_); } #ifdef WITH_VST void plugin_startMidiLearn(int paramIndex, ID pluginId) { - m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_); + g_engine.midiDispatcher.startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_); } #endif @@ -242,7 +243,7 @@ void plugin_startMidiLearn(int paramIndex, ID pluginId) void stopMidiLearn() { - m::midiDispatcher::stopLearn(); + g_engine.midiDispatcher.stopLearn(); rebuildMidiWindows_(); } @@ -250,19 +251,19 @@ void stopMidiLearn() void channel_clearMidiLearn(int param, ID channelId) { - m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_); + g_engine.midiDispatcher.clearChannelLearn(param, channelId, rebuildMidiWindows_); } void master_clearMidiLearn(int param) { - m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_); + g_engine.midiDispatcher.clearMasterLearn(param, rebuildMidiWindows_); } #ifdef WITH_VST void plugin_clearMidiLearn(int param, ID pluginId) { - m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_); + g_engine.midiDispatcher.clearPluginLearn(param, pluginId, rebuildMidiWindows_); } #endif @@ -271,8 +272,8 @@ void plugin_clearMidiLearn(int param, ID pluginId) void master_enableMidiLearn(bool v) { - m::model::get().midiIn.enabled = v; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().midiIn.enabled = v; + g_engine.model.swap(m::model::SwapType::NONE); rebuildMidiWindows_(); } @@ -280,7 +281,7 @@ void master_enableMidiLearn(bool v) void master_setMidiFilter(int c) { - m::model::get().midiIn.filter = c; - m::model::swap(m::model::SwapType::NONE); + g_engine.model.get().midiIn.filter = c; + g_engine.model.swap(m::model::SwapType::NONE); } } // namespace giada::c::io diff --git a/src/glue/io.h b/src/glue/io.h index 69f24f9..f862472 100644 --- a/src/glue/io.h +++ b/src/glue/io.h @@ -31,10 +31,11 @@ #include "core/model/model.h" #include "core/types.h" -namespace giada::m::channel +namespace giada::m { -struct Data; +class Channel; } + namespace giada::c::io { struct PluginParamData @@ -54,7 +55,7 @@ struct PluginData struct Channel_InputData { Channel_InputData() = default; - Channel_InputData(const m::channel::Data&); + Channel_InputData(const m::Channel&); ID channelId; ChannelType channelType; @@ -96,7 +97,7 @@ struct Master_InputData struct MidiChannel_OutputData { - MidiChannel_OutputData(const m::midiSender::Data&); + MidiChannel_OutputData(const m::MidiSender&); bool enabled; int filter; @@ -105,7 +106,7 @@ struct MidiChannel_OutputData struct Channel_OutputData { Channel_OutputData() = default; - Channel_OutputData(const m::channel::Data&); + Channel_OutputData(const m::Channel&); ID channelId; bool lightningEnabled; diff --git a/src/glue/layout.cpp b/src/glue/layout.cpp new file mode 100644 index 0000000..711aa24 --- /dev/null +++ b/src/glue/layout.cpp @@ -0,0 +1,240 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "glue/layout.h" +#include "core/conf.h" +#include "core/engine.h" +#include "core/patch.h" +#include "core/sequencer.h" +#include "glue/config.h" +#include "glue/storage.h" +#include "gui/dialogs/about.h" +#include "gui/dialogs/actionEditor/midiActionEditor.h" +#include "gui/dialogs/actionEditor/sampleActionEditor.h" +#include "gui/dialogs/beatsInput.h" +#include "gui/dialogs/bpmInput.h" +#include "gui/dialogs/browser/browserDir.h" +#include "gui/dialogs/browser/browserLoad.h" +#include "gui/dialogs/browser/browserSave.h" +#include "gui/dialogs/channelNameInput.h" +#include "gui/dialogs/config.h" +#include "gui/dialogs/keyGrabber.h" +#include "gui/dialogs/mainWindow.h" +#include "gui/dialogs/midiIO/midiInputChannel.h" +#include "gui/dialogs/midiIO/midiInputMaster.h" +#include "gui/dialogs/midiIO/midiOutputMidiCh.h" +#include "gui/dialogs/midiIO/midiOutputSampleCh.h" +#include "gui/dialogs/pluginChooser.h" +#include "gui/dialogs/pluginList.h" +#include "gui/dialogs/sampleEditor.h" +#include "gui/ui.h" + +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; + +namespace giada::c::layout +{ +void openBrowserForProjectLoad() +{ + v::gdWindow* childWin = new v::gdBrowserLoad("Open project", g_engine.conf.data.patchPath, + c::storage::loadProject, 0, g_engine.conf.data); + g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER); +} + +/* -------------------------------------------------------------------------- */ + +void openBrowserForProjectSave() +{ + v::gdWindow* childWin = new v::gdBrowserSave("Save project", g_engine.conf.data.patchPath, + g_engine.patch.data.name, c::storage::saveProject, 0, g_engine.conf.data); + g_ui.openSubWindow(*g_ui.mainWindow.get(), childWin, WID_FILE_BROWSER); +} + +/* -------------------------------------------------------------------------- */ + +void openBrowserForSampleLoad(ID channelId) +{ + v::gdWindow* w = new v::gdBrowserLoad("Browse sample", + g_engine.conf.data.samplePath.c_str(), c::storage::loadSample, channelId, g_engine.conf.data); + g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER); +} + +/* -------------------------------------------------------------------------- */ + +void openBrowserForSampleSave(ID channelId) +{ + v::gdWindow* w = new v::gdBrowserSave("Save sample", + g_engine.conf.data.samplePath.c_str(), "", c::storage::saveSample, channelId, g_engine.conf.data); + g_ui.openSubWindow(*g_ui.mainWindow.get(), w, WID_FILE_BROWSER); +} + +/* -------------------------------------------------------------------------- */ + +void openAboutWindow() +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdAbout(), WID_ABOUT); +} + +/* -------------------------------------------------------------------------- */ + +void openKeyGrabberWindow(const c::channel::Data& data) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdKeyGrabber(data), WID_KEY_GRABBER); +} + +/* -------------------------------------------------------------------------- */ + +void openBpmWindow(std::string bpmValue) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBpmInput(bpmValue.c_str()), WID_BPM); +} + +/* -------------------------------------------------------------------------- */ + +void openBeatsWindow(int beats, int bars) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdBeatsInput(beats, bars), WID_BEATS); +} + +/* -------------------------------------------------------------------------- */ + +void openConfigWindow() +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdConfig(400, 370, g_engine.conf.data), WID_CONFIG); +} + +/* -------------------------------------------------------------------------- */ + +void openMasterMidiInputWindow() +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputMaster(g_engine.conf.data), WID_MIDI_INPUT); +} + +/* -------------------------------------------------------------------------- */ + +void openChannelMidiInputWindow(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiInputChannel(channelId, g_engine.conf.data), + WID_MIDI_INPUT); +} + +/* -------------------------------------------------------------------------- */ + +void openSampleChannelMidiOutputWindow(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputSampleCh(channelId), + WID_MIDI_OUTPUT); +} + +/* -------------------------------------------------------------------------- */ + +void openMidiChannelMidiOutputWindow(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdMidiOutputMidiCh(channelId), WID_MIDI_OUTPUT); +} + +/* -------------------------------------------------------------------------- */ + +void openSampleActionEditor(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), + new v::gdSampleActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()), + WID_ACTION_EDITOR); +} + +/* -------------------------------------------------------------------------- */ + +void openMidiActionEditor(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), + new v::gdMidiActionEditor(channelId, g_engine.conf.data, g_engine.sequencer.getFramesInBeat()), + WID_ACTION_EDITOR); +} + +/* -------------------------------------------------------------------------- */ + +void openSampleEditor(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdSampleEditor(channelId, g_engine.conf.data), + WID_SAMPLE_EDITOR); +} + +/* -------------------------------------------------------------------------- */ + +void openRenameChannelWindow(const c::channel::Data& data) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdChannelNameInput(data), + WID_SAMPLE_NAME); +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void openBrowserForPlugins(v::gdWindow& parent) +{ + v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory", + g_engine.conf.data.patchPath, c::config::setPluginPathCb, g_engine.conf.data); + parent.addSubWindow(browser); +} + +/* -------------------------------------------------------------------------- */ + +void openChannelPluginListWindow(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(channelId, g_engine.conf.data), + WID_FX_LIST); +} + +/* -------------------------------------------------------------------------- */ + +void openMasterInPluginListWindow() +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_IN_CHANNEL_ID, g_engine.conf.data), + WID_FX_LIST); +} + +/* -------------------------------------------------------------------------- */ + +void openMasterOutPluginListWindow() +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), new v::gdPluginList(m::Mixer::MASTER_OUT_CHANNEL_ID, g_engine.conf.data), + WID_FX_LIST); +} + +/* -------------------------------------------------------------------------- */ + +void openPluginChooser(ID channelId) +{ + g_ui.openSubWindow(*g_ui.mainWindow.get(), + new v::gdPluginChooser(g_engine.conf.data.pluginChooserX, + g_engine.conf.data.pluginChooserY, g_engine.conf.data.pluginChooserW, + g_engine.conf.data.pluginChooserH, channelId, g_engine.conf.data), + WID_FX_CHOOSER); +} + +#endif +} // namespace giada::c::layout diff --git a/src/glue/layout.h b/src/glue/layout.h new file mode 100644 index 0000000..a286f94 --- /dev/null +++ b/src/glue/layout.h @@ -0,0 +1,71 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_GLUE_LAYOUT_H +#define G_GLUE_LAYOUT_H + +#include "core/types.h" +#include + +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 diff --git a/src/glue/main.cpp b/src/glue/main.cpp index 761e94c..3b68747 100644 --- a/src/glue/main.cpp +++ b/src/glue/main.cpp @@ -24,10 +24,10 @@ * * -------------------------------------------------------------------------- */ -#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" @@ -36,15 +36,18 @@ #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" @@ -52,17 +55,18 @@ #include #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::main { -Timer::Timer(const m::model::Clock& c) +Timer::Timer(const m::model::Sequencer& c) : bpm(c.bpm) , beats(c.beats) , bars(c.bars) , quantize(c.quantize) -, isUsingJack(m::kernelAudio::getAPI() == G_SYS_API_JACK) -, isRecordingInput(m::recManager::isRecordingInput()) +, isUsingJack(g_engine.kernelAudio.getAPI() == G_SYS_API_JACK) +, isRecordingInput(g_engine.recorder.isRecordingInput()) { } @@ -70,7 +74,7 @@ Timer::Timer(const m::model::Clock& c) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -IO::IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m) +IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m) : masterOutVol(out.volume) , masterInVol(in.volume) #ifdef WITH_VST @@ -83,14 +87,21 @@ IO::IO(const m::channel::Data& out, const m::channel::Data& in, const m::model:: /* -------------------------------------------------------------------------- */ -float IO::getMasterOutPeak() +Peak IO::getMasterOutPeak() { - return m::mixer::getPeakOut(); + return g_engine.mixer.getPeakOut(); } -float IO::getMasterInPeak() +Peak IO::getMasterInPeak() { - return m::mixer::getPeakIn(); + return g_engine.mixer.getPeakIn(); +} + +/* -------------------------------------------------------------------------- */ + +bool IO::isKernelReady() +{ + return g_engine.kernelAudio.isReady(); } /* -------------------------------------------------------------------------- */ @@ -99,16 +110,16 @@ float IO::getMasterInPeak() Timer getTimer() { - return Timer(m::model::get().clock); + return Timer(g_engine.model.get().sequencer); } /* -------------------------------------------------------------------------- */ IO getIO() { - return IO(m::model::get().getChannel(m::mixer::MASTER_OUT_CHANNEL_ID), - m::model::get().getChannel(m::mixer::MASTER_IN_CHANNEL_ID), - m::model::get().mixer); + return IO(g_engine.model.get().getChannel(m::Mixer::MASTER_OUT_CHANNEL_ID), + g_engine.model.get().getChannel(m::Mixer::MASTER_IN_CHANNEL_ID), + g_engine.model.get().mixer); } /* -------------------------------------------------------------------------- */ @@ -117,13 +128,13 @@ Sequencer getSequencer() { Sequencer out; - m::mixer::RecordInfo recInfo = m::mixer::getRecordInfo(); + m::Mixer::RecordInfo recInfo = g_engine.mixer.getRecordInfo(); - out.isFreeModeInputRec = m::recManager::isRecordingInput() && m::conf::conf.inputRecMode == InputRecMode::FREE; - out.shouldBlink = u::gui::shouldBlink() && (m::clock::getStatus() == ClockStatus::WAITING || out.isFreeModeInputRec); - out.beats = m::clock::getBeats(); - out.bars = m::clock::getBars(); - out.currentBeat = m::clock::getCurrentBeat(); + out.isFreeModeInputRec = g_engine.recorder.isRecordingInput() && g_engine.conf.data.inputRecMode == InputRecMode::FREE; + out.shouldBlink = g_ui.shouldBlink() && (g_engine.sequencer.getStatus() == SeqStatus::WAITING || out.isFreeModeInputRec); + out.beats = g_engine.sequencer.getBeats(); + out.bars = g_engine.sequencer.getBars(); + out.currentBeat = g_engine.sequencer.getCurrentBeat(); out.recPosition = recInfo.position; out.recMaxLength = recInfo.maxLength; @@ -132,14 +143,38 @@ Sequencer getSequencer() /* -------------------------------------------------------------------------- */ +Transport getTransport() +{ + Transport transport; + transport.isRunning = g_engine.sequencer.isRunning(); + transport.isRecordingAction = g_engine.recorder.isRecordingAction(); + transport.isRecordingInput = g_engine.recorder.isRecordingInput(); + transport.isMetronomeOn = g_engine.sequencer.isMetronomeOn(); + transport.recTriggerMode = g_engine.conf.data.recTriggerMode; + transport.inputRecMode = g_engine.conf.data.inputRecMode; + return transport; +} + +/* -------------------------------------------------------------------------- */ + +MainMenu getMainMenu() +{ + MainMenu mainMenu; + mainMenu.hasAudioData = g_engine.mixerHandler.hasAudioData(); + mainMenu.hasActions = g_engine.mixerHandler.hasActions(); + return mainMenu; +} + +/* -------------------------------------------------------------------------- */ + void setBpm(const char* i, const char* f) { /* Never change this stuff while recording audio. */ - if (m::recManager::isRecordingInput()) + if (g_engine.recorder.isRecordingInput()) return; - m::clock::setBpm(std::atof(i) + (std::atof(f) / 10.0f)); + g_engine.sequencer.setBpm(std::atof(i) + (std::atof(f) / 10.0f), g_engine.kernelAudio.getSampleRate()); } /* -------------------------------------------------------------------------- */ @@ -148,10 +183,10 @@ void setBpm(float f) { /* Never change this stuff while recording audio. */ - if (m::recManager::isRecordingInput()) + if (g_engine.recorder.isRecordingInput()) return; - m::clock::setBpm(f); + g_engine.sequencer.setBpm(f, g_engine.kernelAudio.getSampleRate()); } /* -------------------------------------------------------------------------- */ @@ -160,18 +195,18 @@ void setBeats(int beats, int bars) { /* Never change this stuff while recording audio. */ - if (m::recManager::isRecordingInput()) + if (g_engine.recorder.isRecordingInput()) return; - m::clock::setBeats(beats, bars); - m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop()); + g_engine.sequencer.setBeats(beats, bars, g_engine.kernelAudio.getSampleRate()); + g_engine.mixer.allocRecBuffer(g_engine.sequencer.getMaxFramesInLoop(g_engine.kernelAudio.getSampleRate())); } /* -------------------------------------------------------------------------- */ void quantize(int val) { - m::clock::setQuantize(val); + g_engine.sequencer.setQuantize(val, g_engine.kernelAudio.getSampleRate()); } /* -------------------------------------------------------------------------- */ @@ -180,10 +215,11 @@ void clearAllSamples() { if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?")) return; - G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); - m::clock::setStatus(ClockStatus::STOPPED); - m::mh::freeAllChannels(); - m::recorderHandler::clearAllActions(); + g_ui.closeSubWindow(WID_SAMPLE_EDITOR); + g_engine.sequencer.setStatus(SeqStatus::STOPPED); + g_engine.synchronizer.sendMIDIstop(); + g_engine.mixerHandler.freeAllChannels(); + g_engine.actionRecorder.clearAllActions(); } /* -------------------------------------------------------------------------- */ @@ -192,48 +228,66 @@ void clearAllActions() { if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?")) return; - G_MainWin->delSubWindow(WID_ACTION_EDITOR); - m::recorderHandler::clearAllActions(); + g_ui.closeSubWindow(WID_ACTION_EDITOR); + g_engine.actionRecorder.clearAllActions(); } /* -------------------------------------------------------------------------- */ void setInToOut(bool v) { - m::mh::setInToOut(v); + g_engine.mixerHandler.setInToOut(v); } /* -------------------------------------------------------------------------- */ void toggleRecOnSignal() { - if (!m::recManager::canEnableRecOnSignal()) - { - m::conf::conf.recTriggerMode = RecTriggerMode::NORMAL; - return; - } - m::conf::conf.recTriggerMode = m::conf::conf.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL; + if (!g_engine.recorder.canEnableRecOnSignal()) + g_engine.conf.data.recTriggerMode = RecTriggerMode::NORMAL; + else + g_engine.conf.data.recTriggerMode = g_engine.conf.data.recTriggerMode == RecTriggerMode::NORMAL ? RecTriggerMode::SIGNAL : RecTriggerMode::NORMAL; + g_engine.updateMixerModel(); } /* -------------------------------------------------------------------------- */ void toggleFreeInputRec() { - if (!m::recManager::canEnableFreeInputRec()) - { - m::conf::conf.inputRecMode = InputRecMode::RIGID; - return; - } - m::conf::conf.inputRecMode = m::conf::conf.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE; + if (!g_engine.recorder.canEnableFreeInputRec()) + g_engine.conf.data.inputRecMode = InputRecMode::RIGID; + else + g_engine.conf.data.inputRecMode = g_engine.conf.data.inputRecMode == InputRecMode::FREE ? InputRecMode::RIGID : InputRecMode::FREE; + g_engine.updateMixerModel(); } /* -------------------------------------------------------------------------- */ +#ifdef G_DEBUG_MODE + +void printDebugInfo() +{ + g_engine.model.debug(); +} + +#endif + +/* -------------------------------------------------------------------------- */ + void closeProject() { if (!v::gdConfirmWin("Warning", "Close project: are you sure?")) return; - m::init::reset(); - m::mixer::enable(); + g_engine.mixer.disable(); + g_engine.reset(); + g_ui.reset(); + g_engine.mixer.enable(); +} + +/* -------------------------------------------------------------------------- */ + +void quitGiada() +{ + m::init::closeMainWindow(); } } // namespace giada::c::main diff --git a/src/glue/main.h b/src/glue/main.h index 098fbfc..8757c50 100644 --- a/src/glue/main.h +++ b/src/glue/main.h @@ -29,23 +29,23 @@ #include "core/types.h" -namespace giada::m::channel +namespace giada::m { -struct Data; +class Channel; } + namespace giada::m::model { -struct Clock; -struct Clock; -struct Mixer; -struct Mixer; +class Sequencer; +class Mixer; } // namespace giada::m::model + namespace giada::c::main { struct Timer { Timer() = default; - Timer(const m::model::Clock& c); + Timer(const m::model::Sequencer& c); float bpm; int beats; @@ -58,7 +58,7 @@ struct Timer struct IO { IO() = default; - IO(const m::channel::Data& out, const m::channel::Data& in, const m::model::Mixer& m); + IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m); float masterOutVol; float masterInVol; @@ -68,8 +68,9 @@ struct IO #endif bool inToOut; - float getMasterOutPeak(); - float getMasterInPeak(); + Peak getMasterOutPeak(); + Peak getMasterInPeak(); + bool isKernelReady(); }; struct Sequencer @@ -83,12 +84,30 @@ struct Sequencer Frame recMaxLength; }; +struct Transport +{ + bool isRunning; + bool isRecordingAction; + bool isRecordingInput; + bool isMetronomeOn; + RecTriggerMode recTriggerMode; + InputRecMode inputRecMode; +}; + +struct MainMenu +{ + bool hasAudioData; + bool hasActions; +}; + /* get* Returns viewModel objects filled with data. */ Timer getTimer(); IO getIO(); Sequencer getSequencer(); +Transport getTransport(); +MainMenu getMainMenu(); /* setBpm (1) Sets bpm value from string to float. */ @@ -112,11 +131,16 @@ void setInToOut(bool v); void toggleRecOnSignal(); void toggleFreeInputRec(); +#ifdef G_DEBUG_MODE +void printDebugInfo(); +#endif /* closeProject Resets Giada to init state. If resetGui also refresh all widgets. */ void closeProject(); + +void quitGiada(); } // namespace giada::c::main #endif diff --git a/src/glue/plugin.cpp b/src/glue/plugin.cpp index 7cc9934..a7010e2 100644 --- a/src/glue/plugin.cpp +++ b/src/glue/plugin.cpp @@ -29,22 +29,24 @@ #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 #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::plugin { @@ -102,7 +104,7 @@ void Plugin::setResizeCallback(std::function f) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Plugins::Plugins(const m::channel::Data& c) +Plugins::Plugins(const m::Channel& c) : channelId(c.id) , plugins(c.plugins) { @@ -114,7 +116,7 @@ Plugins::Plugins(const m::channel::Data& c) Plugins getPlugins(ID channelId) { - return Plugins(m::model::get().getChannel(channelId)); + return Plugins(g_engine.model.get().getChannel(channelId)); } Plugin getPlugin(m::Plugin& plugin, ID channelId) @@ -127,11 +129,16 @@ Param getParam(int index, const m::Plugin& plugin, ID channelId) return Param(plugin, index, channelId); } +std::vector getPluginsInfo() +{ + return g_engine.pluginManager.getPluginsInfo(); +} + /* -------------------------------------------------------------------------- */ void updateWindow(ID pluginId, bool gui) { - m::Plugin* p = m::model::find(pluginId); + m::Plugin* p = g_engine.model.findShared(pluginId); assert(p != nullptr); @@ -141,10 +148,10 @@ void updateWindow(ID pluginId, bool gui) /* Get the parent window first: the plug-in list. Then, if it exists, get the child window - the actual pluginWindow. */ - v::gdPluginList* parent = static_cast(u::gui::getSubwindow(G_MainWin, WID_FX_LIST)); + v::gdPluginList* parent = static_cast(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_FX_LIST)); if (parent == nullptr) return; - v::gdPluginWindow* child = static_cast(u::gui::getSubwindow(parent, pluginId + 1)); + v::gdPluginWindow* child = static_cast(g_ui.getSubwindow(*parent, pluginId + 1)); if (child == nullptr) return; @@ -159,32 +166,50 @@ void updateWindow(ID pluginId, bool gui) void addPlugin(int pluginListIndex, ID channelId) { - if (pluginListIndex >= m::pluginManager::countAvailablePlugins()) + if (pluginListIndex >= g_engine.pluginManager.countAvailablePlugins()) return; - std::unique_ptr p = m::pluginManager::makePlugin(pluginListIndex); - if (p != nullptr) - m::pluginHost::addPlugin(std::move(p), channelId); + std::unique_ptr 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(pluginPtr)); + g_engine.model.swap(m::model::SwapType::HARD); } /* -------------------------------------------------------------------------- */ void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId) { - m::pluginHost::swapPlugin(p1, p2, channelId); + g_engine.pluginHost.swapPlugin(p1, p2, g_engine.model.get().getChannel(channelId).plugins); + g_engine.model.swap(m::model::SwapType::HARD); +} + +/* -------------------------------------------------------------------------- */ + +void sortPlugins(m::PluginManager::SortMethod method) +{ + g_engine.pluginManager.sortPlugins(method); } /* -------------------------------------------------------------------------- */ void freePlugin(const m::Plugin& plugin, ID channelId) { - m::pluginHost::freePlugin(plugin, channelId); + u::vector::remove(g_engine.model.get().getChannel(channelId).plugins, &plugin); + g_engine.model.swap(m::model::SwapType::HARD); + + g_engine.pluginHost.freePlugin(plugin); } /* -------------------------------------------------------------------------- */ void setProgram(ID pluginId, int programIndex) { - m::pluginHost::setPluginProgram(pluginId, programIndex); + g_engine.pluginHost.setPluginProgram(pluginId, programIndex); updateWindow(pluginId, /*gui=*/true); } @@ -192,29 +217,19 @@ void setProgram(ID pluginId, int programIndex) void toggleBypass(ID pluginId) { - m::pluginHost::toggleBypass(pluginId); + g_engine.pluginHost.toggleBypass(pluginId); } /* -------------------------------------------------------------------------- */ -void setPluginPathCb(void* data) +void startDispatchLoop() { - v::gdBrowserDir* browser = (v::gdBrowserDir*)data; - - if (browser->getCurrentPath() == "") - { - v::gdAlert("Invalid path."); - return; - } - - if (!m::conf::conf.pluginPath.empty() && m::conf::conf.pluginPath.back() != ';') - m::conf::conf.pluginPath += ";"; - m::conf::conf.pluginPath += browser->getCurrentPath(); - - browser->do_callback(); + g_ui.startJuceDispatchLoop(); +} - v::gdConfig* configWin = static_cast(u::gui::getSubwindow(G_MainWin, WID_CONFIG)); - configWin->refreshVstPath(); +void stopDispatchLoop() +{ + g_ui.stopJuceDispatchLoop(); } } // namespace giada::c::plugin diff --git a/src/glue/plugin.h b/src/glue/plugin.h index f091937..dca83f4 100644 --- a/src/glue/plugin.h +++ b/src/glue/plugin.h @@ -30,6 +30,7 @@ #ifdef WITH_VST #include "core/plugins/pluginHost.h" +#include "core/plugins/pluginManager.h" #include "core/types.h" #include #include @@ -38,14 +39,13 @@ namespace juce { class AudioProcessorEditor; } + namespace giada::m { class Plugin; -} -namespace giada::m::channel -{ -struct Data; -} +class Channel; +} // namespace giada::m + namespace giada::c::plugin { struct Program @@ -89,14 +89,14 @@ struct Plugin std::vector programs; std::vector paramIndexes; - private: +private: m::Plugin& m_plugin; }; struct Plugins { Plugins() = default; - Plugins(const m::channel::Data&); + Plugins(const m::Channel&); ID channelId; std::vector plugins; @@ -109,6 +109,8 @@ Plugins getPlugins(ID channelId); Plugin getPlugin(m::Plugin& plugin, ID channelId); Param getParam(int index, const m::Plugin& plugin, ID channelId); +std::vector getPluginsInfo(); + /* updateWindow Updates the editor-less plug-in window. This is useless if the plug-in has an editor. */ @@ -117,15 +119,12 @@ void updateWindow(ID pluginId, bool gui); void addPlugin(int pluginListIndex, ID channelId); void swapPlugins(const m::Plugin& p1, const m::Plugin& p2, ID channelId); +void sortPlugins(m::PluginManager::SortMethod); void freePlugin(const m::Plugin& plugin, ID channelId); void setProgram(ID pluginId, int programIndex); void toggleBypass(ID pluginId); - -/* setPluginPathCb -Callback attached to the DirBrowser for adding new Plug-in search paths in the -configuration window. */ - -void setPluginPathCb(void* data); +void startDispatchLoop(); +void stopDispatchLoop(); } // namespace giada::c::plugin #endif diff --git a/src/glue/recorder.cpp b/src/glue/recorder.cpp index 03e38ea..1c2d22a 100644 --- a/src/glue/recorder.cpp +++ b/src/glue/recorder.cpp @@ -24,30 +24,35 @@ * * -------------------------------------------------------------------------- */ -#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 +extern giada::m::Engine g_engine; +extern giada::v::Ui g_ui; + namespace giada::c::recorder { void clearAllActions(ID channelId) { if (!v::gdConfirmWin("Warning", "Clear all actions: are you sure?")) return; - m::recorder::clearChannel(channelId); + g_engine.actionRecorder.clearChannel(channelId); updateChannel(channelId, /*updateActionEditor=*/true); } @@ -57,7 +62,7 @@ void clearVolumeActions(ID channelId) { if (!v::gdConfirmWin("Warning", "Clear all volume actions: are you sure?")) return; - m::recorder::clearActions(channelId, m::MidiEvent::ENVELOPE); + g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::ENVELOPE); updateChannel(channelId, /*updateActionEditor=*/true); } @@ -67,9 +72,9 @@ void clearStartStopActions(ID channelId) { if (!v::gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?")) return; - m::recorder::clearActions(channelId, m::MidiEvent::NOTE_ON); - m::recorder::clearActions(channelId, m::MidiEvent::NOTE_OFF); - m::recorder::clearActions(channelId, m::MidiEvent::NOTE_KILL); + g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_ON); + g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_OFF); + g_engine.actionRecorder.clearActions(channelId, m::MidiEvent::NOTE_KILL); updateChannel(channelId, /*updateActionEditor=*/true); } @@ -78,10 +83,10 @@ void clearStartStopActions(ID channelId) void updateChannel(ID channelId, bool updateActionEditor) { /* TODO - move somewhere else in the core area */ - m::model::get().getChannel(channelId).hasActions = m::recorder::hasActions(channelId); - m::model::swap(m::model::SwapType::HARD); + g_engine.model.get().getChannel(channelId).hasActions = g_engine.actionRecorder.hasActions(channelId); + g_engine.model.swap(m::model::SwapType::HARD); if (updateActionEditor) - u::gui::refreshActionEditor(); + g_ui.refreshSubWindow(WID_ACTION_EDITOR); } } // namespace giada::c::recorder diff --git a/src/glue/recorder.h b/src/glue/recorder.h index cb5bb74..6d78432 100644 --- a/src/glue/recorder.h +++ b/src/glue/recorder.h @@ -27,6 +27,8 @@ #ifndef G_GLUE_RECORDER_H #define G_GLUE_RECORDER_H +#include "core/types.h" + namespace giada::c::recorder { void clearAllActions(ID channelId); diff --git a/src/glue/sampleEditor.cpp b/src/glue/sampleEditor.cpp index 5f5f4ea..11a1e99 100644 --- a/src/glue/sampleEditor.cpp +++ b/src/glue/sampleEditor.cpp @@ -27,8 +27,11 @@ #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" @@ -45,24 +48,26 @@ #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 #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; +extern giada::m::Engine g_engine; namespace giada::c::sampleEditor { namespace { -m::channel::Data& getChannel_(ID channelId) +m::Channel& getChannel_(ID channelId) { - return m::model::get().getChannel(channelId); + return g_engine.model.get().getChannel(channelId); } -m::samplePlayer::Data& getSamplePlayer_(ID channelId) +m::SamplePlayer& getSamplePlayer_(ID channelId) { return getChannel_(channelId).samplePlayer.value(); } @@ -98,7 +103,7 @@ void resetBeginEnd_(ID channelId) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -Data::Data(const m::channel::Data& c) +Data::Data(const m::Channel& c) : channelId(c.id) , name(c.name) , volume(c.volume) @@ -119,12 +124,12 @@ Data::Data(const m::channel::Data& c) ChannelStatus Data::a_getPreviewStatus() const { - return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load(); + return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->playStatus.load(); } Frame Data::a_getPreviewTracker() const { - return getChannel_(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.load(); + return getChannel_(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.load(); } const m::Wave& Data::getWaveRef() const @@ -132,6 +137,16 @@ const m::Wave& Data::getWaveRef() const return *m_channel->samplePlayer->getWave(); } +Frame Data::getFramesInBar() const +{ + return g_engine.sequencer.getFramesInBar(); +} + +Frame Data::getFramesInLoop() const +{ + return g_engine.sequencer.getFramesInLoop(); +} + /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ @@ -139,8 +154,9 @@ const m::Wave& Data::getWaveRef() const Data getData(ID channelId) { /* Prepare the preview channel first, then return Data object. */ - m::samplePlayer::loadWave(getChannel_(m::mixer::PREVIEW_CHANNEL_ID), &getWave_(channelId)); - m::model::swap(m::model::SwapType::SOFT); + m::Channel& previewChannel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID); + previewChannel.samplePlayer->loadWave(previewChannel, &getWave_(channelId)); + g_engine.model.swap(m::model::SwapType::SOFT); return Data(getChannel_(channelId)); } @@ -149,7 +165,7 @@ Data getData(ID channelId) void onRefresh(bool gui, std::function f) { - v::gdSampleEditor* se = static_cast(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); + v::gdSampleEditor* se = static_cast(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR)); if (se == nullptr) return; if (!gui) @@ -161,7 +177,7 @@ void onRefresh(bool gui, std::function f) v::gdSampleEditor* getSampleEditorWindow() { - v::gdSampleEditor* se = static_cast(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); + v::gdSampleEditor* se = static_cast(g_ui.getSubwindow(*g_ui.mainWindow.get(), WID_SAMPLE_EDITOR)); assert(se != nullptr); return se; } @@ -170,7 +186,7 @@ v::gdSampleEditor* getSampleEditorWindow() void setBeginEnd(ID channelId, Frame b, Frame e) { - m::channel::Data& c = getChannel_(channelId); + m::Channel& c = getChannel_(channelId); b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1); e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1); @@ -179,12 +195,12 @@ void setBeginEnd(ID channelId, Frame b, Frame e) else if (e < b) e = b + 1; - if (c.state->tracker.load() < b) - c.state->tracker.store(b); + if (c.shared->tracker.load() < b) + c.shared->tracker.store(b); getSamplePlayer_(channelId).begin = b; getSamplePlayer_(channelId).end = e; - m::model::swap(m::model::SwapType::SOFT); + g_engine.model.swap(m::model::SwapType::SOFT); /* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */ getSampleEditorWindow()->rebuild(); @@ -195,7 +211,7 @@ void setBeginEnd(ID channelId, Frame b, Frame e) void cut(ID channelId, Frame a, Frame b) { copy(channelId, a, b); - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::cut(getWave_(channelId), a, b); resetBeginEnd_(channelId); } @@ -204,7 +220,7 @@ void cut(ID channelId, Frame a, Frame b) void copy(ID channelId, Frame a, Frame b) { - waveBuffer_ = m::waveManager::createFromWave(getWave_(channelId), a, b); + waveBuffer_ = g_engine.waveManager.createFromWave(getWave_(channelId), a, b); } /* -------------------------------------------------------------------------- */ @@ -224,7 +240,7 @@ void paste(ID channelId, Frame a) /* Temporary disable wave reading in channel. From now on, the audio thread won't be reading any wave, so editing it is safe. */ - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); /* Paste copied data to destination wave. */ @@ -232,7 +248,7 @@ void paste(ID channelId, Frame a) /* Pass the old wave that contains the pasted data to channel. */ - m::samplePlayer::setWave(getChannel_(channelId), &wave, 1.0f); + getChannel_(channelId).samplePlayer->setWave(&wave, 1.0f); /* In the meantime, shift begin/end points to keep the previous position. */ @@ -252,7 +268,7 @@ void paste(ID channelId, Frame a) void silence(ID channelId, int a, int b) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::silence(getWave_(channelId), a, b); } @@ -260,7 +276,7 @@ void silence(ID channelId, int a, int b) void fade(ID channelId, int a, int b, m::wfx::Fade type) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::fade(getWave_(channelId), a, b, type); } @@ -268,7 +284,7 @@ void fade(ID channelId, int a, int b, m::wfx::Fade type) void smoothEdges(ID channelId, int a, int b) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::smooth(getWave_(channelId), a, b); } @@ -276,7 +292,7 @@ void smoothEdges(ID channelId, int a, int b) void reverse(ID channelId, Frame a, Frame b) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::reverse(getWave_(channelId), a, b); } @@ -284,7 +300,7 @@ void reverse(ID channelId, Frame a, Frame b) void normalize(ID channelId, int a, int b) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::normalize(getWave_(channelId), a, b); } @@ -292,7 +308,7 @@ void normalize(ID channelId, int a, int b) void trim(ID channelId, int a, int b) { - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); m::wfx::trim(getWave_(channelId), a, b); resetBeginEnd_(channelId); } @@ -306,8 +322,8 @@ the One-shot pause mode is implemented: void playPreview(bool loop) { setPreviewTracker(previewTracker_); - channel::setSamplePlayerMode(m::mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC); - events::pressChannel(m::mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN); + channel::setSamplePlayerMode(m::Mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC); + events::pressChannel(m::Mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN); } void stopPreview() @@ -316,15 +332,13 @@ void stopPreview() channel. */ setPreviewTracker(previewTracker_); getSampleEditorWindow()->refresh(); - events::killChannel(m::mixer::PREVIEW_CHANNEL_ID, Thread::MAIN); + events::killChannel(m::Mixer::PREVIEW_CHANNEL_ID, Thread::MAIN); } void setPreviewTracker(Frame f) { - namespace mm = m::model; - - mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID).state->tracker.store(f); - mm::swap(mm::SwapType::SOFT); + g_engine.model.get().getChannel(m::Mixer::PREVIEW_CHANNEL_ID).shared->tracker.store(f); + g_engine.model.swap(m::model::SwapType::SOFT); previewTracker_ = f; @@ -333,18 +347,19 @@ void setPreviewTracker(Frame f) void cleanupPreview() { - namespace mm = m::model; + m::Channel& channel = getChannel_(m::Mixer::PREVIEW_CHANNEL_ID); - m::samplePlayer::loadWave(mm::get().getChannel(m::mixer::PREVIEW_CHANNEL_ID), nullptr); - mm::swap(mm::SwapType::SOFT); + channel.samplePlayer->loadWave(channel, nullptr); + g_engine.model.swap(m::model::SwapType::SOFT); } /* -------------------------------------------------------------------------- */ void toNewChannel(ID channelId, Frame a, Frame b) { - ID columnId = G_MainWin->keyboard->getChannel(channelId)->getColumnId(); - m::mh::addAndLoadChannel(columnId, m::waveManager::createFromWave(getWave_(channelId), a, b)); + ID columnId = g_ui.mainWindow->keyboard->getChannel(channelId)->getColumnId(); + g_engine.mixerHandler.addAndLoadChannel(columnId, g_engine.waveManager.createFromWave(getWave_(channelId), a, b), + g_engine.kernelAudio.getBufferSize(), g_engine.channelManager); } /* -------------------------------------------------------------------------- */ @@ -374,11 +389,10 @@ void reload(ID channelId) void shift(ID channelId, Frame offset) { - namespace mm = m::model; - Frame shift = getSamplePlayer_(channelId).shift; - mm::DataLock lock(); + m::model::DataLock lock = g_engine.model.lockData(); + m::wfx::shift(getWave_(channelId), offset - shift); getSamplePlayer_(channelId).shift = offset; diff --git a/src/glue/sampleEditor.h b/src/glue/sampleEditor.h index 4a1473b..fe93099 100644 --- a/src/glue/sampleEditor.h +++ b/src/glue/sampleEditor.h @@ -35,25 +35,26 @@ namespace giada::m { class Wave; -} -namespace giada::m::channel -{ -struct Data; -} +class Channel; +} // namespace giada::m + namespace giada::v { class gdSampleEditor; } + namespace giada::c::sampleEditor { struct Data { Data() = default; - Data(const m::channel::Data&); + Data(const m::Channel&); ChannelStatus a_getPreviewStatus() const; Frame a_getPreviewTracker() const; const m::Wave& getWaveRef() const; // TODO - getWaveData (or public ptr member to Wave::data) + Frame getFramesInBar() const; + Frame getFramesInLoop() const; ID channelId; std::string name; @@ -70,8 +71,8 @@ struct Data std::string wavePath; bool isLogical; - private: - const m::channel::Data* m_channel; +private: + const m::Channel* m_channel; }; /* onRefresh --- TODO - wrong name */ diff --git a/src/glue/storage.cpp b/src/glue/storage.cpp index e468fac..8e842ae 100644 --- a/src/glue/storage.cpp +++ b/src/glue/storage.cpp @@ -26,8 +26,8 @@ #include "core/model/storage.h" #include "channel.h" -#include "core/clock.h" #include "core/conf.h" +#include "core/engine.h" #include "core/init.h" #include "core/mixer.h" #include "core/mixerHandler.h" @@ -36,7 +36,7 @@ #include "core/plugins/plugin.h" #include "core/plugins/pluginHost.h" #include "core/plugins/pluginManager.h" -#include "core/recorderHandler.h" +#include "core/sequencer.h" #include "core/wave.h" #include "core/waveManager.h" #include "gui/dialogs/browser/browserLoad.h" @@ -46,8 +46,9 @@ #include "gui/elems/basics/progress.h" #include "gui/elems/mainWindow/keyboard/column.h" #include "gui/elems/mainWindow/keyboard/keyboard.h" -#include "gui/model.h" +#include "gui/ui.h" #include "main.h" +#include "src/core/actions/actionRecorder.h" #include "storage.h" #include "utils/fs.h" #include "utils/gui.h" @@ -55,96 +56,21 @@ #include "utils/string.h" #include -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()) - 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& w : m::model::getAll()) - { - w->setPath(makeUniqueWavePath_(basePath, *w)); - m::waveManager::save(*w, w->getPath()); // TODO - error checking - } -} -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - void loadProject(void* data) { - v::gdBrowserLoad* browser = static_cast(data); - std::string fullPath = browser->getSelectedItem(); + v::gdBrowserLoad* browser = static_cast(data); - browser->showStatusBar(); - - u::log::print("[loadProject] load from %s\n", fullPath); + const std::string projectPath = browser->getSelectedItem(); + const std::string patchPath = projectPath + G_SLASH + u::fs::stripExt(u::fs::basename(projectPath)) + ".gptc"; - std::string fileToLoad = fullPath + G_SLASH + u::fs::stripExt(u::fs::basename(fullPath)) + ".gptc"; - std::string basePath = fullPath + G_SLASH; - - /* Read the patch from file. */ + browser->showStatusBar(); - m::patch::init(); - int res = m::patch::read(fileToLoad, basePath); - if (res != G_PATCH_OK) + if (int res = g_engine.load(projectPath, patchPath); res != G_PATCH_OK) { if (res == G_PATCH_UNREADABLE) v::gdAlert("This patch is unreadable."); @@ -156,35 +82,14 @@ void loadProject(void* data) return; } - /* Then reset the system (it disables mixer) and fill the model. */ - - m::init::reset(); - v::model::load(m::patch::patch); - m::model::load(m::patch::patch); - - /* Prepare the engine. Recorder has to recompute the actions positions if - the current samplerate != patch samplerate. Clock needs to update frames - in sequencer. */ - - m::mh::updateSoloCount(); - m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate); - m::clock::recomputeFrames(); - m::mixer::allocRecBuffer(m::clock::getMaxFramesInLoop()); + /* Update UI. */ - /* Mixer is ready to go back online. */ - - m::mixer::enable(); - - /* Utilities and cosmetics. Save patchPath by taking the last dir of the - broswer, in order to reuse it the next time. Also update UI. */ - - m::conf::conf.patchPath = u::fs::dirname(fullPath); - u::gui::updateMainWinLabel(m::patch::patch.name); + g_ui.load(g_engine.patch.data); #ifdef WITH_VST - if (m::pluginManager::hasMissingPlugins()) - v::gdAlert("Some plugins were not loaded successfully.\nCheck the plugin browser to know more."); + if (g_engine.pluginManager.hasMissingPlugins()) + v::gdAlert("Some plug-ins were not loaded successfully.\nCheck the Plug-in Browser to know more."); #endif @@ -195,35 +100,30 @@ void loadProject(void* data) void saveProject(void* data) { - v::gdBrowserSave* browser = static_cast(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(data); - if (name == "") + const std::string projectName = u::fs::stripExt(browser->getName()); + const std::string projectPath = browser->getCurrentPath() + G_SLASH + projectName + ".gprj"; + const std::string patchPath = projectPath + G_SLASH + projectName + ".gptc"; + + if (projectName == "") { v::gdAlert("Please choose a project name."); return; } - if (u::fs::dirExists(fullPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?")) + if (u::fs::dirExists(projectPath) && !v::gdConfirmWin("Warning", "Project exists: overwrite?")) return; - if (!u::fs::mkdir(fullPath)) + g_ui.store(g_engine.patch.data); + + if (!g_engine.store(projectName, projectPath, patchPath)) { - u::log::print("[saveProject] Unable to make project directory!\n"); + v::gdAlert("Unable to save the project!"); return; } - u::log::print("[saveProject] Project dir created: %s\n", fullPath); - - saveWavesToProject_(fullPath); - - if (savePatch_(gptcPath, name)) - browser->do_callback(); - else - v::gdAlert("Unable to save the project!"); + browser->do_callback(); } /* -------------------------------------------------------------------------- */ @@ -240,9 +140,9 @@ void loadSample(void* data) if (res == G_RES_OK) { - m::conf::conf.samplePath = u::fs::dirname(fullPath); + g_engine.conf.data.samplePath = u::fs::dirname(fullPath); browser->do_callback(); - G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open + g_ui.mainWindow->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open } } @@ -266,12 +166,12 @@ void saveSample(void* data) if (u::fs::fileExists(filePath) && !v::gdConfirmWin("Warning", "File exists: overwrite?")) return; - ID waveId = m::model::get().getChannel(channelId).samplePlayer->getWaveId(); - m::Wave* wave = m::model::find(waveId); + ID waveId = g_engine.model.get().getChannel(channelId).samplePlayer->getWaveId(); + m::Wave* wave = g_engine.model.findShared(waveId); assert(wave != nullptr); - if (!m::waveManager::save(*wave, filePath)) + if (!g_engine.waveManager.save(*wave, filePath)) { v::gdAlert("Unable to save this sample!"); return; @@ -281,11 +181,11 @@ void saveSample(void* data) /* Update last used path in conf, so that it can be reused next time. */ - m::conf::conf.samplePath = u::fs::dirname(filePath); + g_engine.conf.data.samplePath = u::fs::dirname(filePath); /* Update logical and edited states in Wave. */ - m::model::DataLock lock; + m::model::DataLock lock = g_engine.model.lockData(); wave->setLogical(false); wave->setEdited(false); @@ -293,6 +193,4 @@ void saveSample(void* data) browser->do_callback(); } -} // namespace storage -} // namespace c -} // namespace giada +} // namespace giada::c::storage \ No newline at end of file diff --git a/src/glue/storage.h b/src/glue/storage.h index 75352f7..6611300 100644 --- a/src/glue/storage.h +++ b/src/glue/storage.h @@ -27,18 +27,12 @@ #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 diff --git a/src/gui/dialogs/actionEditor/baseActionEditor.cpp b/src/gui/dialogs/actionEditor/baseActionEditor.cpp index 9acb522..dd8b480 100644 --- a/src/gui/dialogs/actionEditor/baseActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/baseActionEditor.cpp @@ -24,40 +24,56 @@ * * -------------------------------------------------------------------------- */ -#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 #include #include +#include #include -namespace giada +namespace giada::v { -namespace v -{ -gdBaseActionEditor::gdBaseActionEditor(ID channelId) -: gdWindow(640, 284) +gdBaseActionEditor::gdBaseActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat) +: gdWindow(conf.actionEditorX, conf.actionEditorY, conf.actionEditorW, conf.actionEditorH) , channelId(channelId) -, ratio(G_DEFAULT_ZOOM_RATIO) +, gridTool(0, 0, conf, framesInBeat) +, zoomInBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm) +, zoomOutBtn(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm) +, m_barTop(0, 0, Direction::HORIZONTAL) +, m_splitScroll(0, 0, 0, 0) +, m_conf(conf) +, m_playhead(0) +, m_ratio(conf.actionEditorZoom) { - using namespace giada::m; + end(); - if (conf::conf.actionEditorW) - { - resize(conf::conf.actionEditorX, conf::conf.actionEditorY, - conf::conf.actionEditorW, conf::conf.actionEditorH); - ratio = conf::conf.actionEditorZoom; - } + m_barTop.position(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN); + + m_splitScroll.resize( + G_GUI_OUTER_MARGIN, + (G_GUI_OUTER_MARGIN * 2) + 20, + w() - G_GUI_OUTER_MARGIN * 2, + h() - (G_GUI_OUTER_MARGIN * 3) - 20); + + zoomInBtn.callback(cb_zoomIn, this); + zoomInBtn.copy_tooltip("Zoom in"); + zoomOutBtn.callback(cb_zoomOut, this); + zoomOutBtn.copy_tooltip("Zoom out"); + + add(m_barTop); + add(m_splitScroll); } /* -------------------------------------------------------------------------- */ @@ -66,11 +82,19 @@ gdBaseActionEditor::~gdBaseActionEditor() { using namespace giada::m; - conf::conf.actionEditorX = x(); - conf::conf.actionEditorY = y(); - conf::conf.actionEditorW = w(); - conf::conf.actionEditorH = h(); - conf::conf.actionEditorZoom = ratio; + m_conf.actionEditorX = x(); + m_conf.actionEditorY = y(); + m_conf.actionEditorW = w(); + m_conf.actionEditorH = h(); + m_conf.actionEditorSplitH = m_splitScroll.getTopContentH(); + m_conf.actionEditorZoom = m_ratio; +} + +/* -------------------------------------------------------------------------- */ + +int gdBaseActionEditor::getMouseOverContent() const +{ + return m_splitScroll.getScrollX() + (Fl::event_x() - G_GUI_OUTER_MARGIN); } /* -------------------------------------------------------------------------- */ @@ -80,89 +104,38 @@ void gdBaseActionEditor::cb_zoomOut(Fl_Widget* /*w*/, void* p) { ((gdBaseActionE /* -------------------------------------------------------------------------- */ -void gdBaseActionEditor::computeWidth() +void gdBaseActionEditor::computeWidth(Frame framesInSeq, Frame framesInLoop) { - fullWidth = frameToPixel(m::clock::getFramesInSeq()); - loopWidth = frameToPixel(m::clock::getFramesInLoop()); + fullWidth = frameToPixel(framesInSeq); + loopWidth = frameToPixel(framesInLoop); } /* -------------------------------------------------------------------------- */ Pixel gdBaseActionEditor::frameToPixel(Frame f) const { - return f / ratio; + return f / m_ratio; } Frame gdBaseActionEditor::pixelToFrame(Pixel p, bool snap) const { - return snap ? gridTool->getSnapFrame(p * ratio) : p * ratio; + return snap ? gridTool.getSnapFrame(p * m_ratio) : p * m_ratio; } /* -------------------------------------------------------------------------- */ void gdBaseActionEditor::zoomIn() { - float ratioPrev = ratio; - - ratio /= 2; - if (ratio < MIN_RATIO) - ratio = MIN_RATIO; - - if (ratioPrev != ratio) - { - rebuild(); - centerViewportIn(); - redraw(); - } + // Explicit type std::max to fix MINMAX macro hell on Windows + zoomAbout([&r = m_ratio]() { return std::max(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 to fix MINMAX macro hell on Windows + zoomAbout([&r = m_ratio]() { return std::min(r * RATIO_STEP, MAX_RATIO); }); } /* -------------------------------------------------------------------------- */ @@ -178,7 +151,6 @@ void gdBaseActionEditor::prepareWindow() set_non_modal(); size_range(640, 284); - resizable(viewport); show(); } @@ -196,5 +168,64 @@ int gdBaseActionEditor::handle(int e) return Fl_Group::handle(e); } } -} // namespace v -} // namespace giada + +/* -------------------------------------------------------------------------- */ + +void gdBaseActionEditor::draw() +{ + gdWindow::draw(); + + const geompp::Rect splitBounds = m_splitScroll.getBoundsNoScrollbar(); + const geompp::Line playhead = splitBounds.getHeightAsLine().withX(m_playhead); + + if (splitBounds.contains(playhead)) + drawLine(playhead, G_COLOR_LIGHT_2); +} + +/* -------------------------------------------------------------------------- */ + +void gdBaseActionEditor::zoomAbout(std::function 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(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 diff --git a/src/gui/dialogs/actionEditor/baseActionEditor.h b/src/gui/dialogs/actionEditor/baseActionEditor.h index c76143d..b25a6a3 100644 --- a/src/gui/dialogs/actionEditor/baseActionEditor.h +++ b/src/gui/dialogs/actionEditor/baseActionEditor.h @@ -27,73 +27,88 @@ #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 f); + + Pixel currentFrameToPixel() const; + + Pixel m_playhead; + float m_ratio; +}; +} // namespace giada::v #endif diff --git a/src/gui/dialogs/actionEditor/midiActionEditor.cpp b/src/gui/dialogs/actionEditor/midiActionEditor.cpp index 851e716..911dfdf 100644 --- a/src/gui/dialogs/actionEditor/midiActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/midiActionEditor.cpp @@ -25,62 +25,33 @@ * -------------------------------------------------------------------------- */ #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 -namespace giada +namespace giada::v { -namespace v -{ -gdMidiActionEditor::gdMidiActionEditor(ID channelId) -: gdBaseActionEditor(channelId) +gdMidiActionEditor::gdMidiActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat) +: gdBaseActionEditor(channelId, conf, framesInBeat) +, m_barPadding(0, 0, w() - 150, G_GUI_UNIT) +, m_pianoRoll(0, 0, this) +, m_velocityEditor(0, 0, this) { end(); - computeWidth(); - - gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL); - gridTool = new geGridTool(0, 0); - geBox* b1 = new geBox(0, 0, w() - 150, G_GUI_UNIT); // padding actionType - zoomButtons - zoomInBtn = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); - zoomOutBtn = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm); - upperArea->add(gridTool); - upperArea->add(b1); - upperArea->add(zoomInBtn); - upperArea->add(zoomOutBtn); - upperArea->resizable(b1); - - /* Main viewport: contains all widgets. */ + m_barTop.add(&gridTool); + m_barTop.add(&m_barPadding); + m_barTop.add(&zoomInBtn); + m_barTop.add(&zoomOutBtn); + m_barTop.resizable(m_barPadding); - viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN, - upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0); - m_ne = new geNoteEditor(0, 0, this); - m_ner = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); - m_ve = new geVelocityEditor(0, 0, this); - m_ver = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); - viewport->add(m_ne); - viewport->add(m_ner); - viewport->add(m_ve); - viewport->add(m_ver); + m_splitScroll.addWidgets(m_pianoRoll, m_velocityEditor, conf.actionEditorSplitH); - zoomInBtn->callback(cb_zoomIn, (void*)this); - zoomInBtn->copy_tooltip("Zoom in"); - zoomOutBtn->callback(cb_zoomOut, (void*)this); - zoomOutBtn->copy_tooltip("Zoom out"); + if (conf.actionEditorPianoRollY != -1) + m_splitScroll.setScrollY(conf.actionEditorPianoRollY); - add(upperArea); - add(viewport); - resizable(upperArea); + resizable(m_splitScroll); // Make it resizable only once filled with widgets prepareWindow(); rebuild(); @@ -88,15 +59,23 @@ gdMidiActionEditor::gdMidiActionEditor(ID channelId) /* -------------------------------------------------------------------------- */ +gdMidiActionEditor::~gdMidiActionEditor() +{ + m_conf.actionEditorPianoRollY = m_splitScroll.getScrollY(); + m_barTop.remove(gridTool); + m_barTop.remove(zoomInBtn); + m_barTop.remove(zoomOutBtn); +} + +/* -------------------------------------------------------------------------- */ + void gdMidiActionEditor::rebuild() { m_data = c::actionEditor::getData(channelId); - computeWidth(); - m_ne->rebuild(m_data); - m_ner->size(m_ne->w(), m_ner->h()); - m_ve->rebuild(m_data); - m_ver->size(m_ve->w(), m_ver->h()); + computeWidth(m_data.framesInSeq, m_data.framesInLoop); + + m_pianoRoll.rebuild(m_data); + m_velocityEditor.rebuild(m_data); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/actionEditor/midiActionEditor.h b/src/gui/dialogs/actionEditor/midiActionEditor.h index 8539027..75749cf 100644 --- a/src/gui/dialogs/actionEditor/midiActionEditor.h +++ b/src/gui/dialogs/actionEditor/midiActionEditor.h @@ -28,31 +28,25 @@ #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 diff --git a/src/gui/dialogs/actionEditor/sampleActionEditor.cpp b/src/gui/dialogs/actionEditor/sampleActionEditor.cpp index a6aea68..5c75220 100644 --- a/src/gui/dialogs/actionEditor/sampleActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/sampleActionEditor.cpp @@ -24,80 +24,45 @@ * * -------------------------------------------------------------------------- */ -#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 -namespace giada +namespace giada::v { -namespace v -{ -gdSampleActionEditor::gdSampleActionEditor(ID channelId) -: gdBaseActionEditor(channelId) +gdSampleActionEditor::gdSampleActionEditor(ID channelId, m::Conf::Data& conf, Frame framesInBeat) +: gdBaseActionEditor(channelId, conf, framesInBeat) +, m_barPadding(0, 0, w() - 232, G_GUI_UNIT) +, m_sampleActionEditor(0, 0, this) +, m_envelopeEditor(0, 0, "Volume", this) +, m_actionType(0, 0, 80, G_GUI_UNIT) { end(); - computeWidth(); - - /* Container with zoom buttons and the action type selector. Scheme of the - resizable boxes: |[--b1--][actionType][--b2--][+][-]| */ - - gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL); - actionType = new geChoice(0, 0, 80, 20); - gridTool = new geGridTool(0, 0); - geBox* b1 = new geBox(0, 0, w() - 232, 20); // padding actionType - zoomButtons - zoomInBtn = new geButton(0, 0, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); - zoomOutBtn = new geButton(0, 0, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); - upperArea->add(actionType); - upperArea->add(gridTool); - upperArea->add(b1); - upperArea->add(zoomInBtn); - upperArea->add(zoomOutBtn); - upperArea->resizable(b1); - - actionType->add("Key press"); - actionType->add("Key release"); - actionType->add("Kill chan"); - actionType->value(0); - actionType->copy_tooltip("Action type to add"); + m_barTop.add(&m_actionType); + m_barTop.add(&gridTool); + m_barTop.add(&m_barPadding); + m_barTop.add(&zoomInBtn); + m_barTop.add(&zoomOutBtn); + m_barTop.resizable(m_barPadding); + + m_actionType.add("Key press"); + m_actionType.add("Key release"); + m_actionType.add("Kill chan"); + m_actionType.value(0); + m_actionType.copy_tooltip("Action type to add"); if (!canChangeActionType()) - actionType->deactivate(); - - zoomInBtn->callback(cb_zoomIn, (void*)this); - zoomInBtn->copy_tooltip("Zoom in"); - zoomOutBtn->callback(cb_zoomOut, (void*)this); - zoomOutBtn->copy_tooltip("Zoom out"); - - /* Main viewport: contains all widgets. */ - - viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN, - upperArea->w(), h() - 44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0); - m_ae = new geSampleActionEditor(0, 0, this); - m_aer = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); - m_ee = new geEnvelopeEditor(0, 0, "volume", this); - m_eer = new geResizerBar(0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); - viewport->add(m_ae); - viewport->add(m_aer); - viewport->add(m_ee); - viewport->add(m_eer); - - add(upperArea); - add(viewport); - resizable(upperArea); + m_actionType.deactivate(); + + m_splitScroll.addWidgets(m_sampleActionEditor, m_envelopeEditor, conf.actionEditorSplitH); + + resizable(m_splitScroll); // Make it resizable only once filled with widgets prepareWindow(); rebuild(); @@ -105,6 +70,16 @@ gdSampleActionEditor::gdSampleActionEditor(ID channelId) /* -------------------------------------------------------------------------- */ +gdSampleActionEditor::~gdSampleActionEditor() +{ + m_barTop.remove(m_actionType); + m_barTop.remove(gridTool); + m_barTop.remove(zoomInBtn); + m_barTop.remove(zoomOutBtn); +} + +/* -------------------------------------------------------------------------- */ + bool gdSampleActionEditor::canChangeActionType() { return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS && @@ -117,13 +92,25 @@ void gdSampleActionEditor::rebuild() { m_data = c::actionEditor::getData(channelId); - canChangeActionType() ? actionType->activate() : actionType->deactivate(); - computeWidth(); + canChangeActionType() ? m_actionType.activate() : m_actionType.deactivate(); + computeWidth(m_data.framesInSeq, m_data.framesInLoop); + + m_sampleActionEditor.rebuild(m_data); + m_envelopeEditor.rebuild(m_data); +} - m_ae->rebuild(m_data); - m_aer->size(m_ae->w(), m_aer->h()); - m_ee->rebuild(m_data); - m_eer->size(m_ee->w(), m_eer->h()); +/* -------------------------------------------------------------------------- */ + +int gdSampleActionEditor::getActionType() const +{ + if (m_actionType.value() == 0) + return m::MidiEvent::NOTE_ON; + else if (m_actionType.value() == 1) + return m::MidiEvent::NOTE_OFF; + else if (m_actionType.value() == 2) + return m::MidiEvent::NOTE_KILL; + + assert(false); + return -1; } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/actionEditor/sampleActionEditor.h b/src/gui/dialogs/actionEditor/sampleActionEditor.h index 03d167b..2021331 100644 --- a/src/gui/dialogs/actionEditor/sampleActionEditor.h +++ b/src/gui/dialogs/actionEditor/sampleActionEditor.h @@ -28,32 +28,31 @@ #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 diff --git a/src/gui/dialogs/beatsInput.cpp b/src/gui/dialogs/beatsInput.cpp index e022b89..7951b7d 100644 --- a/src/gui/dialogs/beatsInput.cpp +++ b/src/gui/dialogs/beatsInput.cpp @@ -25,10 +25,7 @@ * -------------------------------------------------------------------------- */ #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" @@ -40,30 +37,28 @@ extern giada::v::gdMainWindow* mainWin; -namespace giada +namespace giada::v { -namespace v -{ -gdBeatsInput::gdBeatsInput() +gdBeatsInput::gdBeatsInput(int beats, int bars) : gdWindow(u::gui::centerWindowX(180), u::gui::centerWindowY(36), 180, 36, "Beats") { set_modal(); - beats = new geInput(8, 8, 43, G_GUI_UNIT); - bars = new geInput(beats->x() + beats->w() + 4, 8, 43, G_GUI_UNIT); - ok = new geButton(bars->x() + bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok"); + m_beats = new geInput(8, 8, 43, G_GUI_UNIT); + m_bars = new geInput(m_beats->x() + m_beats->w() + 4, 8, 43, G_GUI_UNIT); + m_ok = new geButton(m_bars->x() + m_bars->w() + 4, 8, 70, G_GUI_UNIT, "Ok"); end(); - beats->maximum_size(2); - beats->value(std::to_string(m::clock::getBeats()).c_str()); - beats->type(FL_INT_INPUT); + m_beats->maximum_size(2); + m_beats->value(std::to_string(beats).c_str()); + m_beats->type(FL_INT_INPUT); - bars->maximum_size(2); - bars->value(std::to_string(m::clock::getBars()).c_str()); - bars->type(FL_INT_INPUT); + m_bars->maximum_size(2); + m_bars->value(std::to_string(bars).c_str()); + m_bars->type(FL_INT_INPUT); - ok->shortcut(FL_Enter); - ok->callback(cb_update, (void*)this); + m_ok->shortcut(FL_Enter); + m_ok->callback(cb_update, (void*)this); u::gui::setFavicon(this); setId(WID_BEATS); @@ -78,11 +73,9 @@ void gdBeatsInput::cb_update(Fl_Widget* /*w*/, void* p) { ((gdBeatsInput*)p)->cb void gdBeatsInput::cb_update() { - if (!strcmp(beats->value(), "") || !strcmp(bars->value(), "")) + if (!strcmp(m_beats->value(), "") || !strcmp(m_bars->value(), "")) return; - c::main::setBeats(atoi(beats->value()), atoi(bars->value())); + c::main::setBeats(atoi(m_beats->value()), atoi(m_bars->value())); do_callback(); } - -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/beatsInput.h b/src/gui/dialogs/beatsInput.h index 5cae21c..19ed954 100644 --- a/src/gui/dialogs/beatsInput.h +++ b/src/gui/dialogs/beatsInput.h @@ -33,24 +33,21 @@ class geInput; class geButton; class geCheck; -namespace giada -{ -namespace v +namespace giada::v { class gdBeatsInput : public gdWindow { public: - gdBeatsInput(); + gdBeatsInput(int beats, int bars); - private: +private: static void cb_update(Fl_Widget* /*w*/, void* p); void cb_update(); - geInput* beats; - geInput* bars; - geButton* ok; + geInput* m_beats; + geInput* m_bars; + geButton* m_ok; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/bpmInput.cpp b/src/gui/dialogs/bpmInput.cpp index 5c07639..0364ae4 100644 --- a/src/gui/dialogs/bpmInput.cpp +++ b/src/gui/dialogs/bpmInput.cpp @@ -25,7 +25,6 @@ * -------------------------------------------------------------------------- */ #include "bpmInput.h" -#include "core/clock.h" #include "core/conf.h" #include "core/const.h" #include "core/mixer.h" diff --git a/src/gui/dialogs/browser/browserBase.cpp b/src/gui/dialogs/browser/browserBase.cpp index 90c4fa4..eb97019 100644 --- a/src/gui/dialogs/browser/browserBase.cpp +++ b/src/gui/dialogs/browser/browserBase.cpp @@ -24,7 +24,7 @@ * * -------------------------------------------------------------------------- */ -#include "browserBase.h" +#include "gui/dialogs/browser/browserBase.h" #include "core/conf.h" #include "core/const.h" #include "core/graphics.h" @@ -36,15 +36,14 @@ #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 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 callback, ID channelId, m::Conf::Data& c) +: gdWindow(c.browserX, c.browserY, c.browserW, + c.browserH, title.c_str()) , m_callback(callback) +, m_conf(c) , m_channelId(channelId) { set_non_modal(); @@ -66,8 +65,8 @@ gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path, browser = new geBrowser(8, groupTop->y() + groupTop->h() + 8, w() - 16, h() - 101); browser->loadDir(path); - if (path == m::conf::conf.browserLastPath) - browser->preselect(m::conf::conf.browserPosition, m::conf::conf.browserLastValue); + if (path == m_conf.browserLastPath) + browser->preselect(m_conf.browserPosition, m_conf.browserLastValue); Fl_Group* groupButtons = new Fl_Group(8, browser->y() + browser->h() + 8, w() - 16, 20); ok = new geButton(w() - 88, groupButtons->y(), 80, 20); @@ -94,13 +93,13 @@ gdBrowserBase::gdBrowserBase(const std::string& title, const std::string& path, gdBrowserBase::~gdBrowserBase() { - m::conf::conf.browserX = x(); - m::conf::conf.browserY = y(); - m::conf::conf.browserW = w(); - m::conf::conf.browserH = h(); - m::conf::conf.browserPosition = browser->position(); - m::conf::conf.browserLastPath = browser->getCurrentDir(); - m::conf::conf.browserLastValue = browser->value(); + m_conf.browserX = x(); + m_conf.browserY = y(); + m_conf.browserW = w(); + m_conf.browserH = h(); + m_conf.browserPosition = browser->position(); + m_conf.browserLastPath = browser->getCurrentDir(); + m_conf.browserLastValue = browser->value(); } /* -------------------------------------------------------------------------- */ @@ -176,6 +175,4 @@ void gdBrowserBase::fireCallback() const { m_callback((void*)this); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/browser/browserBase.h b/src/gui/dialogs/browser/browserBase.h index 4781d73..6244cda 100644 --- a/src/gui/dialogs/browser/browserBase.h +++ b/src/gui/dialogs/browser/browserBase.h @@ -27,6 +27,7 @@ #ifndef GD_BROWSER_BASE_H #define GD_BROWSER_BASE_H +#include "core/conf.h" #include "core/types.h" #include "gui/dialogs/window.h" #include @@ -38,13 +39,12 @@ class geButton; class geInput; class geProgress; -namespace giada -{ -namespace m +namespace giada::m { class Channel; } -namespace v + +namespace giada::v { class geBrowser; class gdBrowserBase : public gdWindow @@ -69,9 +69,9 @@ public: void showStatusBar(); void hideStatusBar(); - protected: +protected: gdBrowserBase(const std::string& title, const std::string& path, - std::function f, ID channelId); + std::function f, ID channelId, m::Conf::Data&); static void cb_up(Fl_Widget* /*w*/, void* p); static void cb_close(Fl_Widget* /*w*/, void* p); @@ -85,7 +85,8 @@ public: std::function m_callback; - ID m_channelId; + m::Conf::Data& m_conf; + ID m_channelId; Fl_Group* groupTop; geCheck* hiddenFiles; @@ -96,7 +97,6 @@ public: geButton* updir; geProgress* status; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/browser/browserDir.cpp b/src/gui/dialogs/browser/browserDir.cpp index 6b2a1fb..46ee903 100644 --- a/src/gui/dialogs/browser/browserDir.cpp +++ b/src/gui/dialogs/browser/browserDir.cpp @@ -30,13 +30,11 @@ #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 cb) -: gdBrowserBase(title, path, cb, 0) + std::function cb, m::Conf::Data& conf) +: gdBrowserBase(title, path, cb, 0, conf) { where->size(groupTop->w() - updir->w() - 8, 20); @@ -76,6 +74,4 @@ void gdBrowserDir::cb_down() browser->loadDir(path); where->value(browser->getCurrentDir().c_str()); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/browser/browserDir.h b/src/gui/dialogs/browser/browserDir.h index cc14020..daf93fd 100644 --- a/src/gui/dialogs/browser/browserDir.h +++ b/src/gui/dialogs/browser/browserDir.h @@ -27,25 +27,23 @@ #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 cb); + std::function 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 diff --git a/src/gui/dialogs/browser/browserLoad.cpp b/src/gui/dialogs/browser/browserLoad.cpp index edaa07d..4b1d7e8 100644 --- a/src/gui/dialogs/browser/browserLoad.cpp +++ b/src/gui/dialogs/browser/browserLoad.cpp @@ -30,13 +30,11 @@ #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 cb, ID channelId) -: gdBrowserBase(title, path, cb, channelId) + std::function cb, ID channelId, m::Conf::Data& conf) +: gdBrowserBase(title, path, cb, channelId, conf) { where->size(groupTop->w() - updir->w() - 8, 20); @@ -76,6 +74,4 @@ void gdBrowserLoad::cb_down() browser->loadDir(path); where->value(browser->getCurrentDir().c_str()); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/browser/browserLoad.h b/src/gui/dialogs/browser/browserLoad.h index 3c6070a..c01965b 100644 --- a/src/gui/dialogs/browser/browserLoad.h +++ b/src/gui/dialogs/browser/browserLoad.h @@ -28,28 +28,27 @@ #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 cb, ID channelId); + std::function 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 diff --git a/src/gui/dialogs/browser/browserSave.cpp b/src/gui/dialogs/browser/browserSave.cpp index bfcf3fe..eb9e358 100644 --- a/src/gui/dialogs/browser/browserSave.cpp +++ b/src/gui/dialogs/browser/browserSave.cpp @@ -30,13 +30,12 @@ #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 cb, ID channelId) -: gdBrowserBase(title, path, cb, channelId) + const std::string& name_, std::function cb, ID channelId, + m::Conf::Data& conf) +: gdBrowserBase(title, path, cb, channelId, conf) { where->size(groupTop->w() - 236, 20); @@ -96,6 +95,4 @@ void gdBrowserSave::cb_save() { fireCallback(); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/browser/browserSave.h b/src/gui/dialogs/browser/browserSave.h index 92f3342..c8d7c86 100644 --- a/src/gui/dialogs/browser/browserSave.h +++ b/src/gui/dialogs/browser/browserSave.h @@ -27,28 +27,28 @@ #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 cb, - ID channelId); + ID channelId, m::Conf::Data&); std::string getName() const; - private: +private: geInput* name; static void cb_down(Fl_Widget* /*w*/, void* p); @@ -56,7 +56,6 @@ public: void cb_down(); void cb_save(); }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/config.cpp b/src/gui/dialogs/config.cpp index f1933f1..fee616f 100644 --- a/src/gui/dialogs/config.cpp +++ b/src/gui/dialogs/config.cpp @@ -37,11 +37,9 @@ #include "utils/gui.h" #include -namespace giada +namespace giada::v { -namespace v -{ -gdConfig::gdConfig(int w, int h) +gdConfig::gdConfig(int w, int h, m::Conf::Data& conf) : gdWindow(u::gui::centerWindowX(w), u::gui::centerWindowY(h), w, h, "Configuration") { Fl_Tabs* tabs = new Fl_Tabs(8, 8, w - 16, h - 44); @@ -51,8 +49,8 @@ gdConfig::gdConfig(int w, int h) tabAudio = new geTabAudio(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40); tabMidi = new geTabMidi(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40); - tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40); - tabMisc = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40); + tabBehaviors = new geTabBehaviors(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40, conf); + tabMisc = new geTabMisc(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20); #ifdef WITH_VST tabPlugins = new geTabPlugins(tabs->x() + 10, tabs->y() + 20, tabs->w() - 20, tabs->h() - 40); #endif @@ -97,17 +95,4 @@ void gdConfig::cb_cancel() { do_callback(); } - -/* -------------------------------------------------------------------------- */ - -#ifdef WITH_VST - -void gdConfig::refreshVstPath() -{ - tabPlugins->refreshVstPath(); -} - -#endif - -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v diff --git a/src/gui/dialogs/config.h b/src/gui/dialogs/config.h index 0410162..de25911 100644 --- a/src/gui/dialogs/config.h +++ b/src/gui/dialogs/config.h @@ -27,6 +27,7 @@ #ifndef GD_CONFIG_H #define GD_CONFIG_H +#include "core/conf.h" #include "window.h" class geButton; @@ -34,9 +35,7 @@ class geCheck; class geInput; class geBox; -namespace giada -{ -namespace v +namespace giada::v { class geChoice; class geTabAudio; @@ -49,11 +48,7 @@ class geTabPlugins; class gdConfig : public gdWindow { public: - gdConfig(int w, int h); - -#ifdef WITH_VST - void refreshVstPath(); -#endif + gdConfig(int w, int h, m::Conf::Data&); geTabAudio* tabAudio; geTabBehaviors* tabBehaviors; @@ -65,13 +60,12 @@ public: geButton* save; geButton* cancel; - private: +private: static void cb_save_config(Fl_Widget* /*w*/, void* p); static void cb_cancel(Fl_Widget* /*w*/, void* p); void cb_save_config(); void cb_cancel(); }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/keyGrabber.h b/src/gui/dialogs/keyGrabber.h index a4c5feb..bd66e8f 100644 --- a/src/gui/dialogs/keyGrabber.h +++ b/src/gui/dialogs/keyGrabber.h @@ -33,16 +33,12 @@ class geBox; class geButton; -namespace giada -{ -namespace c -{ -namespace channel +namespace giada::c::channel { struct Data; } -} // namespace c -namespace v + +namespace giada::v { class gdKeyGrabber : public gdWindow { @@ -52,7 +48,7 @@ public: int handle(int e) override; void rebuild() override; - private: +private: static void cb_clear(Fl_Widget* /*w*/, void* p); static void cb_cancel(Fl_Widget* /*w*/, void* p); void cb_clear(); @@ -67,7 +63,6 @@ public: geButton* m_clear; geButton* m_cancel; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/mainWindow.cpp b/src/gui/dialogs/mainWindow.cpp index f27d2ca..ef5072c 100644 --- a/src/gui/dialogs/mainWindow.cpp +++ b/src/gui/dialogs/mainWindow.cpp @@ -25,7 +25,6 @@ * -------------------------------------------------------------------------- */ #include "mainWindow.h" -#include "core/clock.h" #include "core/conf.h" #include "core/const.h" #include "core/init.h" @@ -43,9 +42,11 @@ namespace giada::v { -gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv) +gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv, m::Conf::Data& c) : gdWindow(W, H, title) +, m_conf(c) { + Fl::visible_focus(0); Fl::background(25, 25, 25); // TODO use G_COLOR_GREY_1 @@ -61,7 +62,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg Fl_Tooltip::color(G_COLOR_GREY_1); Fl_Tooltip::textcolor(G_COLOR_LIGHT_2); Fl_Tooltip::size(G_GUI_FONT_SIZE_BASE); - Fl_Tooltip::enable(m::conf::conf.showTooltips); + Fl_Tooltip::enable(m_conf.showTooltips); size_range(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT); @@ -118,10 +119,10 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg gdMainWindow::~gdMainWindow() { - m::conf::conf.mainWindowX = x(); - m::conf::conf.mainWindowY = y(); - m::conf::conf.mainWindowW = w(); - m::conf::conf.mainWindowH = h(); + m_conf.mainWindowX = x(); + m_conf.mainWindowY = y(); + m_conf.mainWindowW = w(); + m_conf.mainWindowH = h(); } /* -------------------------------------------------------------------------- */ diff --git a/src/gui/dialogs/mainWindow.h b/src/gui/dialogs/mainWindow.h index a99689e..4cb9c26 100644 --- a/src/gui/dialogs/mainWindow.h +++ b/src/gui/dialogs/mainWindow.h @@ -28,6 +28,7 @@ #define GD_MAINWINDOW_H #include "window.h" +#include "core/conf.h" namespace giada::v { @@ -40,7 +41,7 @@ class geMainTimer; class gdMainWindow : public gdWindow { public: - gdMainWindow(int w, int h, const char* title, int argc, char** argv); + gdMainWindow(int w, int h, const char* title, int argc, char** argv, m::Conf::Data&); ~gdMainWindow(); void refresh() override; @@ -57,6 +58,9 @@ public: geMainIO* mainIO; geMainTimer* mainTimer; geMainTransport* mainTransport; + +private: + m::Conf::Data& m_conf; }; } // namespace giada::v diff --git a/src/gui/dialogs/midiIO/midiInputBase.cpp b/src/gui/dialogs/midiIO/midiInputBase.cpp index f5cce51..f2e4a6d 100644 --- a/src/gui/dialogs/midiIO/midiInputBase.cpp +++ b/src/gui/dialogs/midiIO/midiInputBase.cpp @@ -28,12 +28,12 @@ #include "core/conf.h" #include "glue/io.h" -namespace giada +namespace giada::v { -namespace v -{ -gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title) +gdMidiInputBase::gdMidiInputBase(int x, int y, int w, int h, const char* title, + m::Conf::Data& c) : gdWindow(x, y, w, h, title) +, m_conf(c) { } @@ -43,10 +43,10 @@ gdMidiInputBase::~gdMidiInputBase() { c::io::stopMidiLearn(); - m::conf::conf.midiInputX = x(); - m::conf::conf.midiInputY = y(); - m::conf::conf.midiInputW = w(); - m::conf::conf.midiInputH = h(); + m_conf.midiInputX = x(); + m_conf.midiInputY = y(); + m_conf.midiInputW = w(); + m_conf.midiInputH = h(); } /* -------------------------------------------------------------------------- */ @@ -59,6 +59,4 @@ void gdMidiInputBase::cb_close() { do_callback(); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/midiIO/midiInputBase.h b/src/gui/dialogs/midiIO/midiInputBase.h index 78dd91f..8546354 100644 --- a/src/gui/dialogs/midiIO/midiInputBase.h +++ b/src/gui/dialogs/midiIO/midiInputBase.h @@ -27,15 +27,14 @@ #ifndef GD_MIDI_INPUT_BASE_H #define GD_MIDI_INPUT_BASE_H +#include "core/conf.h" #include "gui/dialogs/window.h" #include "gui/elems/midiIO/midiLearner.h" class geButton; class geCheck; -namespace giada -{ -namespace v +namespace giada::v { class geChoice; class gdMidiInputBase : public gdWindow @@ -43,17 +42,18 @@ class gdMidiInputBase : public gdWindow public: virtual ~gdMidiInputBase(); - protected: - gdMidiInputBase(int x, int y, int w, int h, const char* title = ""); +protected: + gdMidiInputBase(int x, int y, int w, int h, const char* title, m::Conf::Data&); static void cb_close(Fl_Widget* /*w*/, void* p); void cb_close(); + m::Conf::Data& m_conf; + geButton* m_ok; geCheck* m_enable; geChoice* m_channel; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/midiIO/midiInputChannel.cpp b/src/gui/dialogs/midiIO/midiInputChannel.cpp index 7130705..84621f9 100644 --- a/src/gui/dialogs/midiIO/midiInputChannel.cpp +++ b/src/gui/dialogs/midiIO/midiInputChannel.cpp @@ -44,9 +44,7 @@ #include "midiInputChannel.h" #include "utils/string.h" -namespace giada -{ -namespace v +namespace giada::v { geChannelLearnerPack::geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& channel) : geMidiLearnerPack(x, y, "Channel") @@ -114,11 +112,8 @@ void gePluginLearnerPack::update(const c::io::PluginData& d, bool enabled) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -gdMidiInputChannel::gdMidiInputChannel(ID channelId) -: gdMidiInputBase(m::conf::conf.midiInputX, - m::conf::conf.midiInputY, - m::conf::conf.midiInputW, - m::conf::conf.midiInputH) +gdMidiInputChannel::gdMidiInputChannel(ID channelId, m::Conf::Data& c) +: gdMidiInputBase(c.midiInputX, c.midiInputY, c.midiInputW, c.midiInputH, "", c) , m_channelId(channelId) , m_data(c::io::channel_getInputData(channelId)) { @@ -257,5 +252,4 @@ void gdMidiInputChannel::cb_setChannel() c::io::channel_setMidiInputFilter(m_data.channelId, m_channel->value() == 0 ? -1 : m_channel->value() - 1); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/midiIO/midiInputChannel.h b/src/gui/dialogs/midiIO/midiInputChannel.h index 88c8832..9266feb 100644 --- a/src/gui/dialogs/midiIO/midiInputChannel.h +++ b/src/gui/dialogs/midiIO/midiInputChannel.h @@ -29,15 +29,14 @@ #ifndef GD_MIDI_INPUT_CHANNEL_H #define GD_MIDI_INPUT_CHANNEL_H +#include "core/conf.h" #include "glue/io.h" #include "gui/elems/midiIO/midiLearnerPack.h" #include "midiInputBase.h" class geCheck; -namespace giada -{ -namespace v +namespace giada::v { class geChoice; class geScrollPack; @@ -68,11 +67,11 @@ public: class gdMidiInputChannel : public gdMidiInputBase { public: - gdMidiInputChannel(ID channelId); + gdMidiInputChannel(ID channelId, m::Conf::Data&); void rebuild() override; - private: +private: static void cb_enable(Fl_Widget* /*w*/, void* p); static void cb_setChannel(Fl_Widget* /*w*/, void* p); static void cb_veloAsVol(Fl_Widget* /*w*/, void* p); @@ -87,7 +86,6 @@ public: geScrollPack* m_container; geCheck* m_veloAsVol; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/midiIO/midiInputMaster.cpp b/src/gui/dialogs/midiIO/midiInputMaster.cpp index 3380293..e2bf9b0 100644 --- a/src/gui/dialogs/midiIO/midiInputMaster.cpp +++ b/src/gui/dialogs/midiIO/midiInputMaster.cpp @@ -36,9 +36,7 @@ #include "utils/gui.h" #include -namespace giada -{ -namespace v +namespace giada::v { geMasterLearnerPack::geMasterLearnerPack(int x, int y) : geMidiLearnerPack(x, y) @@ -77,8 +75,8 @@ void geMasterLearnerPack::update(const c::io::Master_InputData& d) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -gdMidiInputMaster::gdMidiInputMaster() -: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)") +gdMidiInputMaster::gdMidiInputMaster(m::Conf::Data& c) +: gdMidiInputBase(c.midiInputX, c.midiInputY, 300, 284, "MIDI Input Setup (global)", c) { end(); @@ -156,5 +154,4 @@ void gdMidiInputMaster::cb_setChannel() { c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/dialogs/midiIO/midiInputMaster.h b/src/gui/dialogs/midiIO/midiInputMaster.h index d303189..a4013b0 100644 --- a/src/gui/dialogs/midiIO/midiInputMaster.h +++ b/src/gui/dialogs/midiIO/midiInputMaster.h @@ -27,15 +27,14 @@ #ifndef GD_MIDI_INPUT_MASTER_H #define GD_MIDI_INPUT_MASTER_H +#include "core/conf.h" #include "glue/io.h" #include "gui/elems/midiIO/midiLearnerPack.h" #include "midiInputBase.h" class geCheck; -namespace giada -{ -namespace v +namespace giada::v { class geChoice; class geMasterLearnerPack : public geMidiLearnerPack @@ -51,11 +50,11 @@ public: class gdMidiInputMaster : public gdMidiInputBase { public: - gdMidiInputMaster(); + gdMidiInputMaster(m::Conf::Data&); void rebuild() override; - private: +private: static void cb_enable(Fl_Widget* /*w*/, void* p); static void cb_setChannel(Fl_Widget* /*w*/, void* p); void cb_enable(); @@ -65,7 +64,6 @@ public: geMasterLearnerPack* m_learners; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/pluginChooser.cpp b/src/gui/dialogs/pluginChooser.cpp index e557b77..c0d94b3 100644 --- a/src/gui/dialogs/pluginChooser.cpp +++ b/src/gui/dialogs/pluginChooser.cpp @@ -28,8 +28,6 @@ #include "pluginChooser.h" #include "core/conf.h" -#include "core/plugins/pluginHost.h" -#include "core/plugins/pluginManager.h" #include "glue/plugin.h" #include "gui/elems/basics/box.h" #include "gui/elems/basics/button.h" @@ -37,12 +35,11 @@ #include "gui/elems/plugin/pluginBrowser.h" #include "utils/gui.h" -namespace giada +namespace giada::v { -namespace v -{ -gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId) +gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId, m::Conf::Data& c) : gdWindow(X, Y, W, H, "Available plugins") +, m_conf(c) , m_channelId(channelId) { /* top area */ @@ -69,7 +66,7 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId) sortMethod->add("Category"); sortMethod->add("Manufacturer"); sortMethod->callback(cb_sort, (void*)this); - sortMethod->value(m::conf::conf.pluginSortMethod); + sortMethod->value(m_conf.pluginSortMethod); addBtn->callback(cb_add, (void*)this); addBtn->shortcut(FL_Enter); @@ -84,11 +81,11 @@ gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId) gdPluginChooser::~gdPluginChooser() { - m::conf::conf.pluginChooserX = x(); - m::conf::conf.pluginChooserY = y(); - m::conf::conf.pluginChooserW = w(); - m::conf::conf.pluginChooserH = h(); - m::conf::conf.pluginSortMethod = sortMethod->value(); + m_conf.pluginChooserX = x(); + m_conf.pluginChooserY = y(); + m_conf.pluginChooserW = w(); + m_conf.pluginChooserH = h(); + m_conf.pluginSortMethod = sortMethod->value(); } /* -------------------------------------------------------------------------- */ @@ -108,7 +105,7 @@ void gdPluginChooser::cb_close() void gdPluginChooser::cb_sort() { - m::pluginManager::sortPlugins(static_cast(sortMethod->value())); + c::plugin::sortPlugins(static_cast(sortMethod->value())); browser->refresh(); } @@ -122,7 +119,6 @@ void gdPluginChooser::cb_add() c::plugin::addPlugin(pluginIndex, m_channelId); do_callback(); } -} // namespace v -} // namespace giada +} // namespace giada::v #endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/pluginChooser.h b/src/gui/dialogs/pluginChooser.h index 201035e..a5ab323 100644 --- a/src/gui/dialogs/pluginChooser.h +++ b/src/gui/dialogs/pluginChooser.h @@ -29,6 +29,7 @@ #ifndef GD_PLUGIN_CHOOSER_H #define GD_PLUGIN_CHOOSER_H +#include "core/conf.h" #include "core/types.h" #include "window.h" #include @@ -37,9 +38,7 @@ class geButton; class geButton; -namespace giada -{ -namespace v +namespace giada::v { class geChoice; class gePluginBrowser; @@ -47,10 +46,10 @@ class gePluginBrowser; class gdPluginChooser : public gdWindow { public: - gdPluginChooser(int x, int y, int w, int h, ID channelId); + gdPluginChooser(int x, int y, int w, int h, ID channelId, m::Conf::Data&); ~gdPluginChooser(); - private: +private: static void cb_close(Fl_Widget* /*w*/, void* p); static void cb_add(Fl_Widget* /*w*/, void* p); static void cb_sort(Fl_Widget* /*w*/, void* p); @@ -58,6 +57,8 @@ public: void cb_add(); void cb_sort(); + m::Conf::Data& m_conf; + geChoice* sortMethod; geButton* addBtn; geButton* cancelBtn; @@ -65,8 +66,7 @@ public: ID m_channelId; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/pluginList.cpp b/src/gui/dialogs/pluginList.cpp index 514d9e1..12e73c4 100644 --- a/src/gui/dialogs/pluginList.cpp +++ b/src/gui/dialogs/pluginList.cpp @@ -26,9 +26,10 @@ #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" @@ -36,27 +37,23 @@ #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 #include -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { -gdPluginList::gdPluginList(ID channelId) -: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204) +gdPluginList::gdPluginList(ID channelId, m::Conf::Data& c) +: gdWindow(c.pluginListX, c.pluginListY, 468, 204) +, m_conf(c) , m_channelId(channelId) { end(); list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, - w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2)); + w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2), + Direction::VERTICAL); list->end(); add(list); resizable(list); @@ -71,8 +68,8 @@ gdPluginList::gdPluginList(ID channelId) gdPluginList::~gdPluginList() { - m::conf::conf.pluginListX = x(); - m::conf::conf.pluginListY = y(); + m_conf.pluginListX = x(); + m_conf.pluginListY = y(); } /* -------------------------------------------------------------------------- */ @@ -85,9 +82,9 @@ void gdPluginList::rebuild() { m_plugins = c::plugin::getPlugins(m_channelId); - if (m_plugins.channelId == m::mixer::MASTER_OUT_CHANNEL_ID) + if (m_plugins.channelId == m::Mixer::MASTER_OUT_CHANNEL_ID) label("Master Out Plug-ins"); - else if (m_plugins.channelId == m::mixer::MASTER_IN_CHANNEL_ID) + else if (m_plugins.channelId == m::Mixer::MASTER_IN_CHANNEL_ID) label("Master In Plug-ins"); else { @@ -112,11 +109,7 @@ void gdPluginList::rebuild() void gdPluginList::cb_addPlugin() { - int wx = m::conf::conf.pluginChooserX; - int wy = m::conf::conf.pluginChooserY; - int ww = m::conf::conf.pluginChooserW; - int wh = m::conf::conf.pluginChooserH; - u::gui::openSubWindow(G_MainWin, new v::gdPluginChooser(wx, wy, ww, wh, m_plugins.channelId), WID_FX_CHOOSER); + c::layout::openPluginChooser(m_plugins.channelId); } /* -------------------------------------------------------------------------- */ @@ -138,7 +131,6 @@ const gePluginElement& gdPluginList::getPrevElement(const gePluginElement& currE prev = 0; return *static_cast(list->child(prev)); } -} // namespace v -} // namespace giada +} // namespace giada::v #endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/pluginList.h b/src/gui/dialogs/pluginList.h index 0e2d224..a1f4c10 100644 --- a/src/gui/dialogs/pluginList.h +++ b/src/gui/dialogs/pluginList.h @@ -29,21 +29,20 @@ #ifndef GD_PLUGINLIST_H #define GD_PLUGINLIST_H +#include "core/conf.h" #include "glue/plugin.h" #include "window.h" -class geLiquidScroll; class geButton; -namespace giada -{ -namespace v +namespace giada::v { +class geLiquidScroll; class gePluginElement; class gdPluginList : public gdWindow { public: - gdPluginList(ID channelId); + gdPluginList(ID channelId, m::Conf::Data&); ~gdPluginList(); void rebuild() override; @@ -51,18 +50,19 @@ public: const gePluginElement& getNextElement(const gePluginElement& curr) const; const gePluginElement& getPrevElement(const gePluginElement& curr) const; - private: +private: static void cb_addPlugin(Fl_Widget* /*w*/, void* p); void cb_addPlugin(); + m::Conf::Data& m_conf; + geButton* addPlugin; geLiquidScroll* list; ID m_channelId; c::plugin::Plugins m_plugins; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dialogs/pluginWindow.cpp b/src/gui/dialogs/pluginWindow.cpp index 31ea919..a7f5f07 100644 --- a/src/gui/dialogs/pluginWindow.cpp +++ b/src/gui/dialogs/pluginWindow.cpp @@ -43,7 +43,8 @@ gdPluginWindow::gdPluginWindow(const c::plugin::Plugin& plugin) set_non_modal(); m_list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, - w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2)); + w() - (G_GUI_OUTER_MARGIN * 2), h() - (G_GUI_OUTER_MARGIN * 2), + Direction::VERTICAL); m_list->type(Fl_Scroll::VERTICAL_ALWAYS); m_list->begin(); diff --git a/src/gui/dialogs/pluginWindow.h b/src/gui/dialogs/pluginWindow.h index babe30e..7613e4d 100644 --- a/src/gui/dialogs/pluginWindow.h +++ b/src/gui/dialogs/pluginWindow.h @@ -33,7 +33,6 @@ class geBox; class geSlider; -class geLiquidScroll; namespace giada::c::plugin { @@ -45,6 +44,7 @@ class Plugin; } namespace giada::v { +class geLiquidScroll; class gdPluginWindow : public gdWindow { public: @@ -52,7 +52,7 @@ public: void updateParameters(bool changeSlider = false); - private: +private: const c::plugin::Plugin& m_plugin; geLiquidScroll* m_list; diff --git a/src/gui/dialogs/pluginWindowGUI.cpp b/src/gui/dialogs/pluginWindowGUI.cpp index 03e4170..3b21d6a 100644 --- a/src/gui/dialogs/pluginWindowGUI.cpp +++ b/src/gui/dialogs/pluginWindowGUI.cpp @@ -26,7 +26,7 @@ #ifdef WITH_VST -#include "pluginWindowGUI.h" +#include "gui/dialogs/pluginWindowGUI.h" #include "core/const.h" #include "glue/plugin.h" #include "utils/gui.h" @@ -36,9 +36,7 @@ #import "utils/cocoa.h" // objective-c #endif -namespace giada -{ -namespace v +namespace giada::v { gdPluginWindowGUI::gdPluginWindowGUI(c::plugin::Plugin& p) #ifdef G_OS_MAC @@ -47,109 +45,70 @@ gdPluginWindowGUI::gdPluginWindowGUI(c::plugin::Plugin& p) : gdWindow(320, 200) #endif , m_plugin(p) -, m_ui(nullptr) { - show(); - -#if defined(G_OS_LINUX) || defined(G_OS_MAC) || defined(G_OS_FREEBSD) - - /* Fl_Window::show() is not guaranteed to show and draw the window on all - platforms immediately. Instead this is done in the background; particularly on - X11 it will take a few messages (client server roundtrips) to display the - window. Usually this small delay doesn't matter, but in some cases you may - want to have the window instantiated and displayed synchronously. Currently - (as of FLTK 1.3.4) this method has an effect on X11 and Mac OS. - - http://www.fltk.org/doc-1.3/classFl__Window.html#aafbec14ca8ff8abdaff77a35ebb23dd8 */ + /* Make sure to wait_for_expose() before opening the editor: the window must + be exposed and visible first. Don't fuck with multithreading! */ + copy_label(m_plugin.name.c_str()); + show(); wait_for_expose(); + openEditor(); Fl::flush(); - -#endif - - u::log::print("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n", - (void*)this, (void*)fl_xid(this)); - -#ifdef G_OS_MAC - - void* cocoaWindow = (void*)fl_xid(this); - openEditor(cocoa_getViewFromWindow(cocoaWindow)); - -#else - - openEditor((void*)fl_xid(this)); - - int pluginW = m_ui->getWidth(); - int pluginH = m_ui->getHeight(); - - resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH); - - m_plugin.setResizeCallback([this](int w, int h) { - resize(x(), y(), w, h); - }); - -#endif - -#ifdef G_OS_LINUX - Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this); -#endif - - copy_label(m_plugin.name.c_str()); } /* -------------------------------------------------------------------------- */ gdPluginWindowGUI::~gdPluginWindowGUI() { - cb_close(); -} - -/* -------------------------------------------------------------------------- */ - -void gdPluginWindowGUI::cb_close(Fl_Widget* /*v*/, void* p) { ((gdPluginWindowGUI*)p)->cb_close(); } -void gdPluginWindowGUI::cb_refresh(void* data) { ((gdPluginWindowGUI*)data)->cb_refresh(); } - -/* -------------------------------------------------------------------------- */ - -void gdPluginWindowGUI::cb_close() -{ -#ifdef G_OS_LINUX - Fl::remove_timeout(cb_refresh); -#endif + c::plugin::stopDispatchLoop(); closeEditor(); u::log::print("[gdPluginWindowGUI::__cb_close] GUI closed, this=%p\n", (void*)this); } /* -------------------------------------------------------------------------- */ -void gdPluginWindowGUI::cb_refresh() +void gdPluginWindowGUI::openEditor() { - m::pluginHost::runDispatchLoop(); - Fl::repeat_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*)this); -} - -/* -------------------------------------------------------------------------- */ + u::log::print("[gdPluginWindowGUI] Opening editor, this=%p, xid=%p\n", + this, reinterpret_cast(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(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 diff --git a/src/gui/dialogs/pluginWindowGUI.h b/src/gui/dialogs/pluginWindowGUI.h index a907f28..daf5127 100644 --- a/src/gui/dialogs/pluginWindowGUI.h +++ b/src/gui/dialogs/pluginWindowGUI.h @@ -37,16 +37,12 @@ #include #include -namespace giada -{ -namespace c -{ -namespace plugin +namespace giada::c::plugin { struct Plugin; } -} // namespace c -namespace v + +namespace giada::v { class gdPluginWindowGUI : public gdWindow { @@ -54,22 +50,15 @@ public: gdPluginWindowGUI(c::plugin::Plugin&); ~gdPluginWindowGUI(); - private: - static void cb_close(Fl_Widget* /*w*/, void* p); - static void cb_refresh(void* data); - void cb_close(); - void cb_refresh(); - - void openEditor(void* parent); + void openEditor(); void closeEditor(); - c::plugin::Plugin& m_plugin; - - juce::AudioProcessorEditor* m_ui; +private: + c::plugin::Plugin& m_plugin; + std::unique_ptr m_editor; }; -} // namespace v -} // namespace giada +} // namespace giada::v -#endif // include guard +#endif #endif // #ifdef WITH_VST diff --git a/src/gui/dialogs/sampleEditor.cpp b/src/gui/dialogs/sampleEditor.cpp index a77994d..1fcb0fc 100644 --- a/src/gui/dialogs/sampleEditor.cpp +++ b/src/gui/dialogs/sampleEditor.cpp @@ -59,19 +59,24 @@ #include #include +#ifdef G_OS_WINDOWS +#undef IN +#undef OUT +#endif + namespace giada::v { -gdSampleEditor::gdSampleEditor(ID channelId) -: gdWindow(m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY, - m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH) +gdSampleEditor::gdSampleEditor(ID channelId, m::Conf::Data& c) +: gdWindow(c.sampleEditorX, c.sampleEditorY, c.sampleEditorW, c.sampleEditorH) , m_channelId(channelId) +, m_conf(c) { end(); gePack* upperBar = createUpperBar(); waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y() + upperBar->h() + G_GUI_OUTER_MARGIN, - w() - 16, h() - 168); + w() - 16, h() - 168, m_conf.sampleEditorGridOn, m_conf.sampleEditorGridVal); gePack* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y() + waveTools->h() + G_GUI_OUTER_MARGIN, h() - waveTools->h() - upperBar->h() - 32); @@ -94,12 +99,12 @@ gdSampleEditor::gdSampleEditor(ID channelId) gdSampleEditor::~gdSampleEditor() { - m::conf::conf.sampleEditorX = x(); - m::conf::conf.sampleEditorY = y(); - m::conf::conf.sampleEditorW = w(); - m::conf::conf.sampleEditorH = h(); - m::conf::conf.sampleEditorGridVal = atoi(grid->text()); - m::conf::conf.sampleEditorGridOn = snap->value(); + m_conf.sampleEditorX = x(); + m_conf.sampleEditorY = y(); + m_conf.sampleEditorW = w(); + m_conf.sampleEditorH = h(); + m_conf.sampleEditorGridVal = atoi(grid->text()); + m_conf.sampleEditorGridOn = snap->value(); c::sampleEditor::stopPreview(); c::sampleEditor::cleanupPreview(); @@ -158,13 +163,13 @@ gePack* gdSampleEditor::createUpperBar() grid->add("64"); grid->copy_tooltip("Grid frequency"); - if (m::conf::conf.sampleEditorGridVal == 0) + if (m_conf.sampleEditorGridVal == 0) grid->value(0); else - grid->value(grid->find_item(u::string::iToString(m::conf::conf.sampleEditorGridVal).c_str())); + grid->value(grid->find_item(u::string::iToString(m_conf.sampleEditorGridVal).c_str())); grid->callback(cb_changeGrid, (void*)this); - snap->value(m::conf::conf.sampleEditorGridOn); + snap->value(m_conf.sampleEditorGridOn); snap->copy_tooltip("Snap to grid"); snap->callback(cb_enableSnap, (void*)this); diff --git a/src/gui/dialogs/sampleEditor.h b/src/gui/dialogs/sampleEditor.h index 65e78ce..23f0c01 100644 --- a/src/gui/dialogs/sampleEditor.h +++ b/src/gui/dialogs/sampleEditor.h @@ -28,6 +28,7 @@ #define GD_EDITOR_H #include "core/types.h" +#include "core/conf.h" #include "glue/sampleEditor.h" #include "window.h" @@ -41,6 +42,7 @@ namespace giada::m { class Wave; } + namespace giada::v { class geChoice; @@ -58,7 +60,7 @@ class gdSampleEditor : public gdWindow friend class geWaveform; public: - gdSampleEditor(ID channelId); + gdSampleEditor(ID channelId, m::Conf::Data&); ~gdSampleEditor(); void rebuild() override; @@ -86,7 +88,7 @@ public: geCheck* loop; geBox* info; - private: +private: gePack* createUpperBar(); gePack* createBottomBar(int x, int y, int h); geGroup* createPreviewBox(int x, int y, int h); @@ -112,6 +114,7 @@ public: ID m_channelId; c::sampleEditor::Data m_data; + m::Conf::Data& m_conf; }; } // namespace giada::v diff --git a/src/gui/dialogs/window.cpp b/src/gui/dialogs/window.cpp index 5159f57..adec425 100644 --- a/src/gui/dialogs/window.cpp +++ b/src/gui/dialogs/window.cpp @@ -27,9 +27,7 @@ #include "window.h" #include "utils/log.h" -namespace giada -{ -namespace v +namespace giada::v { void cb_window_closer(Fl_Widget* /*v*/, void* p) { @@ -168,5 +166,4 @@ gdWindow* gdWindow::getChild(int wid) return subWindows.at(j); return nullptr; } -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v diff --git a/src/gui/dialogs/window.h b/src/gui/dialogs/window.h index ac22160..c2d4648 100644 --- a/src/gui/dialogs/window.h +++ b/src/gui/dialogs/window.h @@ -30,9 +30,7 @@ #include #include -namespace giada -{ -namespace v +namespace giada::v { /* cb_window_closer Callback for closing windows. Deletes the widget (delete). */ @@ -72,12 +70,11 @@ public: gdWindow* getParent(); gdWindow* getChild(int id); - protected: +protected: std::vector subWindows; int id; gdWindow* parent; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/dispatcher.cpp b/src/gui/dispatcher.cpp index 89df7e5..d7e594e 100644 --- a/src/gui/dispatcher.cpp +++ b/src/gui/dispatcher.cpp @@ -30,31 +30,27 @@ #include "gui/dialogs/mainWindow.h" #include "gui/elems/mainWindow/keyboard/channel.h" #include "gui/elems/mainWindow/keyboard/keyboard.h" +#include "gui/ui.h" #include #include -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 signalCb_ = nullptr; +} /* -------------------------------------------------------------------------- */ -void perform_(ID channelId, int event) +void Dispatcher::perform(ID channelId, int event) const { if (event == FL_KEYDOWN) { @@ -74,103 +70,84 @@ void perform_(ID channelId, int event) /* Walk channels array, trying to match button's bound key with the event. If found, trigger the key-press/key-release function. */ -void dispatchChannels_(int event) +void Dispatcher::dispatchChannels(int event) const { - G_MainWin->keyboard->forEachChannel([=](geChannel& c) { + g_ui.mainWindow->keyboard->forEachChannel([=](geChannel& c) { if (c.handleKey(event)) - perform_(c.getData().id, event); + perform(c.getData().id, event); }); } /* -------------------------------------------------------------------------- */ -void triggerSignalCb_() +void Dispatcher::dispatchKey(int event) { - if (signalCb_ == nullptr) - return; - signalCb_(); - signalCb_ = nullptr; -} -} // namespace + assert(onEventOccured != nullptr); -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void dispatchKey(int event) -{ /* These events come from the keyboard, not from a direct interaction on the UI with the mouse/touch. */ if (event == FL_KEYDOWN) { - if (Fl::event_key() == FL_BackSpace && !backspace_) + if (Fl::event_key() == FL_BackSpace && !m_backspace) { - backspace_ = true; + m_backspace = true; c::events::rewindSequencer(Thread::MAIN); } - else if (Fl::event_key() == FL_End && !end_) + else if (Fl::event_key() == FL_End && !m_end) { - end_ = true; + m_end = true; c::events::toggleInputRecording(); } - else if (Fl::event_key() == FL_Enter && !enter_) + else if (Fl::event_key() == FL_Enter && !m_enter) { - enter_ = true; + m_enter = true; c::events::toggleActionRecording(); } - else if (Fl::event_key() == ' ' && !space_) + else if (Fl::event_key() == ' ' && !m_space) { - space_ = true; + m_space = true; c::events::toggleSequencer(Thread::MAIN); } - else if (Fl::event_key() == FL_Escape && !esc_) + else if (Fl::event_key() == FL_Escape && !m_esc) { - esc_ = true; + m_esc = true; m::init::closeMainWindow(); } - else if (!key_) + else if (!m_key) { - key_ = true; - triggerSignalCb_(); - dispatchChannels_(event); + m_key = true; + onEventOccured(); + dispatchChannels(event); } } else if (event == FL_KEYUP) { if (Fl::event_key() == FL_BackSpace) - backspace_ = false; + m_backspace = false; else if (Fl::event_key() == FL_End) - end_ = false; + m_end = false; else if (Fl::event_key() == ' ') - space_ = false; + m_space = false; else if (Fl::event_key() == FL_Enter) - enter_ = false; + m_enter = false; else if (Fl::event_key() == FL_Escape) - esc_ = false; + m_esc = false; else { - key_ = false; - dispatchChannels_(event); + m_key = false; + dispatchChannels(event); } } } /* -------------------------------------------------------------------------- */ -void dispatchTouch(const geChannel& gch, bool status) +void Dispatcher::dispatchTouch(const geChannel& gch, bool status) { - triggerSignalCb_(); - perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP); -} + assert(onEventOccured != nullptr); -/* -------------------------------------------------------------------------- */ - -void setSignalCallback(std::function 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 diff --git a/src/gui/dispatcher.h b/src/gui/dispatcher.h index 2d4d7e1..3521dc1 100644 --- a/src/gui/dispatcher.h +++ b/src/gui/dispatcher.h @@ -27,29 +27,48 @@ #ifndef G_V_DISPATCHER_H #define G_V_DISPATCHER_H +#include "core/types.h" #include -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 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 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 diff --git a/src/gui/elems/actionEditor/baseAction.h b/src/gui/elems/actionEditor/baseAction.h index 664a7aa..0fb7bb7 100644 --- a/src/gui/elems/actionEditor/baseAction.h +++ b/src/gui/elems/actionEditor/baseAction.h @@ -27,8 +27,8 @@ #ifndef GE_BASE_ACTION_H #define GE_BASE_ACTION_H -#include "core/recorder.h" #include "core/types.h" +#include "src/core/actions/actions.h" #include namespace giada::m @@ -67,7 +67,7 @@ public: m::Action a1; m::Action a2; - protected: +protected: bool m_resizable; }; } // namespace giada::v diff --git a/src/gui/elems/actionEditor/baseActionEditor.cpp b/src/gui/elems/actionEditor/baseActionEditor.cpp index dc79fe5..c8ffdba 100644 --- a/src/gui/elems/actionEditor/baseActionEditor.cpp +++ b/src/gui/elems/actionEditor/baseActionEditor.cpp @@ -27,15 +27,13 @@ #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 #include -namespace giada -{ -namespace v +namespace giada::v { geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor* base) @@ -76,17 +74,17 @@ void geBaseActionEditor::baseDraw(bool clear) const /* Draw grid, beats and bars. A grid set to 1 has a cell size == beat, so painting it is useless. */ - if (m_base->gridTool->getValue() > 1) + if (m_base->gridTool.getValue() > 1) { fl_color(G_COLOR_GREY_3); - drawVerticals(m_base->gridTool->getCellSize()); + drawVerticals(m_base->gridTool.getCellSize()); } fl_color(G_COLOR_GREY_4); - drawVerticals(m::clock::getFramesInBeat()); + drawVerticals(m_data->framesInBeat); fl_color(G_COLOR_LIGHT_1); - drawVerticals(m::clock::getFramesInBar()); + drawVerticals(m_data->framesInBar); /* Cover unused area. Avoid drawing cover if width == 0 (i.e. beats are 32). */ @@ -101,7 +99,7 @@ void geBaseActionEditor::drawVerticals(int steps) const { /* Start drawing from steps, not from 0. The zero-th element is always graphically useless. */ - for (Frame i = steps; i < m::clock::getFramesInLoop(); i += steps) + for (Frame i = steps; i < m_data->framesInLoop; i += steps) { Pixel p = m_base->frameToPixel(i) + x(); fl_line(p, y() + 1, p, y() + h() - 2); @@ -181,5 +179,4 @@ int geBaseActionEditor::release() m_action = nullptr; return ret; } -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/baseActionEditor.h b/src/gui/elems/actionEditor/baseActionEditor.h index 028a88e..f973375 100644 --- a/src/gui/elems/actionEditor/baseActionEditor.h +++ b/src/gui/elems/actionEditor/baseActionEditor.h @@ -35,6 +35,7 @@ namespace giada::c::actionEditor { struct Data; } + namespace giada::v { class gdBaseActionEditor; @@ -58,7 +59,7 @@ public: geBaseAction* getActionAtCursor() const; - protected: +protected: geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*); c::actionEditor::Data* m_data; @@ -84,7 +85,7 @@ public: virtual void onResizeAction() = 0; virtual void onRefreshAction() = 0; - private: +private: /* drawVerticals Draws generic vertical lines (beats, bars, grid lines...). */ diff --git a/src/gui/elems/actionEditor/envelopeEditor.cpp b/src/gui/elems/actionEditor/envelopeEditor.cpp index b8e6d78..9fa0d28 100644 --- a/src/gui/elems/actionEditor/envelopeEditor.cpp +++ b/src/gui/elems/actionEditor/envelopeEditor.cpp @@ -25,39 +25,30 @@ * -------------------------------------------------------------------------- */ #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 #include #include -namespace giada -{ -namespace v +namespace giada::v { geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b) -: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b) +: geBaseActionEditor(x, y, 200, 40, b) { copy_label(l); } /* -------------------------------------------------------------------------- */ -geEnvelopeEditor::~geEnvelopeEditor() -{ - m::conf::conf.envelopeEditorH = h(); -} - -/* -------------------------------------------------------------------------- */ - void geEnvelopeEditor::draw() { baseDraw(); @@ -214,5 +205,4 @@ void geEnvelopeEditor::onRefreshAction() m_base->rebuild(); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/envelopeEditor.h b/src/gui/elems/actionEditor/envelopeEditor.h index f7663c7..49675fa 100644 --- a/src/gui/elems/actionEditor/envelopeEditor.h +++ b/src/gui/elems/actionEditor/envelopeEditor.h @@ -29,26 +29,17 @@ #include "baseActionEditor.h" -namespace giada +namespace giada::v { -namespace m -{ -class SampleChannel; -} -namespace v -{ -class geEnvelopePoint; class geEnvelopeEditor : public geBaseActionEditor { public: geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*); - ~geEnvelopeEditor(); - void draw() override; void rebuild(c::actionEditor::Data& d) override; - private: +private: void onAddAction() override; void onDeleteAction() override; void onMoveAction() override; @@ -62,7 +53,6 @@ public: bool isFirstPoint() const; bool isLastPoint() const; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/actionEditor/envelopePoint.cpp b/src/gui/elems/actionEditor/envelopePoint.cpp index 4215d39..e362b55 100644 --- a/src/gui/elems/actionEditor/envelopePoint.cpp +++ b/src/gui/elems/actionEditor/envelopePoint.cpp @@ -28,9 +28,7 @@ #include "core/const.h" #include -namespace giada -{ -namespace v +namespace giada::v { geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, m::Action a) : geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, {}) @@ -43,5 +41,4 @@ void geEnvelopePoint::draw() { fl_rectf(x(), y(), w(), h(), hovered ? G_COLOR_LIGHT_2 : G_COLOR_LIGHT_1); } -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/envelopePoint.h b/src/gui/elems/actionEditor/envelopePoint.h index 8f4d4f4..a83b309 100644 --- a/src/gui/elems/actionEditor/envelopePoint.h +++ b/src/gui/elems/actionEditor/envelopePoint.h @@ -28,11 +28,9 @@ #define GE_ENVELOPE_POINT_H #include "baseAction.h" -#include "core/recorder.h" +#include "src/core/actions/actions.h" -namespace giada -{ -namespace v +namespace giada::v { class geEnvelopePoint : public geBaseAction { @@ -43,7 +41,6 @@ public: void draw() override; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/actionEditor/gridTool.cpp b/src/gui/elems/actionEditor/gridTool.cpp index 622d46d..68f5492 100644 --- a/src/gui/elems/actionEditor/gridTool.cpp +++ b/src/gui/elems/actionEditor/gridTool.cpp @@ -24,8 +24,7 @@ * * --------------------------------------------------------------------------- */ -#include "gridTool.h" -#include "core/clock.h" +#include "gui/elems/actionEditor/gridTool.h" #include "core/conf.h" #include "gui/elems/basics/check.h" #include "gui/elems/basics/choice.h" @@ -34,8 +33,10 @@ namespace giada::v { -geGridTool::geGridTool(Pixel x, Pixel y) +geGridTool::geGridTool(Pixel x, Pixel y, m::Conf::Data& c, Frame framesInBeat) : Fl_Group(x, y, 80, 20) +, m_conf(c) +, m_framesInBeat(framesInBeat) { gridType = new geChoice(x, y, 40, 20); gridType->add("1"); @@ -51,8 +52,8 @@ geGridTool::geGridTool(Pixel x, Pixel y) active = new geCheck(gridType->x() + gridType->w() + 4, y, 20, 20); - gridType->value(m::conf::conf.actionEditorGridVal); - active->value(m::conf::conf.actionEditorGridOn); + gridType->value(m_conf.actionEditorGridVal); + active->value(m_conf.actionEditorGridOn); end(); @@ -64,8 +65,8 @@ geGridTool::geGridTool(Pixel x, Pixel y) geGridTool::~geGridTool() { - m::conf::conf.actionEditorGridVal = gridType->value(); - m::conf::conf.actionEditorGridOn = active->value(); + m_conf.actionEditorGridVal = gridType->value(); + m_conf.actionEditorGridOn = active->value(); } /* -------------------------------------------------------------------------- */ @@ -125,6 +126,6 @@ Frame geGridTool::getSnapFrame(Frame v) const Frame geGridTool::getCellSize() const { - return m::clock::getFramesInBeat() / getValue(); + return m_framesInBeat / getValue(); } } // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/gridTool.h b/src/gui/elems/actionEditor/gridTool.h index 04b4cf1..a8cf2d3 100644 --- a/src/gui/elems/actionEditor/gridTool.h +++ b/src/gui/elems/actionEditor/gridTool.h @@ -27,6 +27,7 @@ #ifndef GE_GRID_TOOL_H #define GE_GRID_TOOL_H +#include "core/conf.h" #include "core/types.h" #include @@ -38,7 +39,7 @@ class geChoice; class geGridTool : public Fl_Group { public: - geGridTool(Pixel x, Pixel y); + geGridTool(Pixel x, Pixel y, m::Conf::Data&, Frame framesInBeat); ~geGridTool(); int getValue() const; @@ -51,7 +52,10 @@ public: Frame getCellSize() const; - private: +private: + m::Conf::Data& m_conf; + Frame m_framesInBeat; + geChoice* gridType; geCheck* active; diff --git a/src/gui/elems/actionEditor/noteEditor.cpp b/src/gui/elems/actionEditor/noteEditor.cpp deleted file mode 100644 index 42936fe..0000000 --- a/src/gui/elems/actionEditor/noteEditor.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#include "noteEditor.h" -#include "core/conf.h" -#include "core/const.h" -#include "gui/dialogs/actionEditor/midiActionEditor.h" -#include "pianoRoll.h" -#include - -namespace giada -{ -namespace v -{ -geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base) -: geScroll(x, y, 200, 422) -, m_base(base) -{ - end(); - - type(Fl_Scroll::VERTICAL_ALWAYS); - size(m_base->fullWidth, m::conf::conf.pianoRollH); - - pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, base); - add(pianoRoll); -} - -/* -------------------------------------------------------------------------- */ - -geNoteEditor::~geNoteEditor() -{ - m::conf::conf.pianoRollH = h(); - m::conf::conf.pianoRollY = pianoRoll->y(); -} - -/* -------------------------------------------------------------------------- */ - -void geNoteEditor::scroll() -{ - Pixel ey = Fl::event_y() - pianoRoll->pick; - - Pixel y1 = y(); - Pixel y2 = (y() + h()) - pianoRoll->h(); - - if (ey > y1) - ey = y1; - else if (ey < y2) - ey = y2; - - pianoRoll->position(x(), ey); - - redraw(); -} - -/* -------------------------------------------------------------------------- */ - -void geNoteEditor::rebuild(c::actionEditor::Data& d) -{ - size(m_base->fullWidth, h()); - pianoRoll->rebuild(d); -} -} // namespace v -} // namespace giada \ No newline at end of file diff --git a/src/gui/elems/actionEditor/noteEditor.h b/src/gui/elems/actionEditor/noteEditor.h deleted file mode 100644 index 9c737d5..0000000 --- a/src/gui/elems/actionEditor/noteEditor.h +++ /dev/null @@ -1,57 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#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 diff --git a/src/gui/elems/actionEditor/pianoItem.cpp b/src/gui/elems/actionEditor/pianoItem.cpp index e44f9a6..2b46bca 100644 --- a/src/gui/elems/actionEditor/pianoItem.cpp +++ b/src/gui/elems/actionEditor/pianoItem.cpp @@ -25,9 +25,9 @@ * -------------------------------------------------------------------------- */ #include "pianoItem.h" -#include "core/action.h" #include "core/const.h" #include "core/midiEvent.h" +#include "src/core/actions/action.h" #include "utils/math.h" #include diff --git a/src/gui/elems/actionEditor/pianoItem.h b/src/gui/elems/actionEditor/pianoItem.h index 00c25f1..ab448be 100644 --- a/src/gui/elems/actionEditor/pianoItem.h +++ b/src/gui/elems/actionEditor/pianoItem.h @@ -29,16 +29,13 @@ #include "baseAction.h" -namespace giada -{ -namespace m +namespace giada::m { struct Action; } -namespace v -{ -class gdActionEditor; +namespace giada::v +{ class gePianoItem : public geBaseAction { public: @@ -48,13 +45,12 @@ public: bool isResizable() const; - private: +private: bool m_ringLoop; bool m_orphaned; Pixel calcVelocityH() const; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/actionEditor/pianoRoll.cpp b/src/gui/elems/actionEditor/pianoRoll.cpp index 78c5ae3..ba89dd7 100644 --- a/src/gui/elems/actionEditor/pianoRoll.cpp +++ b/src/gui/elems/actionEditor/pianoRoll.cpp @@ -25,39 +25,34 @@ * -------------------------------------------------------------------------- */ #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 #include -namespace giada +namespace giada::v { -namespace v +gePianoRoll::gePianoRoll(Pixel X, Pixel Y, gdBaseActionEditor* b) +: geBaseActionEditor(X, Y, 200, CELL_H * MAX_KEYS, b) +, m_pick(0) { -gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, gdBaseActionEditor* b) -: geBaseActionEditor(X, Y, W, 40, b) -, pick(0) -{ - position(x(), m::conf::conf.pianoRollY == -1 ? y() - (h() / 2) : m::conf::conf.pianoRollY); } /* -------------------------------------------------------------------------- */ -void gePianoRoll::drawSurface1() +void gePianoRoll::drawSurfaceY() { - surface1 = fl_create_offscreen(CELL_W, h()); - fl_begin_offscreen(surface1); + surfaceY = fl_create_offscreen(CELL_W, h()); + fl_begin_offscreen(surfaceY); /* Warning: only w() and h() come from this widget, x and y coordinates are absolute, since we are writing in a memory chunk. */ @@ -71,7 +66,6 @@ void gePianoRoll::drawSurface1() for (int i = 1; i <= MAX_KEYS + 1; i++) { - /* print key note label. C C# D D# E F F# G G# A A# B */ std::string note = u::string::iToString(octave); @@ -139,11 +133,11 @@ void gePianoRoll::drawSurface1() /* -------------------------------------------------------------------------- */ -void gePianoRoll::drawSurface2() +void gePianoRoll::drawSurfaceX() { - surface2 = fl_create_offscreen(CELL_W, h()); + surfaceX = fl_create_offscreen(CELL_W, h()); - fl_begin_offscreen(surface2); + fl_begin_offscreen(surfaceX); fl_rectf(0, 0, CELL_W, h(), G_COLOR_GREY_1); fl_color(G_COLOR_GREY_3); fl_line_style(FL_DASH, 0, nullptr); @@ -152,11 +146,11 @@ void gePianoRoll::drawSurface2() { switch (i % KEYS) { - case (int)Notes::G: - case (int)Notes::E: - case (int)Notes::D: - case (int)Notes::B: - case (int)Notes::A: + case static_cast(Notes::G): + case static_cast(Notes::E): + case static_cast(Notes::D): + case static_cast(Notes::B): + case static_cast(Notes::A): fl_rectf(0, i * CELL_H, CELL_W, CELL_H, G_COLOR_GREY_2); break; } @@ -175,15 +169,15 @@ void gePianoRoll::drawSurface2() void gePianoRoll::draw() { - fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0); + fl_copy_offscreen(x(), y(), CELL_W, h(), surfaceY, 0, 0); // TODO - is this APPLE thing still useful? #if defined(__APPLE__) for (Pixel i = 36; i < m_base->fullWidth; i += 36) /// TODO: i < m_base->loopWidth is faster - fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 1, 0); + fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 1, 0); #else for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W) - fl_copy_offscreen(x() + i, y(), CELL_W, h(), surface2, 0, 0); + fl_copy_offscreen(x() + i, y(), CELL_W, h(), surfaceX, 0, 0); #endif baseDraw(false); @@ -194,16 +188,28 @@ void gePianoRoll::draw() int gePianoRoll::handle(int e) { - if (e == FL_PUSH && Fl::event_button3()) - { - pick = Fl::event_y() - y(); + if (!Fl::event_button3()) return geBaseActionEditor::handle(e); + + switch (e) + { + case FL_PUSH: + { + m_pick = Fl::event_y() - y(); + break; } - if (e == FL_DRAG && Fl::event_button3()) + case FL_DRAG: { - static_cast(parent())->scroll(); - return 1; + const int pos = Fl::event_y() - m_pick; + const int min = parent()->y(); + const int max = -h() + (parent()->h() + parent()->y()); + position(x(), std::clamp(pos, max, min)); + break; + } + default: + break; } + return geBaseActionEditor::handle(e); } @@ -377,10 +383,9 @@ void gePianoRoll::rebuild(c::actionEditor::Data& d) add(new gePianoItem(px, py, pw, ph, a1, a2)); } - drawSurface1(); - drawSurface2(); + drawSurfaceY(); + drawSurfaceX(); redraw(); } -} // namespace v -} // namespace giada +} // namespace giada::v diff --git a/src/gui/elems/actionEditor/pianoRoll.h b/src/gui/elems/actionEditor/pianoRoll.h index 470359b..db44cbf 100644 --- a/src/gui/elems/actionEditor/pianoRoll.h +++ b/src/gui/elems/actionEditor/pianoRoll.h @@ -32,9 +32,9 @@ namespace giada::m { -class MidiChannel; -class Action; -} // namespace giada::m +struct Action; +} + namespace giada::v { class gePianoRoll : public geBaseActionEditor @@ -46,16 +46,14 @@ public: static const Pixel CELL_H = 20; static const Pixel CELL_W = 40; - gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*); + gePianoRoll(Pixel x, Pixel y, gdBaseActionEditor* b); void draw() override; int handle(int e) override; void rebuild(c::actionEditor::Data& d) override; - Pixel pick; - - private: +private: enum class Notes { G = 1, @@ -72,9 +70,6 @@ public: GS = 0 }; - Fl_Offscreen surface1; // notes, no repeat - Fl_Offscreen surface2; // lines, x-repeat - void onAddAction() override; void onDeleteAction() override; void onMoveAction() override; @@ -89,13 +84,22 @@ public: containing the rest of the piano roll. The latter will then be tiled during the ::draw() call. */ - void drawSurface1(); - void drawSurface2(); + void drawSurfaceY(); + void drawSurfaceX(); Pixel snapToY(Pixel p) const; int yToNote(Pixel y) const; Pixel noteToY(int n) const; Pixel getPianoItemW(Pixel x, const m::Action& a1, const m::Action& a2) const; + + Fl_Offscreen surfaceY; // vertical notes, no x-repeat + Fl_Offscreen surfaceX; // lines, x-repeat + + /* m_pick + Y-coordinate of the click event when the user clicks on an empty area of the + piano roll. Used for right mouse button scrolling. */ + + Pixel m_pick; }; } // namespace giada::v diff --git a/src/gui/elems/actionEditor/sampleAction.cpp b/src/gui/elems/actionEditor/sampleAction.cpp index 38f71d0..211ad7a 100644 --- a/src/gui/elems/actionEditor/sampleAction.cpp +++ b/src/gui/elems/actionEditor/sampleAction.cpp @@ -25,8 +25,8 @@ * -------------------------------------------------------------------------- */ #include "sampleAction.h" -#include "core/action.h" #include "core/const.h" +#include "src/core/actions/action.h" #include namespace giada diff --git a/src/gui/elems/actionEditor/sampleAction.h b/src/gui/elems/actionEditor/sampleAction.h index 0ec2eaf..b606652 100644 --- a/src/gui/elems/actionEditor/sampleAction.h +++ b/src/gui/elems/actionEditor/sampleAction.h @@ -28,15 +28,9 @@ #define GE_SAMPLE_ACTION_H #include "baseAction.h" -#include "core/recorder.h" +#include "src/core/actions/actions.h" -namespace giada -{ -namespace m -{ -class SampleChannel; -} -namespace v +namespace giada::v { class geSampleAction : public geBaseAction { @@ -46,10 +40,9 @@ public: void draw() override; - private: +private: bool m_singlePress; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/actionEditor/sampleActionEditor.cpp b/src/gui/elems/actionEditor/sampleActionEditor.cpp index c290abf..89fddb4 100644 --- a/src/gui/elems/actionEditor/sampleActionEditor.cpp +++ b/src/gui/elems/actionEditor/sampleActionEditor.cpp @@ -25,33 +25,24 @@ * -------------------------------------------------------------------------- */ #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 #include #include -namespace giada -{ -namespace v +namespace giada::v { geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b) -: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b) -{ -} - -/* -------------------------------------------------------------------------- */ - -geSampleActionEditor::~geSampleActionEditor() +: geBaseActionEditor(x, y, 200, 40, b) { - m::conf::conf.sampleActionEditorH = h(); } /* -------------------------------------------------------------------------- */ @@ -101,6 +92,12 @@ void geSampleActionEditor::rebuild(c::actionEditor::Data& d) void geSampleActionEditor::draw() { + /* Force height to match its parent's height. This widget belongs to a + geScroll container (see geSplitScroll class in baseActionEditor.h) but + there's nothing to scroll here actually. */ + + size(w(), parent()->h()); + /* Draw basic boundaries (+ beat bars) and hide the unused area. Then draw children (the actions). */ @@ -123,7 +120,7 @@ void geSampleActionEditor::draw() void geSampleActionEditor::onAddAction() { Frame f = m_base->pixelToFrame(Fl::event_x() - x()); - c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f); + c::actionEditor::recordSampleAction(m_data->channelId, static_cast(m_base)->getActionType(), f); } /* -------------------------------------------------------------------------- */ @@ -210,5 +207,4 @@ bool geSampleActionEditor::isNoteOffSinglePress(const m::Action& a) return m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS && a.event.getStatus() == m::MidiEvent::NOTE_OFF; } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/sampleActionEditor.h b/src/gui/elems/actionEditor/sampleActionEditor.h index c1bab27..fd8a830 100644 --- a/src/gui/elems/actionEditor/sampleActionEditor.h +++ b/src/gui/elems/actionEditor/sampleActionEditor.h @@ -29,28 +29,24 @@ #include "baseActionEditor.h" -namespace giada +namespace giada::m { -namespace m -{ -class SampleChannel; struct Action; -} // namespace m -namespace v +} + +namespace giada::v { class geSampleAction; - class geSampleActionEditor : public geBaseActionEditor { public: geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*); - ~geSampleActionEditor(); void draw() override; void rebuild(c::actionEditor::Data& d) override; - private: +private: void onAddAction() override; void onDeleteAction() override; void onMoveAction() override; @@ -59,7 +55,6 @@ public: bool isNoteOffSinglePress(const m::Action& a); }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/actionEditor/splitScroll.cpp b/src/gui/elems/actionEditor/splitScroll.cpp new file mode 100644 index 0000000..bcece2a --- /dev/null +++ b/src/gui/elems/actionEditor/splitScroll.cpp @@ -0,0 +1,105 @@ +/* ----------------------------------------------------------------------------- +* +* Giada - Your Hardcore Loopmachine +* +* ------------------------------------------------------------------------------ +* +* Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual +* +* This file is part of Giada - Your Hardcore Loopmachine. +* +* Giada - Your Hardcore Loopmachine is free software: you can +* redistribute it and/or modify it under the terms of the GNU General +* Public License as published by the Free Software Foundation, either +* version 3 of the License, or (at your option) any later version. +* +* Giada - Your Hardcore Loopmachine is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Giada - Your Hardcore Loopmachine. If not, see +* . +* +* --------------------------------------------------------------------------- */ + +#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 geSplitScroll::getBoundsNoScrollbar() const +{ + return { + x(), y(), + w() - m_a.scrollbar.w() - G_GUI_OUTER_MARGIN, + h() - m_b.hscrollbar.h() - G_GUI_OUTER_MARGIN}; +} + +/* -------------------------------------------------------------------------- */ + +void geSplitScroll::setScrollX(Pixel p) +{ + p = std::max(0, p); + m_a.scroll_to(p, m_a.yposition()); + m_b.scroll_to(p, m_b.yposition()); +} + +void geSplitScroll::setScrollY(Pixel p) +{ + m_a.scroll_to(m_a.xposition(), p); +} +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/splitScroll.h b/src/gui/elems/actionEditor/splitScroll.h new file mode 100644 index 0000000..6bfd29c --- /dev/null +++ b/src/gui/elems/actionEditor/splitScroll.h @@ -0,0 +1,59 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 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 diff --git a/src/gui/elems/actionEditor/velocityEditor.cpp b/src/gui/elems/actionEditor/velocityEditor.cpp index fe01bd4..dd6ddc1 100644 --- a/src/gui/elems/actionEditor/velocityEditor.cpp +++ b/src/gui/elems/actionEditor/velocityEditor.cpp @@ -25,33 +25,23 @@ * -------------------------------------------------------------------------- */ #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 #include #include -namespace giada -{ -namespace v +namespace giada::v { geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b) -: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b) -{ -} - -/* -------------------------------------------------------------------------- */ - -geVelocityEditor::~geVelocityEditor() +: geBaseActionEditor(x, y, 200, 40, b) { - m::conf::conf.velocityEditorH = h(); } /* -------------------------------------------------------------------------- */ @@ -60,25 +50,28 @@ void geVelocityEditor::draw() { baseDraw(); + if (h() < geEnvelopePoint::SIDE) + return; + /* Print label. */ fl_color(G_COLOR_GREY_4); fl_font(FL_HELVETICA, G_GUI_FONT_SIZE_BASE); - fl_draw("Velocity", x() + 4, y(), w(), h(), (Fl_Align)(FL_ALIGN_LEFT)); + fl_draw("Velocity", x() + 4, y(), w(), h(), FL_ALIGN_LEFT); if (children() == 0) return; - Pixel side = geEnvelopePoint::SIDE / 2; + const Pixel side = geEnvelopePoint::SIDE / 2; for (int i = 0; i < children(); i++) { geEnvelopePoint* p = static_cast(child(i)); if (m_action == nullptr) p->position(p->x(), valueToY(p->a1.event.getVelocity())); - Pixel x1 = p->x() + side; - Pixel y1 = p->y(); - Pixel y2 = y() + h(); + const Pixel x1 = p->x() + side; + const Pixel y1 = p->y(); + const Pixel y2 = y() + h(); fl_line(x1, y1, x1, y2); } @@ -152,5 +145,4 @@ void geVelocityEditor::onRefreshAction() m_base->rebuild(); // Rebuild pianoRoll as well } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/actionEditor/velocityEditor.h b/src/gui/elems/actionEditor/velocityEditor.h index 7f83a87..cb15f90 100644 --- a/src/gui/elems/actionEditor/velocityEditor.h +++ b/src/gui/elems/actionEditor/velocityEditor.h @@ -29,27 +29,19 @@ #include "baseActionEditor.h" -namespace giada -{ -namespace m -{ -class MidiChannel; -} -namespace v +namespace giada::v { class geEnvelopePoint; - class geVelocityEditor : public geBaseActionEditor { public: geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*); - ~geVelocityEditor(); void draw() override; void rebuild(c::actionEditor::Data& d) override; - private: +private: void onMoveAction() override; void onRefreshAction() override; void onAddAction() override{}; @@ -59,7 +51,6 @@ public: Pixel valueToY(int v) const; int yToValue(Pixel y) const; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/basics/choice.cpp b/src/gui/elems/basics/choice.cpp index a9c4182..acb639b 100644 --- a/src/gui/elems/basics/choice.cpp +++ b/src/gui/elems/basics/choice.cpp @@ -86,10 +86,12 @@ ID geChoice::getSelectedId() const void geChoice::addItem(const std::string& label, ID id) { - assert(id >= 0); - Fl_Choice::add(label.c_str(), 0, cb_onChange, static_cast(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); } /* -------------------------------------------------------------------------- */ diff --git a/src/gui/elems/basics/choice.h b/src/gui/elems/basics/choice.h index ea97974..be7903c 100644 --- a/src/gui/elems/basics/choice.h +++ b/src/gui/elems/basics/choice.h @@ -43,7 +43,11 @@ public: ID getSelectedId() const; - void addItem(const std::string& label, ID id); + /* addItem + Adds a new item with a certain ID. Pass id = -1 to auto-increment it. */ + + void addItem(const std::string& label, ID id = -1); + void showItem(const std::string& label); void showItem(ID id); void clear(); diff --git a/src/gui/elems/basics/group.h b/src/gui/elems/basics/group.h index 0e59445..55d884c 100644 --- a/src/gui/elems/basics/group.h +++ b/src/gui/elems/basics/group.h @@ -38,7 +38,7 @@ namespace giada namespace v { /* geGroup -A group that resizes itself accoring to the content. */ +A group that resizes itself according to the content. */ class geGroup : public Fl_Group { @@ -52,14 +52,18 @@ public: /* add Adds a Fl_Widget 'w' to this group. Coordinates are relative to the group, - so origin starts at (0, 0). */ + so origin starts at (0, 0). As with any other FLTK group, the widget becomes + owned by this group: If you add static or automatic (local) variables, then + it is your responsibility to remove (or delete) all such static or automatic + child widgets before destroying the group - otherwise the child widgets' + destructors would be called twice! */ void add(Fl_Widget* w); Fl_Widget* getChild(std::size_t i); Fl_Widget* getLastChild(); - private: +private: /* m_widgets The internal Fl_Scroll::array_ is unreliable when inspected with the child() method. Let's keep track of widgets that belong to this group manually. */ diff --git a/src/gui/elems/basics/liquidScroll.cpp b/src/gui/elems/basics/liquidScroll.cpp index 9223e1e..a639aeb 100644 --- a/src/gui/elems/basics/liquidScroll.cpp +++ b/src/gui/elems/basics/liquidScroll.cpp @@ -33,8 +33,11 @@ #include "boxtypes.h" #include "core/const.h" -geLiquidScroll::geLiquidScroll(int x, int y, int w, int h) -: geScroll(x, y, w, h, Fl_Scroll::VERTICAL_ALWAYS) +namespace giada::v +{ +geLiquidScroll::geLiquidScroll(int x, int y, int w, int h, Direction d) +: geScroll(x, y, w, h, d == Direction::VERTICAL ? Fl_Scroll::VERTICAL_ALWAYS : Fl_Scroll::HORIZONTAL_ALWAYS) +, m_direction(d) { } @@ -42,12 +45,16 @@ geLiquidScroll::geLiquidScroll(int x, int y, int w, int h) void geLiquidScroll::resize(int X, int Y, int W, int H) { - int nc = children() - 2; // skip hscrollbar and vscrollbar - for (int t = 0; t < nc; t++) - { // tell children to resize to our new width + const int nc = children() - 2; // skip hscrollbar and vscrollbar + for (int t = 0; t < nc; t++) // tell children to resize to our new width + { Fl_Widget* c = child(t); - c->resize(c->x(), c->y(), W - 24, c->h()); // W-24: leave room for scrollbar + if (m_direction == Direction::VERTICAL) + c->resize(c->x(), c->y(), W - 24, c->h()); // -24: leave room for scrollbar + else + c->resize(c->x(), c->y(), c->w(), H - 24); // -24: leave room for scrollbar } init_sizes(); // tell scroll children changed in size Fl_Scroll::resize(X, Y, W, H); } +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/basics/liquidScroll.h b/src/gui/elems/basics/liquidScroll.h index 530249f..04b5983 100644 --- a/src/gui/elems/basics/liquidScroll.h +++ b/src/gui/elems/basics/liquidScroll.h @@ -33,12 +33,15 @@ #define GE_LIQUID_SCROLL_H #include "core/const.h" +#include "gui/types.h" #include "scroll.h" +namespace giada::v +{ class geLiquidScroll : public geScroll { public: - geLiquidScroll(int x, int y, int w, int h); + geLiquidScroll(int x, int y, int w, int h, Direction d); void resize(int x, int y, int w, int h) override; @@ -61,6 +64,10 @@ public: return wg; } + +private: + Direction m_direction; }; +} // namespace giada::v #endif diff --git a/src/gui/elems/basics/pack.cpp b/src/gui/elems/basics/pack.cpp index eaf50c4..0b6982d 100644 --- a/src/gui/elems/basics/pack.cpp +++ b/src/gui/elems/basics/pack.cpp @@ -30,9 +30,7 @@ #include "pack.h" #include "core/const.h" -namespace giada -{ -namespace v +namespace giada::v { gePack::gePack(int x, int y, Direction d, int gutter) : geGroup(x, y) @@ -55,5 +53,4 @@ void gePack::add(Fl_Widget* widget) geGroup::add(widget); } -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/basics/pack.h b/src/gui/elems/basics/pack.h index 13a6080..723f99c 100644 --- a/src/gui/elems/basics/pack.h +++ b/src/gui/elems/basics/pack.h @@ -32,17 +32,10 @@ #include "core/const.h" #include "gui/elems/basics/group.h" +#include "gui/types.h" -namespace giada +namespace giada::v { -namespace v -{ -enum class Direction -{ - HORIZONTAL, - VERTICAL -}; - /* gePack A stack of widgets that resize itself according to its content. */ @@ -53,15 +46,18 @@ public: /* add Adds a Fl_Widget 'w' to this pack. Coordinates are relative to the group, - so origin starts at (0, 0). */ + so origin starts at (0, 0). As with any other FLTK group, the widget becomes + owned by this group: If you add static or automatic (local) variables, then + it is your responsibility to remove (or delete) all such static or automatic + child widgets before destroying the group - otherwise the child widgets' + destructors would be called twice! */ void add(Fl_Widget* w); - private: +private: Direction m_direction; int m_gutter; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/basics/resizerBar.cpp b/src/gui/elems/basics/resizerBar.cpp index d3c1546..aa8bc4b 100644 --- a/src/gui/elems/basics/resizerBar.cpp +++ b/src/gui/elems/basics/resizerBar.cpp @@ -2,22 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * geResizerBar - * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from - * FLTK dev team. http://seriss.com/people/erco/fltk/ - * - * Shows a resize cursor when hovered over. - * Assumes: - * - Parent is an Fl_Scroll - * - All children of Fl_Scroll are m_vertically arranged - * - The widget above us has a bottom edge touching our top edge - * ie. (w->y()+w->h() == this->y()) - * - * When this widget is dragged: - * - The widget above us (with a common edge) will be /resized/ - * m_vertically - * - All children below us will be /moved/ m_vertically - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual @@ -43,30 +27,21 @@ #include "resizerBar.h" #include "core/const.h" #include -#include +#include #include +#include +#include -geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type, Fl_Widget* target) +namespace giada::v +{ +geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, Direction dir, Mode mode) : Fl_Box(X, Y, W, H) -, m_type(type) +, m_direction(dir) +, m_mode(mode) , m_minSize(minSize) , m_lastPos(0) -, m_initialPos(0) , m_hover(false) -, m_target(target) { - if (m_type == VERTICAL) - { - m_origSize = H; - labelsize(H); - } - else - { - m_origSize = W; - labelsize(W); - } - align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE); - labelfont(FL_COURIER); visible_focus(0); } @@ -74,65 +49,113 @@ geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type, F void geResizerBar::handleDrag(int diff) { - Fl_Scroll* group = static_cast(parent()); + m_mode == Mode::MOVE ? move(diff) : resize(diff); + + Fl_Group* group = static_cast(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 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 geResizerBar::findWidgets(std::function f, int howmany) const +{ + std::vector out; + Fl_Group* group = static_cast(parent()); for (int t = 0; t < group->children(); t++) { Fl_Widget* wd = group->child(t); - if (m_type == VERTICAL) - { - if (wd->y() >= bot) // found widget below us? - wd->resize(wd->x(), wd->y() + diff, wd->w(), wd->h()); // change position - } - else - { - if (wd->x() >= bot) - wd->resize(wd->x() + diff, wd->y(), wd->w(), wd->h()); - } + if (!f(*wd)) + continue; + out.push_back(wd); + if (howmany != -1 && out.size() == (size_t)howmany) + break; } - // Change our position last + /* Make sure it finds the exact number of widgets requested, in case + howmany != -1. */ - if (m_type == VERTICAL) - resize(x(), y() + diff, w(), h()); - else - resize(x() + diff, y(), w(), h()); + assert(howmany == -1 || (howmany != -1 && out.size() == (size_t)howmany)); - group->init_sizes(); - group->redraw(); + return out; } /* -------------------------------------------------------------------------- */ @@ -148,7 +171,7 @@ void geResizerBar::draw() int geResizerBar::handle(int e) { int ret = 0; - int currentPos = m_type == VERTICAL ? Fl::event_y_root() : Fl::event_x_root(); + int currentPos = m_direction == Direction::VERTICAL ? Fl::event_y_root() : Fl::event_x_root(); switch (e) { @@ -157,7 +180,7 @@ int geResizerBar::handle(int e) break; case FL_ENTER: ret = 1; - fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE); + fl_cursor(m_direction == Direction::VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE); m_hover = true; redraw(); break; @@ -168,20 +191,19 @@ int geResizerBar::handle(int e) redraw(); break; case FL_PUSH: - ret = 1; - m_lastPos = currentPos; - m_initialPos = currentPos; + ret = 1; + m_lastPos = currentPos; break; case FL_DRAG: handleDrag(currentPos - m_lastPos); m_lastPos = currentPos; ret = 1; if (onDrag != nullptr) - onDrag(m_target); + onDrag(getFirstWidget()); break; case FL_RELEASE: - if (m_initialPos != currentPos && onRelease != nullptr) - onRelease(m_target); + if (onRelease != nullptr) + onRelease(getFirstWidget()); break; default: break; @@ -191,17 +213,20 @@ int geResizerBar::handle(int e) /* -------------------------------------------------------------------------- */ -int geResizerBar::getMinSize() const +void geResizerBar::resize(int X, int Y, int W, int H) { - return m_minSize; + if (m_direction == Direction::VERTICAL) + Fl_Box::resize(X, Y, W, h()); + else + Fl_Box::resize(X, Y, w(), H); } /* -------------------------------------------------------------------------- */ -void geResizerBar::resize(int x, int y, int w, int h) +void geResizerBar::moveTo(int p) { - if (m_type == VERTICAL) - Fl_Box::resize(x, y, w, m_origSize); // Height of resizer stays constant size - else - Fl_Box::resize(x, y, m_origSize, h); + const Fl_Widget& wd = getFirstWidget(); + const int curr = m_direction == Direction::VERTICAL ? wd.h() : wd.w(); + handleDrag(p - curr); } +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/basics/resizerBar.h b/src/gui/elems/basics/resizerBar.h index a860241..27ef9de 100644 --- a/src/gui/elems/basics/resizerBar.h +++ b/src/gui/elems/basics/resizerBar.h @@ -2,22 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * geResizerBar - * 'resizer bar' between widgets Fl_Scroll. Thanks to Greg Ercolano from - * FLTK dev team. http://seriss.com/people/erco/fltk/ - * - * Shows a resize cursor when hovered over. - * Assumes: - * - Parent is an Fl_Scroll - * - All children of Fl_Scroll are vertically arranged - * - The widget above us has a bottom edge touching our top edge - * ie. (w->y()+w->h() == this->y()) - * - * When this widget is dragged: - * - The widget above us (with a common edge) will be /resized/ - * vertically - * - All children below us will be /moved/ vertically - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual @@ -46,34 +30,93 @@ #include #include +/* 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 onDrag = nullptr; + std::function onRelease = nullptr; + +private: + /* isBefore + True if widget 'w' is before the drag bar. */ - std::function onDrag = nullptr; - std::function 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 findWidgets(std::function 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 diff --git a/src/gui/elems/basics/scroll.cpp b/src/gui/elems/basics/scroll.cpp index 9746ea2..c6faf35 100644 --- a/src/gui/elems/basics/scroll.cpp +++ b/src/gui/elems/basics/scroll.cpp @@ -42,11 +42,51 @@ geScroll::geScroll(int x, int y, int w, int h, int t) scrollbar.selection_color(G_COLOR_GREY_4); scrollbar.labelcolor(G_COLOR_LIGHT_1); scrollbar.slider(G_CUSTOM_BORDER_BOX); + scrollbar.callback(cb_onScrollV, this); hscrollbar.color(G_COLOR_GREY_2); hscrollbar.selection_color(G_COLOR_GREY_4); hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); + hscrollbar.callback(cb_onScrollH, this); +} + +/* -------------------------------------------------------------------------- */ + +void geScroll::cb_onScrollV(Fl_Widget* w, void* p) +{ + geScroll* s = static_cast(w->parent()); + Fl_Scrollbar* b = static_cast(w); + + s->scroll_to(s->xposition(), b->value()); + + (static_cast(p))->cb_onScrollV(); +} + +void geScroll::cb_onScrollH(Fl_Widget* w, void* p) +{ + geScroll* s = static_cast(w->parent()); + Fl_Scrollbar* b = static_cast(w); + + s->scroll_to(b->value(), s->yposition()); + + (static_cast(p))->cb_onScrollH(); +} + +/* -------------------------------------------------------------------------- */ + +void geScroll::cb_onScrollV() +{ + if (onScrollV != nullptr) + onScrollV(yposition()); +} + +/* -------------------------------------------------------------------------- */ + +void geScroll::cb_onScrollH() +{ + if (onScrollH != nullptr) + onScrollH(xposition()); } /* -------------------------------------------------------------------------- */ diff --git a/src/gui/elems/basics/scroll.h b/src/gui/elems/basics/scroll.h index 5963c5c..6562ec4 100644 --- a/src/gui/elems/basics/scroll.h +++ b/src/gui/elems/basics/scroll.h @@ -31,6 +31,7 @@ #define GE_SCROLL_H #include +#include class geScroll : public Fl_Scroll { @@ -38,6 +39,15 @@ public: geScroll(int x, int y, int w, int h, int type = Fl_Scroll::BOTH); int countChildren() const; + + std::function onScrollV{nullptr}; + std::function onScrollH{nullptr}; + +private: + static void cb_onScrollV(Fl_Widget* w, void* p); + static void cb_onScrollH(Fl_Widget* w, void* p); + void cb_onScrollV(); + void cb_onScrollH(); }; #endif diff --git a/src/gui/elems/basics/split.cpp b/src/gui/elems/basics/split.cpp new file mode 100644 index 0000000..658ed7c --- /dev/null +++ b/src/gui/elems/basics/split.cpp @@ -0,0 +1,69 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#include "split.h" +#include "core/const.h" +#include "gui/elems/basics/box.h" + +namespace giada::v +{ +geSplit::geSplit(int x, int y, int w, int h) +: Fl_Group(x, y, w, h) +, m_a(nullptr) +, m_b(nullptr) +, m_bar(0, 0, w, G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL, geResizerBar::Mode::RESIZE) +{ + end(); +} + +/* -------------------------------------------------------------------------- */ + +void geSplit::init(Fl_Widget& a, Fl_Widget& b) +{ + a.resize(x(), y(), w(), (h() / 2) - G_GUI_INNER_MARGIN); // Panel A goes on top + a.redraw(); + m_a = &a; + + m_bar.resize(x(), m_a->y() + m_a->h(), w(), G_GUI_INNER_MARGIN); + + b.resize(x(), m_bar.y() + m_bar.h(), w(), h() / 2); // Panel B goes on bottom + b.redraw(); + m_b = &b; + + Fl_Group::add(m_a); + Fl_Group::add(m_bar); + Fl_Group::add(m_b); + + resizable(m_a); +} + +/* -------------------------------------------------------------------------- */ + +void geSplit::resizePanel(Panel p, int s) +{ + m_bar.moveTo(p == Panel::A ? s : h() - s); +} +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/basics/split.h b/src/gui/elems/basics/split.h new file mode 100644 index 0000000..f407466 --- /dev/null +++ b/src/gui/elems/basics/split.h @@ -0,0 +1,60 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef GE_SPLIT_H +#define GE_SPLIT_H + +#include "gui/elems/basics/resizerBar.h" +#include + +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 diff --git a/src/gui/elems/config/tabAudio.cpp b/src/gui/elems/config/tabAudio.cpp index 0705c4d..8539215 100644 --- a/src/gui/elems/config/tabAudio.cpp +++ b/src/gui/elems/config/tabAudio.cpp @@ -37,7 +37,8 @@ namespace giada::v { -geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l, const std::vector& devices) +geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l, + const std::vector& devices) : geChoice(x, y, w, h, l) { if (devices.size() == 0) @@ -55,7 +56,8 @@ geTabAudio::geDeviceMenu::geDeviceMenu(int x, int y, int w, int h, const char* l /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData& data) +geTabAudio::geChannelMenu::geChannelMenu(int x, int y, int w, int h, const char* l, + const c::config::AudioDeviceData& data) : geChoice(x, y, w, h, l) , m_data(data) { @@ -77,7 +79,7 @@ int geTabAudio::geChannelMenu::getChannelsStart() const /* -------------------------------------------------------------------------- */ -void geTabAudio::geChannelMenu::rebuild(c::config::AudioDeviceData& data) +void geTabAudio::geChannelMenu::rebuild(const c::config::AudioDeviceData& data) { m_data = data; diff --git a/src/gui/elems/config/tabAudio.h b/src/gui/elems/config/tabAudio.h index a359193..91cf904 100644 --- a/src/gui/elems/config/tabAudio.h +++ b/src/gui/elems/config/tabAudio.h @@ -47,17 +47,17 @@ public: struct geChannelMenu : public geChoice { - geChannelMenu(int x, int y, int w, int h, const char* l, c::config::AudioDeviceData&); + geChannelMenu(int x, int y, int w, int h, const char* l, const c::config::AudioDeviceData&); int getChannelsCount() const; int getChannelsStart() const; - void rebuild(c::config::AudioDeviceData&); + void rebuild(const c::config::AudioDeviceData&); private: static constexpr int STEREO_OFFSET = 1000; - c::config::AudioDeviceData& m_data; + c::config::AudioDeviceData m_data; }; geTabAudio(int x, int y, int w, int h); diff --git a/src/gui/elems/config/tabBehaviors.cpp b/src/gui/elems/config/tabBehaviors.cpp index ddcced2..4178b57 100644 --- a/src/gui/elems/config/tabBehaviors.cpp +++ b/src/gui/elems/config/tabBehaviors.cpp @@ -31,17 +31,16 @@ #include "gui/elems/basics/check.h" #include -namespace giada +namespace giada::v { -namespace v -{ -geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H) +geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H, m::Conf::Data& c) : Fl_Group(X, Y, W, H) , m_container(X, Y + G_GUI_OUTER_MARGIN, Direction::VERTICAL, G_GUI_OUTER_MARGIN) , m_chansStopOnSeqHalt(0, 0, 280, 30, "Dynamic channels stop immediately when the sequencer\nis halted") , m_treatRecsAsLoops(0, 0, 280, 20, "Treat one shot channels with actions as loops") , m_inputMonitorDefaultOn(0, 0, 280, 20, "New sample channels have input monitor on by default") , m_overdubProtectionDefaultOn(0, 0, 280, 30, "New sample channels have overdub protection on\nby default") +, m_conf(c) { end(); @@ -56,20 +55,19 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H) add(m_container); - m_chansStopOnSeqHalt.value(m::conf::conf.chansStopOnSeqHalt); - m_treatRecsAsLoops.value(m::conf::conf.treatRecsAsLoops); - m_inputMonitorDefaultOn.value(m::conf::conf.inputMonitorDefaultOn); - m_overdubProtectionDefaultOn.value(m::conf::conf.overdubProtectionDefaultOn); + m_chansStopOnSeqHalt.value(m_conf.chansStopOnSeqHalt); + m_treatRecsAsLoops.value(m_conf.treatRecsAsLoops); + m_inputMonitorDefaultOn.value(m_conf.inputMonitorDefaultOn); + m_overdubProtectionDefaultOn.value(m_conf.overdubProtectionDefaultOn); } /* -------------------------------------------------------------------------- */ void geTabBehaviors::save() { - m::conf::conf.chansStopOnSeqHalt = m_chansStopOnSeqHalt.value(); - m::conf::conf.treatRecsAsLoops = m_treatRecsAsLoops.value(); - m::conf::conf.inputMonitorDefaultOn = m_inputMonitorDefaultOn.value(); - m::conf::conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value(); + m_conf.chansStopOnSeqHalt = m_chansStopOnSeqHalt.value(); + m_conf.treatRecsAsLoops = m_treatRecsAsLoops.value(); + m_conf.inputMonitorDefaultOn = m_inputMonitorDefaultOn.value(); + m_conf.overdubProtectionDefaultOn = m_overdubProtectionDefaultOn.value(); } -} // namespace v -} // namespace giada \ No newline at end of file +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/config/tabBehaviors.h b/src/gui/elems/config/tabBehaviors.h index 22785ae..a481c46 100644 --- a/src/gui/elems/config/tabBehaviors.h +++ b/src/gui/elems/config/tabBehaviors.h @@ -27,29 +27,29 @@ #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 -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 diff --git a/src/gui/elems/config/tabMidi.cpp b/src/gui/elems/config/tabMidi.cpp index 2fefe2d..df36959 100644 --- a/src/gui/elems/config/tabMidi.cpp +++ b/src/gui/elems/config/tabMidi.cpp @@ -24,235 +24,142 @@ * * -------------------------------------------------------------------------- */ -#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 #include -namespace giada +namespace giada::v { -namespace v +geTabMidi::geMenu::geMenu(int x, int y, int w, int h, const char* l, + const std::vector& 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 diff --git a/src/gui/elems/config/tabMidi.h b/src/gui/elems/config/tabMidi.h index 658bc5a..b2374a3 100644 --- a/src/gui/elems/config/tabMidi.h +++ b/src/gui/elems/config/tabMidi.h @@ -27,40 +27,42 @@ #ifndef GE_TAB_MIDI_H #define GE_TAB_MIDI_H +#include "glue/config.h" +#include "gui/elems/basics/choice.h" #include 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&, + 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 diff --git a/src/gui/elems/config/tabMisc.cpp b/src/gui/elems/config/tabMisc.cpp index b6917fa..bcd66ad 100644 --- a/src/gui/elems/config/tabMisc.cpp +++ b/src/gui/elems/config/tabMisc.cpp @@ -25,41 +25,30 @@ * -------------------------------------------------------------------------- */ #include "tabMisc.h" -#include "core/conf.h" #include "core/const.h" -#include namespace giada::v { -geTabMisc::geTabMisc(int X, int Y, int W, int H) +geTabMisc::geTabMisc(int X, int Y, int W) : geGroup(X, Y) +, m_data(c::config::getMiscData()) , m_debugMsg(W - 230, 9, 230, 20, "Debug messages") , m_tooltips(W - 230, 37, 230, 20, "Tooltips") { add(&m_debugMsg); add(&m_tooltips); - m_debugMsg.add("Disabled"); - m_debugMsg.add("To standard output"); - m_debugMsg.add("To file"); + m_debugMsg.addItem("Disabled"); + m_debugMsg.addItem("To standard output"); + m_debugMsg.addItem("To file"); + m_debugMsg.onChange = [this](ID id) { m_data.logMode = id; }; - m_tooltips.add("Disabled"); - m_tooltips.add("Enabled"); + m_tooltips.addItem("Disabled"); + m_tooltips.addItem("Enabled"); + m_tooltips.onChange = [this](ID id) { m_data.showTooltips = id; }; - switch (m::conf::conf.logMode) - { - case LOG_MODE_MUTE: - m_debugMsg.value(0); - break; - case LOG_MODE_STDOUT: - m_debugMsg.value(1); - break; - case LOG_MODE_FILE: - m_debugMsg.value(2); - break; - } - - m_tooltips.value(m::conf::conf.showTooltips); + m_debugMsg.showItem(m_data.logMode); + m_tooltips.showItem(m_data.showTooltips); copy_label("Misc"); labelsize(G_GUI_FONT_SIZE_BASE); @@ -70,20 +59,6 @@ geTabMisc::geTabMisc(int X, int Y, int W, int H) void geTabMisc::save() { - switch (m_debugMsg.value()) - { - case 0: - m::conf::conf.logMode = LOG_MODE_MUTE; - break; - case 1: - m::conf::conf.logMode = LOG_MODE_STDOUT; - break; - case 2: - m::conf::conf.logMode = LOG_MODE_FILE; - break; - } - - m::conf::conf.showTooltips = m_tooltips.value(); - Fl_Tooltip::enable(m_tooltips.value()); + c::config::save(m_data); } } // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/config/tabMisc.h b/src/gui/elems/config/tabMisc.h index 9f0058c..f854eee 100644 --- a/src/gui/elems/config/tabMisc.h +++ b/src/gui/elems/config/tabMisc.h @@ -27,20 +27,22 @@ #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; }; diff --git a/src/gui/elems/config/tabPlugins.cpp b/src/gui/elems/config/tabPlugins.cpp index b2c4fa1..49eb2ae 100644 --- a/src/gui/elems/config/tabPlugins.cpp +++ b/src/gui/elems/config/tabPlugins.cpp @@ -30,59 +30,56 @@ #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 #include -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { geTabPlugins::geTabPlugins(int X, int Y, int W, int H) -: Fl_Group(X, Y, W, H, "Plugins") +: Fl_Group(X, Y, W, H, "Plug-ins") +, m_browse(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm) +, m_folderPath(m_browse.x() - 258, y() + 9, 250, G_GUI_UNIT) +, m_scanButton(x() + w() - 150, m_folderPath.y() + m_folderPath.h() + 8, 150, G_GUI_UNIT) +, m_info(x(), m_scanButton.y() + m_scanButton.h() + 8, w(), 240) { - m_browse = new geButton(x() + w() - G_GUI_UNIT, y() + 9, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); - m_folderPath = new geInput(m_browse->x() - 258, y() + 9, 250, G_GUI_UNIT); - m_scanButton = new geButton(x() + w() - 150, m_folderPath->y() + m_folderPath->h() + 8, 150, G_GUI_UNIT); - m_info = new geBox(x(), m_scanButton->y() + m_scanButton->h() + 8, w(), 240); - end(); labelsize(G_GUI_FONT_SIZE_BASE); selection_color(G_COLOR_GREY_4); - m_info->label("Scan in progress. Please wait..."); - m_info->hide(); - - m_folderPath->value(m::conf::conf.pluginPath.c_str()); - m_folderPath->label("Plugins folder"); + m_info.hide(); - m_browse->callback(cb_browse, (void*)this); + m_folderPath.label("Plug-ins folder"); + m_folderPath.onChange = [this](const std::string& v) { + m_data.pluginPath = v; + }; - m_scanButton->callback(cb_scan, (void*)this); + m_browse.callback(cb_browse, (void*)this); + m_scanButton.callback(cb_scan, (void*)this); - refreshCount(); + rebuild(); } /* -------------------------------------------------------------------------- */ -void geTabPlugins::refreshCount() +void geTabPlugins::rebuild() { - std::string scanLabel = "Scan (" + std::to_string(m::pluginManager::countAvailablePlugins()) + " found)"; - m_scanButton->copy_label(scanLabel.c_str()); + m_data = c::config::getPluginData(); + + const std::string scanLabel = "Scan (" + std::to_string(m_data.numAvailablePlugins) + " found)"; + m_scanButton.copy_label(scanLabel.c_str()); + + m_folderPath.value(m_data.pluginPath.c_str()); + m_folderPath.redraw(); } /* -------------------------------------------------------------------------- */ @@ -94,10 +91,7 @@ void geTabPlugins::cb_browse(Fl_Widget* /*w*/, void* p) { ((geTabPlugins*)p)->cb void geTabPlugins::cb_browse() { - v::gdBrowserDir* browser = new v::gdBrowserDir("Add plug-ins directory", - m::conf::conf.patchPath, c::plugin::setPluginPathCb); - - static_cast(top_window())->addSubWindow(browser); + c::layout::openBrowserForPlugins(*static_cast(top_window())); } /* -------------------------------------------------------------------------- */ @@ -106,32 +100,22 @@ void geTabPlugins::cb_scan() { std::function 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 diff --git a/src/gui/elems/config/tabPlugins.h b/src/gui/elems/config/tabPlugins.h index f1af634..aeac23a 100644 --- a/src/gui/elems/config/tabPlugins.h +++ b/src/gui/elems/config/tabPlugins.h @@ -29,15 +29,13 @@ #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 -class geInput; -class geButton; -class geBox; - -namespace giada -{ -namespace v +namespace giada::v { class geTabPlugins : public Fl_Group { @@ -45,23 +43,22 @@ public: geTabPlugins(int x, int y, int w, int h); void save(); - void refreshVstPath(); + void rebuild(); - private: +private: static void cb_scan(Fl_Widget* /*w*/, void* p); static void cb_browse(Fl_Widget* /*w*/, void* p); void cb_scan(); void cb_browse(); - void refreshCount(); + c::config::PluginData m_data; - geInput* m_folderPath; - geButton* m_browse; - geButton* m_scanButton; - geBox* m_info; + geButton m_browse; + geInput m_folderPath; + geButton m_scanButton; + geBox m_info; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif // WITH_VST diff --git a/src/gui/elems/mainWindow/keyboard/channel.cpp b/src/gui/elems/mainWindow/keyboard/channel.cpp index 9d80c4b..48eb094 100644 --- a/src/gui/elems/mainWindow/keyboard/channel.cpp +++ b/src/gui/elems/mainWindow/keyboard/channel.cpp @@ -25,25 +25,21 @@ * -------------------------------------------------------------------------- */ #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 #include -extern giada::v::gdMainWindow* G_MainWin; +extern giada::v::Ui g_ui; namespace giada::v { @@ -137,7 +133,7 @@ void geChannel::cb_changeVol() #ifdef WITH_VST void geChannel::cb_openFxWindow() { - u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST); + c::layout::openChannelPluginListWindow(m_channel.id); } #endif @@ -152,7 +148,7 @@ int geChannel::getColumnId() void geChannel::blink() { - if (u::gui::shouldBlink()) + if (g_ui.shouldBlink()) mainButton->setPlayMode(); else mainButton->setDefaultMode(); diff --git a/src/gui/elems/mainWindow/keyboard/channelButton.cpp b/src/gui/elems/mainWindow/keyboard/channelButton.cpp index ed8dd56..e868d63 100644 --- a/src/gui/elems/mainWindow/keyboard/channelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelButton.cpp @@ -28,8 +28,8 @@ #include "core/channels/channel.h" #include "core/const.h" #include "core/model/model.h" -#include "core/recorder.h" #include "glue/channel.h" +#include "src/core/actions/actions.h" #include "utils/string.h" #include diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.cpp b/src/gui/elems/mainWindow/keyboard/channelMode.cpp index b0a7351..66e837a 100644 --- a/src/gui/elems/mainWindow/keyboard/channelMode.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelMode.cpp @@ -38,9 +38,7 @@ #include #include -namespace giada -{ -namespace v +namespace giada::v { geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d) : Fl_Menu_Button(x, y, w, h) @@ -56,6 +54,7 @@ geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d) add("Loop . once . bar", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_ONCE_BAR); add("Loop . repeat", 0, cb_changeMode, (void*)SamplePlayerMode::LOOP_REPEAT); add("Oneshot . basic", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC); + add("Oneshot . basic . pause", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_BASIC_PAUSE); add("Oneshot . press", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_PRESS); add("Oneshot . retrig", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_RETRIG); add("Oneshot . endless", 0, cb_changeMode, (void*)SamplePlayerMode::SINGLE_ENDLESS); @@ -86,6 +85,9 @@ void geChannelMode::draw() case SamplePlayerMode::SINGLE_BASIC: fl_draw_pixmap(oneshotBasic_xpm, x() + 1, y() + 1); break; + case SamplePlayerMode::SINGLE_BASIC_PAUSE: + fl_draw_pixmap(oneshotBasicPause_xpm, x() + 1, y() + 1); + break; case SamplePlayerMode::SINGLE_PRESS: fl_draw_pixmap(oneshotPress_xpm, x() + 1, y() + 1); break; @@ -95,6 +97,9 @@ void geChannelMode::draw() case SamplePlayerMode::SINGLE_ENDLESS: fl_draw_pixmap(oneshotEndless_xpm, x() + 1, y() + 1); break; + default: + assert(false); + break; } } @@ -108,5 +113,4 @@ void geChannelMode::cb_changeMode(int mode) { c::channel::setSamplePlayerMode(m_channel.id, static_cast(mode)); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.h b/src/gui/elems/mainWindow/keyboard/channelMode.h index 125d868..c31d923 100644 --- a/src/gui/elems/mainWindow/keyboard/channelMode.h +++ b/src/gui/elems/mainWindow/keyboard/channelMode.h @@ -35,6 +35,7 @@ namespace giada::c::channel { struct Data; } + namespace giada::v { class geChannelMode : public Fl_Menu_Button @@ -44,7 +45,7 @@ public: void draw() override; - private: +private: static void cb_changeMode(Fl_Widget* /*w*/, void* p); void cb_changeMode(int mode); diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp index 6d9c250..cac9515 100644 --- a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp @@ -24,9 +24,10 @@ * * -------------------------------------------------------------------------- */ -#include "channelStatus.h" +#include "gui/elems/mainWindow/keyboard/channelStatus.h" #include "core/const.h" #include "glue/channel.h" +#include "utils/math.h" #include namespace giada::v @@ -44,9 +45,12 @@ void geChannelStatus::draw() fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // reset border fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // reset background - ChannelStatus playStatus = m_channel.getPlayStatus(); - ChannelStatus recStatus = m_channel.getRecStatus(); - Pixel pos = 0; + const ChannelStatus playStatus = m_channel.getPlayStatus(); + const ChannelStatus recStatus = m_channel.getRecStatus(); + const Frame tracker = m_channel.sample->getTracker(); + const Frame begin = m_channel.sample->getBegin(); + const Frame end = m_channel.sample->getEnd(); + const Pixel pos = u::math::map(tracker, begin, end, 0, w()); if (playStatus == ChannelStatus::WAIT || playStatus == ChannelStatus::ENDING || @@ -57,18 +61,13 @@ void geChannelStatus::draw() } else if (playStatus == ChannelStatus::PLAY) { - /* Equation for the progress bar: - ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */ - Frame tracker = m_channel.sample->getTracker(); - Frame begin = m_channel.sample->getBegin(); - Frame end = m_channel.sample->getEnd(); - pos = ((tracker - begin) * (w() - 1)) / ((end - begin)); fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1); + fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1); } else + { fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); // status empty - - if (pos != 0) - fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_LIGHT_1); + fl_rectf(x() + 1, y() + 1, pos, h() - 2, G_COLOR_GREY_4); + } } } // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/keyboard/column.cpp b/src/gui/elems/mainWindow/keyboard/column.cpp index 1074dee..72982b5 100644 --- a/src/gui/elems/mainWindow/keyboard/column.cpp +++ b/src/gui/elems/mainWindow/keyboard/column.cpp @@ -34,6 +34,7 @@ #include "midiChannel.h" #include "sampleChannel.h" #include "utils/fs.h" +#include "utils/gui.h" #include "utils/log.h" #include "utils/string.h" #include @@ -76,20 +77,21 @@ geChannel* geColumn::addChannel(c::channel::Data d) gch = new geMidiChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d); geResizerBar* bar = new geResizerBar(x(), gch->y() + gch->h(), w(), - G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::VERTICAL, gch); + G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::Direction::VERTICAL, + geResizerBar::Mode::MOVE); /* Update the column height while dragging the resizer bar. */ - bar->onDrag = [this](const Fl_Widget* /*w*/) { + bar->onDrag = [this](const Fl_Widget& /*w*/) { resizable(nullptr); size(this->w(), (child(children() - 1)->y() - y()) + G_GUI_INNER_MARGIN); }; /* Store the channel height in model when the resizer bar is released. */ - bar->onRelease = [channelId = d.id, this](const Fl_Widget* w) { + bar->onRelease = [channelId = d.id, this](const Fl_Widget& w) { resizable(this); - c::channel::setHeight(channelId, w->h()); + c::channel::setHeight(channelId, w.h()); }; m_channels.push_back(gch); @@ -115,10 +117,10 @@ void geColumn::cb_addChannel() u::log::print("[geColumn::cb_addChannel] id = %d\n", id); Fl_Menu_Item menu[] = { - {"Add Sample channel"}, - {"Add MIDI channel"}, - {"Remove"}, - {0}}; + u::gui::makeMenuItem("Add Sample channel"), + u::gui::makeMenuItem("Add MIDI channel"), + u::gui::makeMenuItem("Remove"), + {}}; if (countChannels() > 0) menu[2].deactivate(); diff --git a/src/gui/elems/mainWindow/keyboard/column.h b/src/gui/elems/mainWindow/keyboard/column.h index 43ebdf2..2ab96d5 100644 --- a/src/gui/elems/mainWindow/keyboard/column.h +++ b/src/gui/elems/mainWindow/keyboard/column.h @@ -34,10 +34,10 @@ #include class geButton; -class geResizerBar; namespace giada::v { +class geResizerBar; class geKeyboard; class geChannel; class geColumn : public Fl_Group @@ -65,7 +65,7 @@ public: geResizerBar* resizerBar; - private: +private: static void cb_addChannel(Fl_Widget* /*w*/, void* p); void cb_addChannel(); diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.cpp b/src/gui/elems/mainWindow/keyboard/keyboard.cpp index c94af88..7b304ec 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.cpp +++ b/src/gui/elems/mainWindow/keyboard/keyboard.cpp @@ -33,6 +33,7 @@ #include "gui/dispatcher.h" #include "gui/elems/basics/boxtypes.h" #include "gui/elems/basics/resizerBar.h" +#include "gui/ui.h" #include "sampleChannel.h" #include "utils/fs.h" #include "utils/log.h" @@ -41,9 +42,9 @@ #include #include -namespace giada -{ -namespace v +extern giada::v::Ui g_ui; + +namespace giada::v { geKeyboard::geKeyboard(int X, int Y, int W, int H) : geScroll(X, Y, W, H, Fl_Scroll::BOTH_ALWAYS) @@ -140,7 +141,7 @@ int geKeyboard::handle(int e) case FL_KEYDOWN: // Keyboard key pushed case FL_KEYUP: { // Keyboard key released - dispatcher::dispatchKey(e); + g_ui.dispatcher.dispatchKey(e); return 1; } case FL_DND_ENTER: // return(1) for these events to 'accept' dnd @@ -207,12 +208,12 @@ void geKeyboard::addColumn(int width, ID id) /* Add a new column + a new resizer bar. */ - geResizerBar* bar = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::HORIZONTAL); + geResizerBar* bar = new geResizerBar(colx + width, y(), COLUMN_GAP, h(), G_MIN_COLUMN_WIDTH, geResizerBar::Direction::HORIZONTAL); geColumn* column = new geColumn(colx, y(), width, G_GUI_UNIT, m_columnId.generate(id), bar); /* Store the column width in layout when the resizer bar is released. */ - bar->onRelease = [=](const Fl_Widget* /*w*/) { + bar->onRelease = [=](const Fl_Widget& /*w*/) { storeLayout(); }; @@ -294,5 +295,4 @@ void geKeyboard::storeLayout() layout.push_back({c->id, c->w()}); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.h b/src/gui/elems/mainWindow/keyboard/keyboard.h index d9326cb..70d131e 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.h +++ b/src/gui/elems/mainWindow/keyboard/keyboard.h @@ -34,12 +34,10 @@ #include class geButton; -class geResizerBar; -namespace giada -{ -namespace v +namespace giada::v { +class geResizerBar; class geColumn; class geChannel; class geKeyboard : public geScroll @@ -90,11 +88,11 @@ public: void forEachColumn(std::function 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 layout; - private: +private: static constexpr int COLUMN_GAP = 20; static void cb_addColumn(Fl_Widget* /*w*/, void* p); @@ -128,7 +126,6 @@ public: geButton* m_addColumnBtn; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/mainWindow/keyboard/midiChannel.cpp b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp index dcb3307..2a9368f 100644 --- a/src/gui/elems/mainWindow/keyboard/midiChannel.cpp +++ b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp @@ -24,39 +24,27 @@ * * -------------------------------------------------------------------------- */ -#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 #include -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { namespace { @@ -87,25 +75,25 @@ void menuCallback(Fl_Widget* w, void* v) case Menu::__END_CLEAR_ACTION_SUBMENU__: break; case Menu::EDIT_ACTIONS: - u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR); + c::layout::openMidiActionEditor(data.id); break; case Menu::CLEAR_ACTIONS_ALL: c::recorder::clearAllActions(data.id); break; case Menu::SETUP_KEYBOARD_INPUT: - u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER); + c::layout::openKeyGrabberWindow(data); break; case Menu::SETUP_MIDI_INPUT: - u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT); + c::layout::openChannelMidiInputWindow(data.id); break; case Menu::SETUP_MIDI_OUTPUT: - u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT); + c::layout::openMidiChannelMidiOutputWindow(data.id); break; case Menu::CLONE_CHANNEL: c::channel::cloneChannel(data.id); break; case Menu::RENAME_CHANNEL: - u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME); + c::layout::openRenameChannelWindow(data); break; case Menu::DELETE_CHANNEL: c::channel::deleteChannel(data.id); @@ -189,7 +177,7 @@ void geMidiChannel::cb_openMenu(Fl_Widget* /*w*/, void* p) { ((geMidiChannel*)p) void geMidiChannel::cb_playButton() { - v::dispatcher::dispatchTouch(*this, playButton->value()); + m_channel.viewDispatcher.dispatchTouch(*this, playButton->value()); } /* -------------------------------------------------------------------------- */ @@ -197,17 +185,17 @@ void geMidiChannel::cb_playButton() void geMidiChannel::cb_openMenu() { Fl_Menu_Item rclick_menu[] = { - {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS}, - {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU}, - {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL}, - {0}, - {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT}, - {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT}, - {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT}, - {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL}, - {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL}, - {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL}, - {0}}; + u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS), + u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU), + u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL), + {}, + u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT), + u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT), + u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT), + u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL), + u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL), + u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL), + {}}; /* No 'clear actions' if there are no actions. */ @@ -246,6 +234,4 @@ void geMidiChannel::resize(int X, int Y, int W, int H) packWidgets(); } - -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp index 690220a..db9fcd1 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp @@ -24,50 +24,27 @@ * * -------------------------------------------------------------------------- */ -#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 -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { namespace { @@ -114,46 +91,37 @@ void menuCallback(Fl_Widget* w, void* v) } case Menu::LOAD_SAMPLE: { - gdWindow* w = new gdBrowserLoad("Browse sample", - m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id); - u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER); + c::layout::openBrowserForSampleLoad(data.id); break; } case Menu::EXPORT_SAMPLE: { - gdWindow* w = new gdBrowserSave("Save sample", - m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id); - u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER); + c::layout::openBrowserForSampleSave(data.id); break; } case Menu::SETUP_KEYBOARD_INPUT: { - u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), - WID_KEY_GRABBER); + c::layout::openKeyGrabberWindow(data); break; } case Menu::SETUP_MIDI_INPUT: { - u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), - WID_MIDI_INPUT); + c::layout::openChannelMidiInputWindow(data.id); break; } case Menu::SETUP_MIDI_OUTPUT: { - u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id), - WID_MIDI_OUTPUT); + c::layout::openSampleChannelMidiOutputWindow(data.id); break; } case Menu::EDIT_SAMPLE: { - u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id), - WID_SAMPLE_EDITOR); + c::layout::openSampleEditor(data.id); break; } case Menu::EDIT_ACTIONS: { - u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id), - WID_ACTION_EDITOR); + c::layout::openSampleActionEditor(data.id); break; } case Menu::CLEAR_ACTIONS: @@ -181,8 +149,7 @@ void menuCallback(Fl_Widget* w, void* v) } case Menu::RENAME_CHANNEL: { - u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), - WID_SAMPLE_NAME); + c::layout::openRenameChannelWindow(data); break; } case Menu::FREE_CHANNEL: @@ -285,7 +252,7 @@ void geSampleChannel::cb_readActions(Fl_Widget* /*w*/, void* p) { ((geSampleChan void geSampleChannel::cb_playButton() { - v::dispatcher::dispatchTouch(*this, playButton->value()); + m_channel.viewDispatcher.dispatchTouch(*this, playButton->value()); } /* -------------------------------------------------------------------------- */ @@ -295,31 +262,31 @@ void geSampleChannel::cb_openMenu() /* If you're recording (input or actions) no menu is allowed; you can't do anything, especially deallocate the channel. */ - if (m::recManager::isRecording()) + if (m_channel.isRecordingAction() || m_channel.isRecordingInput()) return; Fl_Menu_Item rclick_menu[] = { - {"Input monitor", 0, menuCallback, (void*)Menu::INPUT_MONITOR, - FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)}, - {"Overdub protection", 0, menuCallback, (void*)Menu::OVERDUB_PROTECTION, - FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)}, - {"Load new sample...", 0, menuCallback, (void*)Menu::LOAD_SAMPLE}, - {"Export sample to file...", 0, menuCallback, (void*)Menu::EXPORT_SAMPLE}, - {"Setup keyboard input...", 0, menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT}, - {"Setup MIDI input...", 0, menuCallback, (void*)Menu::SETUP_MIDI_INPUT}, - {"Setup MIDI output...", 0, menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT}, - {"Edit sample...", 0, menuCallback, (void*)Menu::EDIT_SAMPLE}, - {"Edit actions...", 0, menuCallback, (void*)Menu::EDIT_ACTIONS}, - {"Clear actions", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU}, - {"All", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL}, - {"Volume", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME}, - {"Start/Stop", 0, menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP}, - {0}, - {"Rename", 0, menuCallback, (void*)Menu::RENAME_CHANNEL}, - {"Clone", 0, menuCallback, (void*)Menu::CLONE_CHANNEL}, - {"Free", 0, menuCallback, (void*)Menu::FREE_CHANNEL}, - {"Delete", 0, menuCallback, (void*)Menu::DELETE_CHANNEL}, - {0}}; + u::gui::makeMenuItem("Input monitor", menuCallback, (void*)Menu::INPUT_MONITOR, + FL_MENU_TOGGLE | (m_channel.sample->getInputMonitor() ? FL_MENU_VALUE : 0)), + u::gui::makeMenuItem("Overdub protection", menuCallback, (void*)Menu::OVERDUB_PROTECTION, + FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->getOverdubProtection() ? FL_MENU_VALUE : 0)), + u::gui::makeMenuItem("Load new sample...", menuCallback, (void*)Menu::LOAD_SAMPLE), + u::gui::makeMenuItem("Export sample to file...", menuCallback, (void*)Menu::EXPORT_SAMPLE), + u::gui::makeMenuItem("Setup keyboard input...", menuCallback, (void*)Menu::SETUP_KEYBOARD_INPUT), + u::gui::makeMenuItem("Setup MIDI input...", menuCallback, (void*)Menu::SETUP_MIDI_INPUT), + u::gui::makeMenuItem("Setup MIDI output...", menuCallback, (void*)Menu::SETUP_MIDI_OUTPUT), + u::gui::makeMenuItem("Edit sample...", menuCallback, (void*)Menu::EDIT_SAMPLE), + u::gui::makeMenuItem("Edit actions...", menuCallback, (void*)Menu::EDIT_ACTIONS), + u::gui::makeMenuItem("Clear actions", menuCallback, (void*)Menu::CLEAR_ACTIONS, FL_SUBMENU), + u::gui::makeMenuItem("All", menuCallback, (void*)Menu::CLEAR_ACTIONS_ALL), + u::gui::makeMenuItem("Volume", menuCallback, (void*)Menu::CLEAR_ACTIONS_VOLUME), + u::gui::makeMenuItem("Start/Stop", menuCallback, (void*)Menu::CLEAR_ACTIONS_START_STOP), + {}, + u::gui::makeMenuItem("Rename", menuCallback, (void*)Menu::RENAME_CHANNEL), + u::gui::makeMenuItem("Clone", menuCallback, (void*)Menu::CLONE_CHANNEL), + u::gui::makeMenuItem("Free", menuCallback, (void*)Menu::FREE_CHANNEL), + u::gui::makeMenuItem("Delete", menuCallback, (void*)Menu::DELETE_CHANNEL), + {}}; if (m_channel.sample->waveId == 0) { @@ -423,5 +390,4 @@ void geSampleChannel::resize(int X, int Y, int W, int H) packWidgets(); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.h b/src/gui/elems/mainWindow/keyboard/sampleChannel.h index 3b0ec91..32cfc99 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannel.h +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.h @@ -47,7 +47,7 @@ public: geChannelMode* modeBox; geStatusButton* readActions; - private: +private: static void cb_playButton(Fl_Widget* /*w*/, void* p); static void cb_openMenu(Fl_Widget* /*w*/, void* p); static void cb_readActions(Fl_Widget* /*w*/, void* p); diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp index e8c163e..73f5fea 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp @@ -33,11 +33,7 @@ #include "utils/string.h" #include -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d) : geChannelButton(x, y, w, h, d) @@ -91,6 +87,4 @@ int geSampleChannelButton::handle(int e) } return ret; } - -} // namespace v -} // namespace giada +} // namespace giada::v diff --git a/src/gui/elems/mainWindow/mainIO.cpp b/src/gui/elems/mainWindow/mainIO.cpp index f89f8b3..cc4a408 100644 --- a/src/gui/elems/mainWindow/mainIO.cpp +++ b/src/gui/elems/mainWindow/mainIO.cpp @@ -24,24 +24,19 @@ * * --------------------------------------------------------------------------- */ -#include "mainIO.h" +#include "gui/elems/mainWindow/mainIO.h" #include "core/const.h" #include "core/graphics.h" #include "glue/channel.h" #include "glue/events.h" +#include "glue/layout.h" #include "glue/main.h" -#include "gui/dialogs/mainWindow.h" -#include "gui/dialogs/pluginList.h" #include "gui/elems/basics/dial.h" #include "gui/elems/basics/statusButton.h" #include "gui/elems/soundMeter.h" #include "utils/gui.h" -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { geMainIO::geMainIO(int x, int y) : gePack(x, y, Direction::HORIZONTAL) @@ -127,12 +122,12 @@ void geMainIO::cb_inToOut() void geMainIO::cb_masterFxOut() { - u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_OUT_CHANNEL_ID), WID_FX_LIST); + c::layout::openMasterOutPluginListWindow(); } void geMainIO::cb_masterFxIn() { - u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m::mixer::MASTER_IN_CHANNEL_ID), WID_FX_LIST); + c::layout::openMasterInPluginListWindow(); } #endif @@ -169,8 +164,10 @@ void geMainIO::setMasterFxInFull(bool v) void geMainIO::refresh() { - outMeter.mixerPeak = m_io.getMasterOutPeak(); - inMeter.mixerPeak = m_io.getMasterInPeak(); + outMeter.peak = m_io.getMasterOutPeak(); + outMeter.ready = m_io.isKernelReady(); + inMeter.peak = m_io.getMasterInPeak(); + inMeter.ready = m_io.isKernelReady(); outMeter.redraw(); inMeter.redraw(); } @@ -189,5 +186,4 @@ void geMainIO::rebuild() inToOut.value(m_io.inToOut); #endif } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/mainWindow/mainIO.h b/src/gui/elems/mainWindow/mainIO.h index 589bd99..3abc4fb 100644 --- a/src/gui/elems/mainWindow/mainIO.h +++ b/src/gui/elems/mainWindow/mainIO.h @@ -36,9 +36,7 @@ #endif #include "glue/main.h" -namespace giada -{ -namespace v +namespace giada::v { class geMainIO : public gePack { @@ -55,7 +53,7 @@ public: void setMasterFxInFull(bool v); #endif - private: +private: static void cb_outVol(Fl_Widget* /*w*/, void* p); static void cb_inVol(Fl_Widget* /*w*/, void* p); static void cb_inToOut(Fl_Widget* /*w*/, void* p); @@ -81,7 +79,6 @@ public: geStatusButton masterFxIn; #endif }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/mainWindow/mainMenu.cpp b/src/gui/elems/mainWindow/mainMenu.cpp index 5015551..3d57bd0 100644 --- a/src/gui/elems/mainWindow/mainMenu.cpp +++ b/src/gui/elems/mainWindow/mainMenu.cpp @@ -24,34 +24,18 @@ * * -------------------------------------------------------------------------- */ -#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 -#include -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace v +namespace giada::v { geMainMenu::geMainMenu(int x, int y) : gePack(x, y, Direction::HORIZONTAL) @@ -71,10 +55,10 @@ geMainMenu::geMainMenu(int x, int y) edit->callback(cb_edit, (void*)this); about->callback([](Fl_Widget* /*w*/, void* /*v*/) { - u::gui::openSubWindow(G_MainWin, new gdAbout(), WID_ABOUT); + c::layout::openAboutWindow(); }); config->callback([](Fl_Widget* /*w*/, void* /*v*/) { - u::gui::openSubWindow(G_MainWin, new gdConfig(400, 370), WID_CONFIG); + c::layout::openConfigWindow(); }); } @@ -92,14 +76,14 @@ void geMainMenu::cb_file() /* An Fl_Menu_Button is made of many Fl_Menu_Item */ Fl_Menu_Item menu[] = { - {"Open project..."}, - {"Save project..."}, - {"Close project"}, + u::gui::makeMenuItem("Open project..."), + u::gui::makeMenuItem("Save project..."), + u::gui::makeMenuItem("Close project"), #ifndef NDEBUG - {"Debug stats"}, + u::gui::makeMenuItem("Debug stats"), #endif - {"Quit Giada"}, - {0}}; + u::gui::makeMenuItem("Quit Giada"), + {}}; Fl_Menu_Button b(0, 0, 100, 50); b.box(G_CUSTOM_BORDER_BOX); @@ -113,29 +97,25 @@ void geMainMenu::cb_file() if (strcmp(m->label(), "Open project...") == 0) { - gdWindow* childWin = new gdBrowserLoad("Open project", - conf::conf.patchPath, c::storage::loadProject, 0); - u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); + c::layout::openBrowserForProjectLoad(); } else if (strcmp(m->label(), "Save project...") == 0) { - gdWindow* childWin = new gdBrowserSave("Save project", conf::conf.patchPath, - patch::patch.name, c::storage::saveProject, 0); - u::gui::openSubWindow(G_MainWin, childWin, WID_FILE_BROWSER); + c::layout::openBrowserForProjectSave(); } else if (strcmp(m->label(), "Close project") == 0) { c::main::closeProject(); } -#ifndef NDEBUG +#ifdef G_DEBUG_MODE else if (strcmp(m->label(), "Debug stats") == 0) { - m::model::debug(); + c::main::printDebugInfo(); } #endif else if (strcmp(m->label(), "Quit Giada") == 0) { - G_MainWin->do_callback(); + c::main::quitGiada(); } } @@ -143,19 +123,21 @@ void geMainMenu::cb_file() void geMainMenu::cb_edit() { - Fl_Menu_Item menu[] = { - {"Free all Sample channels"}, - {"Clear all actions"}, - {"Setup global MIDI input..."}, - {0}}; + c::main::MainMenu menu = c::main::getMainMenu(); - menu[0].deactivate(); - menu[1].deactivate(); + Fl_Menu_Item menuItem[] = { + u::gui::makeMenuItem("Free all Sample channels"), + u::gui::makeMenuItem("Clear all actions"), + u::gui::makeMenuItem("Setup global MIDI input..."), + {}}; - if (m::mh::hasAudioData()) - menu[0].activate(); - if (m::mh::hasActions()) - menu[1].activate(); + menuItem[0].deactivate(); + menuItem[1].deactivate(); + + if (menu.hasAudioData) + menuItem[0].activate(); + if (menu.hasActions) + menuItem[1].activate(); Fl_Menu_Button b(0, 0, 100, 50); b.box(G_CUSTOM_BORDER_BOX); @@ -163,7 +145,7 @@ void geMainMenu::cb_edit() b.textcolor(G_COLOR_LIGHT_2); b.color(G_COLOR_GREY_2); - const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b); + const Fl_Menu_Item* m = menuItem->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b); if (!m) return; @@ -172,8 +154,6 @@ void geMainMenu::cb_edit() else if (strcmp(m->label(), "Clear all actions") == 0) c::main::clearAllActions(); else if (strcmp(m->label(), "Setup global MIDI input...") == 0) - u::gui::openSubWindow(G_MainWin, new gdMidiInputMaster(), WID_MIDI_INPUT); + c::layout::openMasterMidiInputWindow(); } - -} // namespace v -} // namespace giada +} // namespace giada::v diff --git a/src/gui/elems/mainWindow/mainMenu.h b/src/gui/elems/mainWindow/mainMenu.h index c6a317e..3f14b29 100644 --- a/src/gui/elems/mainWindow/mainMenu.h +++ b/src/gui/elems/mainWindow/mainMenu.h @@ -31,16 +31,14 @@ class geButton; -namespace giada -{ -namespace v +namespace giada::v { class geMainMenu : public gePack { public: geMainMenu(int x, int y); - private: +private: static void cb_file(Fl_Widget* /*w*/, void* p); static void cb_edit(Fl_Widget* /*w*/, void* p); void cb_file(); @@ -51,7 +49,6 @@ public: geButton* config; geButton* about; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/mainWindow/mainTimer.cpp b/src/gui/elems/mainWindow/mainTimer.cpp index 3faef61..59552ba 100644 --- a/src/gui/elems/mainWindow/mainTimer.cpp +++ b/src/gui/elems/mainWindow/mainTimer.cpp @@ -25,21 +25,16 @@ * -------------------------------------------------------------------------- */ #include "mainTimer.h" -#include "core/clock.h" #include "core/const.h" #include "core/graphics.h" #include "glue/events.h" +#include "glue/layout.h" #include "glue/main.h" -#include "gui/dialogs/beatsInput.h" -#include "gui/dialogs/bpmInput.h" -#include "gui/dialogs/mainWindow.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/choice.h" #include "utils/gui.h" #include "utils/string.h" -extern giada::v::gdMainWindow* G_MainWin; - namespace giada::v { geMainTimer::geMainTimer(int x, int y) @@ -91,14 +86,14 @@ void geMainTimer::cb_divider(Fl_Widget* /*w*/, void* p) { ((geMainTimer*)p)->cb_ void geMainTimer::cb_bpm() { - u::gui::openSubWindow(G_MainWin, new gdBpmInput(m_bpm.label()), WID_BPM); + c::layout::openBpmWindow(m_bpm.label()); } /* -------------------------------------------------------------------------- */ void geMainTimer::cb_meter() { - u::gui::openSubWindow(G_MainWin, new gdBeatsInput(), WID_BEATS); + c::layout::openBeatsWindow(m_timer.beats, m_timer.bars); } /* -------------------------------------------------------------------------- */ diff --git a/src/gui/elems/mainWindow/mainTransport.cpp b/src/gui/elems/mainWindow/mainTransport.cpp index 441aa7a..fef9729 100644 --- a/src/gui/elems/mainWindow/mainTransport.cpp +++ b/src/gui/elems/mainWindow/mainTransport.cpp @@ -24,20 +24,12 @@ * * -------------------------------------------------------------------------- */ -#include "mainTransport.h" -#include "core/clock.h" +#include "gui/elems/mainWindow/mainTransport.h" #include "core/conf.h" #include "core/const.h" #include "core/graphics.h" -#include "core/mixer.h" -#include "core/mixerHandler.h" -#include "core/recManager.h" -#include "core/sequencer.h" #include "glue/events.h" #include "glue/main.h" -#include "gui/elems/basics/box.h" -#include "gui/elems/basics/button.h" -#include "gui/elems/basics/statusButton.h" namespace giada::v { @@ -110,11 +102,13 @@ geMainTransport::geMainTransport(int x, int y) void geMainTransport::refresh() { - m_play.setStatus(m::clock::isRunning()); - m_recAction.setStatus(m::recManager::isRecordingAction()); - m_recInput.setStatus(m::recManager::isRecordingInput()); - m_metronome.setStatus(m::sequencer::isMetronomeOn()); - m_recTriggerMode.setStatus(m::conf::conf.recTriggerMode == RecTriggerMode::SIGNAL); - m_inputRecMode.setStatus(m::conf::conf.inputRecMode == InputRecMode::FREE); + c::main::Transport transport = c::main::getTransport(); + + m_play.setStatus(transport.isRunning); + m_recAction.setStatus(transport.isRecordingAction); + m_recInput.setStatus(transport.isRecordingInput); + m_metronome.setStatus(transport.isMetronomeOn); + m_recTriggerMode.setStatus(transport.recTriggerMode == RecTriggerMode::SIGNAL); + m_inputRecMode.setStatus(transport.inputRecMode == InputRecMode::FREE); } } // namespace giada::v diff --git a/src/gui/elems/plugin/pluginBrowser.cpp b/src/gui/elems/plugin/pluginBrowser.cpp index 890c9b6..06bb2c0 100644 --- a/src/gui/elems/plugin/pluginBrowser.cpp +++ b/src/gui/elems/plugin/pluginBrowser.cpp @@ -26,20 +26,18 @@ #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 -namespace giada -{ -namespace v +namespace giada::v { gePluginBrowser::gePluginBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) +, m_widths{0} { box(G_CUSTOM_BORDER_BOX); textsize(G_GUI_FONT_SIZE_BASE); @@ -61,7 +59,7 @@ gePluginBrowser::gePluginBrowser(int x, int y, int w, int h) computeWidths(); - column_widths(widths); + column_widths(m_widths); column_char('\t'); // tabs as column delimiters refresh(); @@ -78,18 +76,19 @@ void gePluginBrowser::refresh() add("NAME\tMANUFACTURER\tCATEGORY\tFORMAT\tUID"); add("---\t---\t---\t---\t---"); - for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++) + for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo()) { - m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i); - std::string m = m::pluginManager::doesPluginExist(pi.uid) ? "" : "@-"; - std::string s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m + - pi.category + "\t" + m + pi.format + "\t" + m + pi.uid; - add(s.c_str()); - } + std::string s; + if (pi.isKnown) + { + std::string m = pi.exists ? "" : "@-"; + + s = m + pi.name + "\t" + m + pi.manufacturerName + "\t" + m + + pi.category + "\t" + m + pi.format + "\t" + m + pi.uid; + } + else + std::string s = "?\t?\t?\t?\t? " + pi.uid + " ?"; - for (int i = 0; i < m::pluginManager::countUnknownPlugins(); i++) - { - std::string s = "?\t?\t?\t?\t? " + m::pluginManager::getUnknownPluginInfo(i) + " ?"; add(s.c_str()); } } @@ -99,26 +98,24 @@ void gePluginBrowser::refresh() void gePluginBrowser::computeWidths() { int w0, w1, w3; - for (int i = 0; i < m::pluginManager::countAvailablePlugins(); i++) + for (m::PluginManager::PluginInfo pi : c::plugin::getPluginsInfo()) { - m::pluginManager::PluginInfo pi = m::pluginManager::getAvailablePluginInfo(i); - w0 = (int)fl_width(pi.name.c_str()); - w1 = (int)fl_width(pi.manufacturerName.c_str()); - w3 = (int)fl_width(pi.format.c_str()); - if (w0 > widths[0]) - widths[0] = w0; - if (w1 > widths[1]) - widths[1] = w1; - if (w3 > widths[3]) - widths[3] = w3; + w0 = static_cast(fl_width(pi.name.c_str())); + w1 = static_cast(fl_width(pi.manufacturerName.c_str())); + w3 = static_cast(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(fl_width("CATEGORY") + 60); - widths[3] += 60; - widths[4] = 0; + m_widths[0] += 60; + m_widths[1] += 60; + m_widths[2] = static_cast(fl_width("CATEGORY") + 60); + m_widths[3] += 60; + m_widths[4] = 0; } -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/plugin/pluginBrowser.h b/src/gui/elems/plugin/pluginBrowser.h index c40fd27..e039728 100644 --- a/src/gui/elems/plugin/pluginBrowser.h +++ b/src/gui/elems/plugin/pluginBrowser.h @@ -31,9 +31,7 @@ #include -namespace giada -{ -namespace v +namespace giada::v { class gePluginBrowser : public Fl_Browser { @@ -42,13 +40,12 @@ public: void refresh(); - private: +private: void computeWidths(); - int widths[5] = {0}; + int m_widths[5]; }; -} // namespace v -} // namespace giada +} // namespace giada::v #endif diff --git a/src/gui/elems/plugin/pluginElement.cpp b/src/gui/elems/plugin/pluginElement.cpp index ce8caa4..dd05266 100644 --- a/src/gui/elems/plugin/pluginElement.cpp +++ b/src/gui/elems/plugin/pluginElement.cpp @@ -159,24 +159,25 @@ void gePluginElement::cb_openPluginWindow() /* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent window 'add plugin'. */ - int pwid = m_plugin.id + 1; + const int pwid = m_plugin.id + 1; gdWindow* parent = static_cast(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); } /* -------------------------------------------------------------------------- */ diff --git a/src/gui/elems/sampleEditor/pitchTool.cpp b/src/gui/elems/sampleEditor/pitchTool.cpp index 023f557..bfeb6d2 100644 --- a/src/gui/elems/sampleEditor/pitchTool.cpp +++ b/src/gui/elems/sampleEditor/pitchTool.cpp @@ -26,7 +26,6 @@ * -------------------------------------------------------------------------- */ #include "pitchTool.h" -#include "core/clock.h" #include "core/const.h" #include "core/graphics.h" #include "core/model/model.h" @@ -40,9 +39,7 @@ #include "utils/string.h" #include -namespace giada -{ -namespace v +namespace giada::v { gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y) : gePack(x, y, Direction::HORIZONTAL) @@ -141,7 +138,7 @@ void gePitchTool::cb_setPitchDouble() void gePitchTool::cb_setPitchToBar() { - c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInBar(), + c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInBar(), Thread::MAIN); } @@ -149,7 +146,7 @@ void gePitchTool::cb_setPitchToBar() void gePitchTool::cb_setPitchToSong() { - c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m::clock::getFramesInLoop(), + c::events::setChannelPitch(m_data->channelId, m_data->end / (float)m_data->getFramesInLoop(), Thread::MAIN); } @@ -159,5 +156,4 @@ void gePitchTool::cb_resetPitch() { c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/sampleEditor/pitchTool.h b/src/gui/elems/sampleEditor/pitchTool.h index 9a0da80..e410fd8 100644 --- a/src/gui/elems/sampleEditor/pitchTool.h +++ b/src/gui/elems/sampleEditor/pitchTool.h @@ -37,6 +37,7 @@ namespace giada::c::sampleEditor { struct Data; } + namespace giada::v { class gePitchTool : public gePack @@ -47,7 +48,7 @@ public: void rebuild(const c::sampleEditor::Data& d); void update(float v, bool isDial = false); - private: +private: static void cb_setPitch(Fl_Widget* /*w*/, void* p); static void cb_setPitchToBar(Fl_Widget* /*w*/, void* p); static void cb_setPitchToSong(Fl_Widget* /*w*/, void* p); diff --git a/src/gui/elems/sampleEditor/waveTools.cpp b/src/gui/elems/sampleEditor/waveTools.cpp index 4f06c06..607eb51 100644 --- a/src/gui/elems/sampleEditor/waveTools.cpp +++ b/src/gui/elems/sampleEditor/waveTools.cpp @@ -31,6 +31,7 @@ #include "glue/sampleEditor.h" #include "gui/dialogs/sampleEditor.h" #include "gui/elems/basics/boxtypes.h" +#include "utils/gui.h" #include "waveform.h" #include #include @@ -116,7 +117,7 @@ void menuCallback_(Fl_Widget* w, void* v) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -geWaveTools::geWaveTools(int x, int y, int w, int h) +geWaveTools::geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal) : Fl_Scroll(x, y, w, h, nullptr) , m_data(nullptr) { @@ -126,7 +127,7 @@ geWaveTools::geWaveTools(int x, int y, int w, int h) hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); - waveform = new v::geWaveform(x, y, w, h - 24); + waveform = new v::geWaveform(x, y, w, h - 24, gridEnabled, gridVal); } /* -------------------------------------------------------------------------- */ @@ -197,19 +198,19 @@ int geWaveTools::handle(int e) void geWaveTools::openMenu() { Fl_Menu_Item menu[] = { - {"Cut", 0, menuCallback_, (void*)Menu::CUT, 0, 0, 0, 0, 0}, - {"Copy", 0, menuCallback_, (void*)Menu::COPY, 0, 0, 0, 0, 0}, - {"Paste", 0, menuCallback_, (void*)Menu::PASTE, 0, 0, 0, 0, 0}, - {"Trim", 0, menuCallback_, (void*)Menu::TRIM, 0, 0, 0, 0, 0}, - {"Silence", 0, menuCallback_, (void*)Menu::SILENCE, 0, 0, 0, 0, 0}, - {"Reverse", 0, menuCallback_, (void*)Menu::REVERSE, 0, 0, 0, 0, 0}, - {"Normalize", 0, menuCallback_, (void*)Menu::NORMALIZE, 0, 0, 0, 0, 0}, - {"Fade in", 0, menuCallback_, (void*)Menu::FADE_IN, 0, 0, 0, 0, 0}, - {"Fade out", 0, menuCallback_, (void*)Menu::FADE_OUT, 0, 0, 0, 0, 0}, - {"Smooth edges", 0, menuCallback_, (void*)Menu::SMOOTH_EDGES, 0, 0, 0, 0, 0}, - {"Set begin/end here", 0, menuCallback_, (void*)Menu::SET_BEGIN_END, 0, 0, 0, 0, 0}, - {"Copy to new channel", 0, menuCallback_, (void*)Menu::TO_NEW_CHANNEL, 0, 0, 0, 0, 0}, - {0}}; + u::gui::makeMenuItem("Cut", menuCallback_, (void*)Menu::CUT), + u::gui::makeMenuItem("Copy", menuCallback_, (void*)Menu::COPY), + u::gui::makeMenuItem("Paste", menuCallback_, (void*)Menu::PASTE), + u::gui::makeMenuItem("Trim", menuCallback_, (void*)Menu::TRIM), + u::gui::makeMenuItem("Silence", menuCallback_, (void*)Menu::SILENCE), + u::gui::makeMenuItem("Reverse", menuCallback_, (void*)Menu::REVERSE), + u::gui::makeMenuItem("Normalize", menuCallback_, (void*)Menu::NORMALIZE), + u::gui::makeMenuItem("Fade in", menuCallback_, (void*)Menu::FADE_IN), + u::gui::makeMenuItem("Fade out", menuCallback_, (void*)Menu::FADE_OUT), + u::gui::makeMenuItem("Smooth edges", menuCallback_, (void*)Menu::SMOOTH_EDGES), + u::gui::makeMenuItem("Set begin/end here", menuCallback_, (void*)Menu::SET_BEGIN_END), + u::gui::makeMenuItem("Copy to new channel", menuCallback_, (void*)Menu::TO_NEW_CHANNEL), + {}}; if (!waveform->isSelected()) { diff --git a/src/gui/elems/sampleEditor/waveTools.h b/src/gui/elems/sampleEditor/waveTools.h index 53387fd..f17a8a7 100644 --- a/src/gui/elems/sampleEditor/waveTools.h +++ b/src/gui/elems/sampleEditor/waveTools.h @@ -33,13 +33,14 @@ namespace giada::c::sampleEditor { struct Data; } + namespace giada::v { class geWaveform; class geWaveTools : public Fl_Scroll { public: - geWaveTools(int x, int y, int w, int h); + geWaveTools(int x, int y, int w, int h, bool gridEnabled, int gridVal); void resize(int x, int y, int w, int h) override; int handle(int e) override; @@ -61,7 +62,7 @@ public: v::geWaveform* waveform; - private: +private: void openMenu(); const c::sampleEditor::Data* m_data; diff --git a/src/gui/elems/sampleEditor/waveform.cpp b/src/gui/elems/sampleEditor/waveform.cpp index c92f48c..98b0e2a 100644 --- a/src/gui/elems/sampleEditor/waveform.cpp +++ b/src/gui/elems/sampleEditor/waveform.cpp @@ -24,8 +24,7 @@ * * -------------------------------------------------------------------------- */ -#include "waveform.h" -#include "core/conf.h" +#include "gui/elems/sampleEditor/waveform.h" #include "core/const.h" #include "core/mixer.h" #include "core/model/model.h" @@ -42,11 +41,9 @@ #include #include -namespace giada +namespace giada::v { -namespace v -{ -geWaveform::geWaveform(int x, int y, int w, int h) +geWaveform::geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal) : Fl_Widget(x, y, w, h, nullptr) , m_selection{} , m_data(nullptr) @@ -62,8 +59,8 @@ geWaveform::geWaveform(int x, int y, int w, int h) { m_waveform.size = w; - m_grid.snap = m::conf::conf.sampleEditorGridOn; - m_grid.level = m::conf::conf.sampleEditorGridVal; + m_grid.snap = gridEnabled; + m_grid.level = gridVal; } /* -------------------------------------------------------------------------- */ @@ -290,7 +287,7 @@ void geWaveform::draw() fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas /* Draw things from 'from' (offset driven by the scrollbar) to 'to' (width of - parent window). We don't draw the entire waveform, only the visibile part. */ + parent window). We don't draw the entire waveform, only the visible part. */ int from = abs(x() - parent()->x()); int to = from + parent()->w(); @@ -583,6 +580,10 @@ void geWaveform::clearSelection() /* -------------------------------------------------------------------------- */ +#ifdef G_OS_WINDOWS +#undef IN +#endif + void geWaveform::setZoom(Zoom z) { if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR)) @@ -679,5 +680,4 @@ void geWaveform::selectAll() m_selection.b = m_data->waveSize - 1; redraw(); } -} // namespace v -} // namespace giada +} // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/sampleEditor/waveform.h b/src/gui/elems/sampleEditor/waveform.h index b184747..392f809 100644 --- a/src/gui/elems/sampleEditor/waveform.h +++ b/src/gui/elems/sampleEditor/waveform.h @@ -36,6 +36,7 @@ namespace giada::c::sampleEditor { struct Data; } + namespace giada::v { class geWaveform : public Fl_Widget @@ -52,7 +53,7 @@ public: OUT }; - geWaveform(int x, int y, int w, int h); + geWaveform(int x, int y, int w, int h, bool gridEnabled, int gridVal); void draw() override; int handle(int e) override; @@ -105,7 +106,7 @@ public: void setWaveId(ID /*id*/){/* TODO m_waveId = id;*/}; - private: +private: static const int FLAG_WIDTH = 20; static const int FLAG_HEIGHT = 20; static const int BORDER = 8; // window border <-> widget border diff --git a/src/gui/elems/soundMeter.cpp b/src/gui/elems/soundMeter.cpp index 05777af..a1cc905 100644 --- a/src/gui/elems/soundMeter.cpp +++ b/src/gui/elems/soundMeter.cpp @@ -28,6 +28,7 @@ #include "core/const.h" #include "core/kernelAudio.h" #include "core/types.h" +#include "gui/drawing.h" #include "utils/math.h" #include #include @@ -48,10 +49,26 @@ Pixel dbToPx_(float db, Pixel max) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ +float geSoundMeter::Meter::compute(float peak) +{ + /* dBFS (full scale) calculation, plus decay of -2dB per call. */ + + float dbLevelCur = u::math::linearToDB(std::fabs(peak)); + + if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE) + dbLevelCur = m_dbLevelOld - 2.0f; + + m_dbLevelOld = dbLevelCur; + + return dbLevelCur; +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l) : Fl_Box(x, y, w, h, l) -, mixerPeak(0.0f) -, m_dbLevelOld(0.0f) { } @@ -59,26 +76,28 @@ geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l) void geSoundMeter::draw() { - fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); - - /* Compute peak level on 0.0 -> 1.0 scale. 1.0 is considered clip. */ + const geompp::Rect outline(x(), y(), w(), h()); + const geompp::Rect body(outline.reduced(1)); - const bool clip = std::fabs(mixerPeak) >= 1.0f ? true : false; + drawRect(outline, G_COLOR_GREY_4); - /* dBFS (full scale) calculation, plus decay of -2dB per frame. */ + if (!ready) + { + drawRectf(body, G_COLOR_BLUE); + return; + } - float dbLevelCur = u::math::linearToDB(std::fabs(mixerPeak)); - - if (dbLevelCur < m_dbLevelOld && m_dbLevelOld > -G_MIN_DB_SCALE) - dbLevelCur = m_dbLevelOld - 2.0f; - - m_dbLevelOld = dbLevelCur; + drawRectf(body, G_COLOR_GREY_2); // Cleanup - /* Paint the meter on screen. */ + const float dbL = m_left.compute(peak.left); + const float dbR = m_right.compute(peak.right); + const int colorL = std::fabs(peak.left) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4; + const int colorR = std::fabs(peak.right) > 1.0f ? G_COLOR_BLUE : G_COLOR_GREY_4; - const int bodyCol = clip || !m::kernelAudio::isReady() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4; + const geompp::Rect bodyL(body.withTrimmedBottom(h() / 2)); + const geompp::Rect bodyR(body.withTrimmedTop(h() / 2)); - fl_rectf(x() + 1, y() + 1, w() - 2, h() - 2, G_COLOR_GREY_2); - fl_rectf(x() + 1, y() + 1, dbToPx_(dbLevelCur, w()), h() - 2, bodyCol); + drawRectf(bodyL.withW(dbToPx_(dbL, w() - 2)), colorL); + drawRectf(bodyR.withW(dbToPx_(dbR, w() - 2)), colorR); } } // namespace giada::v \ No newline at end of file diff --git a/src/gui/elems/soundMeter.h b/src/gui/elems/soundMeter.h index 4669326..2949835 100644 --- a/src/gui/elems/soundMeter.h +++ b/src/gui/elems/soundMeter.h @@ -27,6 +27,7 @@ #ifndef GE_SOUND_METER_H #define GE_SOUND_METER_H +#include "core/types.h" #include namespace giada::v @@ -38,10 +39,21 @@ public: void draw() override; - float mixerPeak; // peak from mixer + Peak peak; // Peak from Mixer + bool ready; // Kernel state private: - float m_dbLevelOld; + class Meter + { + public: + float compute(float peak); + + private: + float m_dbLevelOld = 0.0f; + }; + + Meter m_left; + Meter m_right; }; } // namespace giada::v diff --git a/src/gui/model.cpp b/src/gui/model.cpp deleted file mode 100644 index ba21638..0000000 --- a/src/gui/model.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#include "model.h" -#include "core/patch.h" -#include "gui/dialogs/mainWindow.h" -#include "gui/elems/mainWindow/keyboard/channel.h" -#include "gui/elems/mainWindow/keyboard/column.h" -#include "gui/elems/mainWindow/keyboard/keyboard.h" -#include "utils/log.h" - -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada::v::model -{ -void store(m::patch::Patch& patch) -{ - G_MainWin->keyboard->forEachColumn([&](const geColumn& c) { - patch.columns.push_back({c.id, c.w()}); - }); -} - -/* -------------------------------------------------------------------------- */ - -void load(const m::patch::Patch& patch) -{ - G_MainWin->keyboard->layout.clear(); - for (const m::patch::Column& col : patch.columns) - G_MainWin->keyboard->layout.push_back({col.id, col.width}); -} -} // namespace giada::v::model diff --git a/src/gui/model.h b/src/gui/model.h deleted file mode 100644 index 97e4986..0000000 --- a/src/gui/model.h +++ /dev/null @@ -1,40 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - -#ifndef G_V_MODEL_H -#define G_V_MODEL_H - -namespace giada::m::patch -{ -struct Patch; -} -namespace giada::v::model -{ -void store(m::patch::Patch& patch); -void load(const m::patch::Patch& patch); -} // namespace giada::v::model - -#endif \ No newline at end of file diff --git a/src/gui/types.h b/src/gui/types.h new file mode 100644 index 0000000..4662088 --- /dev/null +++ b/src/gui/types.h @@ -0,0 +1,39 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#ifndef G_V_TYPES_H +#define G_V_TYPES_H + +namespace giada::v +{ +enum class Direction +{ + HORIZONTAL, + VERTICAL +}; +} // namespace giada::v + +#endif \ No newline at end of file diff --git a/src/gui/ui.cpp b/src/gui/ui.cpp new file mode 100644 index 0000000..e7d2897 --- /dev/null +++ b/src/gui/ui.cpp @@ -0,0 +1,260 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) +#include // 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(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT, "", argc, argv, engine.conf.data); + mainWindow->resize(engine.conf.data.mainWindowX, engine.conf.data.mainWindowY, engine.conf.data.mainWindowW, + engine.conf.data.mainWindowH); + + setMainWindowTitle(engine.patch.data.name == "" ? G_DEFAULT_PATCH_NAME : engine.patch.data.name); + + m_updater.init(engine.model); + + if (engine.kernelAudio.isReady()) + rebuildStaticWidgets(); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::reset() +{ + setMainWindowTitle(G_DEFAULT_PATCH_NAME); + rebuildStaticWidgets(); + closeAllSubwindows(); + mainWindow->clearKeyboard(); + mainWindow->rebuild(); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::shutdown() +{ + mainWindow.reset(); + m_updater.close(); + + u::log::print("[ui] All windows closed\n"); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::refresh() +{ + /* Update dynamic elements inside main window: in and out meters, beat meter + and each channel. */ + + mainWindow->refresh(); + + /* Compute timer for blinker. */ + + m_blinker = (m_blinker + 1) % 12; // TODO magic numbers + + /* Refresh Sample Editor and Action Editor for dynamic playhead. */ + + refreshSubWindow(WID_SAMPLE_EDITOR); + refreshSubWindow(WID_ACTION_EDITOR); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::rebuild() +{ + mainWindow->rebuild(); + rebuildSubWindow(WID_FX_LIST); + rebuildSubWindow(WID_SAMPLE_EDITOR); + rebuildSubWindow(WID_ACTION_EDITOR); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::rebuildSubWindow(int wid) +{ + v::gdWindow* w = getSubwindow(*mainWindow.get(), wid); + if (w != nullptr) // If its open + w->rebuild(); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::refreshSubWindow(int wid) +{ + v::gdWindow* w = getSubwindow(*mainWindow.get(), wid); + if (w != nullptr) // If its open + w->refresh(); +} + +/* -------------------------------------------------------------------------- */ + +v::gdWindow* Ui::getSubwindow(v::gdWindow& parent, int wid) +{ + return parent.hasWindow(wid) ? parent.getChild(wid) : nullptr; +} + +/* -------------------------------------------------------------------------- */ + +void Ui::openSubWindow(v::gdWindow& parent, v::gdWindow* child, int wid) +{ + if (parent.hasWindow(wid)) + { + u::log::print("[GU] parent has subwindow with id=%d, deleting\n", wid); + parent.delSubWindow(wid); + } + child->setId(wid); + parent.addSubWindow(child); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::closeSubWindow(int wid) +{ + mainWindow->delSubWindow(wid); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::closeAllSubwindows() +{ + mainWindow->delSubWindow(WID_ACTION_EDITOR); + mainWindow->delSubWindow(WID_SAMPLE_EDITOR); + mainWindow->delSubWindow(WID_FX_LIST); + mainWindow->delSubWindow(WID_FX); +} + +/* -------------------------------------------------------------------------- */ + +void Ui::setMainWindowTitle(const std::string& s) +{ + std::string out = std::string(G_APP_NAME) + " - " + s; + mainWindow->copy_label(out.c_str()); +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void Ui::startJuceDispatchLoop() +{ + Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop); +} + +void Ui::stopJuceDispatchLoop() +{ + Fl::remove_timeout(juceDispatchLoop); +} + +#endif + +/* -------------------------------------------------------------------------- */ + +void Ui::rebuildStaticWidgets() +{ + mainWindow->mainIO->rebuild(); + mainWindow->mainTimer->rebuild(); +} + +/* -------------------------------------------------------------------------- */ + +#ifdef WITH_VST + +void Ui::juceDispatchLoop(void*) +{ + juce::MessageManager* mm = juce::MessageManager::getInstanceWithoutCreating(); + assert(mm != nullptr); + mm->runDispatchLoopUntil(1); + Fl::add_timeout(G_GUI_PLUGIN_RATE, juceDispatchLoop); +} + +#endif +} // namespace giada::v diff --git a/src/gui/ui.h b/src/gui/ui.h new file mode 100644 index 0000000..a23a5c1 --- /dev/null +++ b/src/gui/ui.h @@ -0,0 +1,143 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2021 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + +#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 +#include + +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 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 diff --git a/src/gui/updater.cpp b/src/gui/updater.cpp index 3ecdc76..dbb95b8 100644 --- a/src/gui/updater.cpp +++ b/src/gui/updater.cpp @@ -24,17 +24,24 @@ * * -------------------------------------------------------------------------- */ -#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 -namespace giada::v::updater +namespace giada::v { -void init() +Updater::Updater(Ui& ui) +: m_ui(ui) { - m::model::onSwap([](m::model::SwapType type) { +} + +/* -------------------------------------------------------------------------- */ + +void Updater::init(m::model::Model& model) +{ + model.onSwap = [this](m::model::SwapType type) { if (type == m::model::SwapType::NONE) return; @@ -42,25 +49,29 @@ void init() synchronization with the main one. */ Fl::lock(); - type == m::model::SwapType::HARD ? u::gui::rebuild() : u::gui::refresh(); + type == m::model::SwapType::HARD ? m_ui.rebuild() : m_ui.refresh(); Fl::unlock(); - }); + }; - Fl::add_timeout(G_GUI_REFRESH_RATE, update, nullptr); + Fl::add_timeout(G_GUI_REFRESH_RATE, update, this); } /* -------------------------------------------------------------------------- */ -void update(void* /*p*/) +void Updater::update(void* p) { static_cast(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 diff --git a/src/gui/updater.h b/src/gui/updater.h index d2dca2c..e53de90 100644 --- a/src/gui/updater.h +++ b/src/gui/updater.h @@ -27,11 +27,28 @@ #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 diff --git a/src/main.cpp b/src/main.cpp index b7bf05b..8584d81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,38 +24,16 @@ * * -------------------------------------------------------------------------- */ -#include "core/init.h" -#include "gui/dialogs/mainWindow.h" -#include -#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 -#include -#include -#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 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 diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp index 6d6b31e..1d15cc5 100644 --- a/src/utils/gui.cpp +++ b/src/utils/gui.cpp @@ -32,127 +32,29 @@ #elif defined(__linux__) || defined(__FreeBSD__) #include #endif -#include "core/clock.h" #include "core/conf.h" #include "core/graphics.h" #include "core/mixer.h" #include "core/mixerHandler.h" #include "core/plugins/pluginHost.h" +#include "core/sequencer.h" #include "gui.h" #include "gui/dialogs/actionEditor/baseActionEditor.h" -#include "gui/dialogs/mainWindow.h" #include "gui/dialogs/sampleEditor.h" #include "gui/dialogs/warnings.h" #include "gui/dialogs/window.h" -#include "gui/elems/mainWindow/sequencer.h" #include "gui/elems/mainWindow/keyboard/channel.h" #include "gui/elems/mainWindow/keyboard/keyboard.h" #include "gui/elems/mainWindow/mainIO.h" #include "gui/elems/mainWindow/mainTimer.h" #include "gui/elems/mainWindow/mainTransport.h" +#include "gui/elems/mainWindow/sequencer.h" #include "gui/elems/sampleEditor/waveTools.h" #include "log.h" #include "string.h" -extern giada::v::gdMainWindow* G_MainWin; - -namespace giada -{ -namespace u -{ -namespace gui -{ -namespace -{ -int blinker_ = 0; -} // namespace - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - -void rebuildSubWindow(int wid) -{ - v::gdWindow* w = getSubwindow(G_MainWin, wid); - if (w != nullptr) // If its open - w->rebuild(); -} - -/* -------------------------------------------------------------------------- */ - -void refreshSubWindow(int wid) -{ - v::gdWindow* w = getSubwindow(G_MainWin, wid); - if (w != nullptr) // If its open - w->refresh(); -} - -/* -------------------------------------------------------------------------- */ - -void refresh() -{ - /* Update dynamic elements inside main window: in and out meters, beat meter - and each channel. */ - - G_MainWin->refresh(); - - /* Compute timer for blinker. */ - - blinker_ = (blinker_ + 1) % 12; - - /* Refresh Sample Editor (if open) for dynamic play head. */ - - refreshSubWindow(WID_SAMPLE_EDITOR); -} - -/* -------------------------------------------------------------------------- */ - -void rebuild() -{ - G_MainWin->rebuild(); - rebuildSubWindow(WID_FX_LIST); - rebuildSubWindow(WID_SAMPLE_EDITOR); - rebuildSubWindow(WID_ACTION_EDITOR); -} - -/* -------------------------------------------------------------------------- */ - -bool shouldBlink() -{ - return blinker_ > 6; -} - -/* -------------------------------------------------------------------------- */ - -void updateStaticWidgets() -{ - using namespace giada::m; - - G_MainWin->mainIO->setOutVol(mh::getOutVol()); - G_MainWin->mainIO->setInVol(mh::getInVol()); - -#ifdef WITH_VST - - // G_MainWin->mainIO->setMasterFxOutFull(pluginHost::getStack(pluginHost::StackType::MASTER_OUT).plugins.size() > 0); - // G_MainWin->mainIO->setMasterFxInFull(pluginHost::getStack(pluginHost::StackType::MASTER_IN).plugins.size() > 0); - -#endif - - G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars()); - G_MainWin->mainTimer->setBpm(clock::getBpm()); - G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue()); -} - -/* -------------------------------------------------------------------------- */ - -void updateMainWinLabel(const std::string& s) +namespace giada::u::gui { - std::string out = std::string(G_APP_NAME) + " - " + s; - G_MainWin->copy_label(out.c_str()); -} - -/* -------------------------------------------------------------------------- */ - void setFavicon(v::gdWindow* w) { #if defined(__linux__) || defined(__FreeBSD__) @@ -172,51 +74,6 @@ void setFavicon(v::gdWindow* w) /* -------------------------------------------------------------------------- */ -void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id) -{ - if (parent->hasWindow(id)) - { - u::log::print("[GU] parent has subwindow with id=%d, deleting\n", id); - parent->delSubWindow(id); - } - child->setId(id); - parent->addSubWindow(child); -} - -/* -------------------------------------------------------------------------- */ - -void refreshActionEditor() -{ - v::gdBaseActionEditor* ae = static_cast(G_MainWin->getChild(WID_ACTION_EDITOR)); - if (ae != nullptr) - ae->rebuild(); -} - -/* -------------------------------------------------------------------------- */ - -v::gdWindow* getSubwindow(v::gdWindow* parent, int id) -{ - if (parent->hasWindow(id)) - return parent->getChild(id); - else - return nullptr; -} - -/* -------------------------------------------------------------------------- */ - -void closeAllSubwindows() -{ - /* don't close WID_FILE_BROWSER, because it's the caller of this - * function */ - - G_MainWin->delSubWindow(WID_ACTION_EDITOR); - G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); - G_MainWin->delSubWindow(WID_FX_LIST); - G_MainWin->delSubWindow(WID_FX); -} - -/* -------------------------------------------------------------------------- */ - int getStringWidth(const std::string& s) { int w = 0; @@ -267,6 +124,4 @@ int centerWindowY(int h) { return (Fl::h() / 2) - (h / 2); } -} // namespace gui -} // namespace u -} // namespace giada +} // namespace giada::u::gui diff --git a/src/utils/gui.h b/src/utils/gui.h index 36dc544..afe1927 100644 --- a/src/utils/gui.h +++ b/src/utils/gui.h @@ -28,79 +28,26 @@ #define G_UTILS_GUI_H #include "core/types.h" +#include #include -namespace giada -{ -namespace v +namespace giada::v { class gdWindow; } -namespace u -{ -namespace gui -{ -/* refresh -Repaints some dynamic GUI elements. */ - -void refresh(); - -/* rebuild -Rebuilds the UI from scratch. Used when the model has changed. */ - -void rebuild(); - -/* [rebuild|refresh]SubWindow -Rebuilds or refreshes subwindow with ID 'wid' if it exists. i.e. if its open. */ - -void rebuildSubWindow(int wid); -void refreshSubWindow(int wid); - -/* shouldBlink -Return whether is time to blink something or not. This is used to make widgets -blink. */ - -bool shouldBlink(); - -/* updateStaticWidgets -Updates attributes of static widgets, i.e. those elements that don't get -automatically refreshed during the UI update loop. Useful when loading a new -patch. */ - -void updateStaticWidgets(); - -/* updateMainWinLabel -Updates the name of the main window */ - -void updateMainWinLabel(const std::string& s); +namespace giada::u::gui +{ void setFavicon(v::gdWindow* w); -void openSubWindow(v::gdWindow* parent, v::gdWindow* child, int id); - -// TODO closeSubWindow(...) - -/* refreshActionEditor -Reloads the action editor window by closing and reopening it. It's used when you -delete some actions from the mainWindow and the action editor window is open. */ - -void refreshActionEditor(); - -/* closeAllSubwindows -Closes all subwindows attached to mainWin. */ - -void closeAllSubwindows(); - -/* getSubwindow -Returns a pointer to an open subwindow, otherwise nullptr. */ - -v::gdWindow* getSubwindow(v::gdWindow* parent, int id); - /* removeFltkChars Strips special chars used by FLTK to split menus into sub-menus. */ std::string removeFltkChars(const std::string& s); +/* getStringWidth +Returns the width in pixels of a string 's'. */ + int getStringWidth(const std::string& s); /* truncate @@ -111,8 +58,14 @@ std::string truncate(const std::string& s, Pixel width); int centerWindowX(int w); int centerWindowY(int h); -} // namespace gui -} // namespace u -} // namespace giada +/* makeMenuItem +Makes a new Fl_Menu_Item at compile time. Used to initialize pop-up menus. */ + +constexpr Fl_Menu_Item makeMenuItem(const char* text, Fl_Callback* callback = nullptr, + void* data = nullptr, int flags = 0) +{ + return Fl_Menu_Item{text, 0, callback, data, flags, 0, 0, 0, 0}; +} +} // namespace giada::u::gui #endif diff --git a/src/utils/math.h b/src/utils/math.h index 906b2f0..a7c1b96 100644 --- a/src/utils/math.h +++ b/src/utils/math.h @@ -27,6 +27,7 @@ #ifndef G_UTILS_MATH_H #define G_UTILS_MATH_H +#include #include namespace giada::u::math @@ -47,6 +48,8 @@ TO map(TI x, TI a, TI b, TO w, TO z) static_assert(std::is_arithmetic_v); static_assert(std::is_arithmetic_v); + if (a == b) // Prevents division by zero (undefined behavior) + return x; return (((x - a) / (double)(b - a)) * (z - w)) + w; } diff --git a/src/utils/vector.h b/src/utils/vector.h index acfe6c6..b05959a 100644 --- a/src/utils/vector.h +++ b/src/utils/vector.h @@ -76,6 +76,14 @@ std::vector cast(const I& i) { return {i.begin(), i.end()}; } + +/* -------------------------------------------------------------------------- */ + +template +auto atOr(const Vector& v, int index, Default d) +{ + return index >= 0 && static_cast(index) < v.size() ? v[index] : d; +} } // namespace giada::u::vector #endif diff --git a/tests/actionRecorder.cpp b/tests/actionRecorder.cpp new file mode 100644 index 0000000..e0f884c --- /dev/null +++ b/tests/actionRecorder.cpp @@ -0,0 +1,69 @@ +#include "src/core/actions/actionRecorder.h" +#include "src/core/actions/action.h" +#include "src/core/actions/actions.h" +#include "src/core/const.h" +#include "src/core/model/model.h" +#include "src/core/types.h" +#include + +TEST_CASE("ActionRecorder") +{ + using namespace giada; + using namespace giada::m; + + model::Model model; + ActionRecorder ar(model); + + REQUIRE(ar.hasActions(/*ch=*/0) == false); + + SECTION("Test record") + { + const int ch = 0; + const Frame f1 = 10; + const Frame f2 = 70; + const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00); + const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00); + + const Action a1 = ar.rec(ch, f1, e1); + const Action a2 = ar.rec(ch, f2, e2); + + REQUIRE(ar.hasActions(ch) == true); + REQUIRE(a1.frame == f1); + REQUIRE(a2.frame == f2); + REQUIRE(a1.prevId == 0); + REQUIRE(a1.nextId == 0); + REQUIRE(a2.prevId == 0); + REQUIRE(a2.nextId == 0); + + SECTION("Test clear actions by channel") + { + const int ch = 1; + const Frame f1 = 100; + const Frame f2 = 200; + const MidiEvent e1 = MidiEvent(MidiEvent::NOTE_ON, 0x00, 0x00); + const MidiEvent e2 = MidiEvent(MidiEvent::NOTE_OFF, 0x00, 0x00); + + ar.rec(ch, f1, e1); + ar.rec(ch, f2, e2); + + ar.clearChannel(/*channel=*/0); + + REQUIRE(ar.hasActions(/*channel=*/0) == false); + REQUIRE(ar.hasActions(/*channel=*/1) == true); + } + + SECTION("Test clear actions by type") + { + ar.clearActions(/*channel=*/0, MidiEvent::NOTE_ON); + ar.clearActions(/*channel=*/0, MidiEvent::NOTE_OFF); + + REQUIRE(ar.hasActions(/*channel=*/0) == false); + } + + SECTION("Test clear all") + { + ar.clearAllActions(); + REQUIRE(ar.hasActions(/*channel=*/0) == false); + } + } +} diff --git a/tests/audioBuffer.cpp b/tests/audioBuffer.cpp deleted file mode 100644 index 28b1f60..0000000 --- a/tests/audioBuffer.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "../src/core/audioBuffer.h" -#include -#include - -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); - } - } -} diff --git a/tests/main.cpp b/tests/main.cpp index 14ea358..b76f5ca 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,8 +1,3 @@ #define CATCH_CONFIG_MAIN #define CATCH_CONFIG_FAST_COMPILE -#include - -/* 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 \ No newline at end of file diff --git a/tests/midiLighter.cpp b/tests/midiLighter.cpp new file mode 100644 index 0000000..d951a8a --- /dev/null +++ b/tests/midiLighter.cpp @@ -0,0 +1,82 @@ +#include "../src/core/channels/midiLighter.h" +#include "mocks/kernelMidiMock.h" +#include +#include + +TEST_CASE("MidiMapper") +{ + using namespace giada; + + m::KernelMidiMock kernelMidi; + m::MidiMapper midiMapper(kernelMidi); + m::MidiLighter midiLighter(midiMapper); + + midiMapper.currentMap = { + "test-brand", + "test-device", + {{0, "0x000000", 0, 0x000000}}, // init commands + {0, "0x000001", 0, 0x000001}, // mute on + {0, "0x000002", 0, 0x000002}, // mute off + {0, "0x000003", 0, 0x000003}, // solo on + {0, "0x000004", 0, 0x000004}, // solo off + {0, "0x000005", 0, 0x000005}, // waiting + {0, "0x000006", 0, 0x000006}, // playing + {0, "0x000007", 0, 0x000007}, // stopping + {0, "0x000008", 0, 0x000008}, // stopped + {0, "0x000009", 0, 0x000009}, // playingInaudible + }; + + midiLighter.playing = {0x000010, 0}; + midiLighter.mute = {0x000011, 0}; + midiLighter.solo = {0x000012, 0}; + + SECTION("Test initialization") + { + REQUIRE(midiLighter.enabled == false); + } + + SECTION("Test send OFF status") + { + midiLighter.sendStatus(ChannelStatus::OFF, /*audible=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000008); // Stopped + } + + SECTION("Test send WAIT status") + { + midiLighter.sendStatus(ChannelStatus::WAIT, /*audible=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000005); // Waiting + } + + SECTION("Test send ENDING status") + { + midiLighter.sendStatus(ChannelStatus::ENDING, /*audible=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000007); // Stopping + } + + SECTION("Test send PLAY status") + { + midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000006); // Playing + + midiLighter.sendStatus(ChannelStatus::PLAY, /*audible=*/false); + REQUIRE(kernelMidi.sent.back() == 0x000009); // Playing inaudible + } + + SECTION("Test send mute") + { + midiLighter.sendMute(/*isMuted=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000001); // Mute on + + midiLighter.sendMute(/*isMuted=*/false); + REQUIRE(kernelMidi.sent.back() == 0x000002); // Mute off + } + + SECTION("Test send solo") + { + midiLighter.sendSolo(/*isSoloed=*/true); + REQUIRE(kernelMidi.sent.back() == 0x000003); // Solo on + + midiLighter.sendSolo(/*isSoloed=*/false); + REQUIRE(kernelMidi.sent.back() == 0x000004); // Solo off + } +} diff --git a/tests/mocks/kernelMidiMock.h b/tests/mocks/kernelMidiMock.h new file mode 100644 index 0000000..8929b1a --- /dev/null +++ b/tests/mocks/kernelMidiMock.h @@ -0,0 +1,18 @@ +#ifndef G_TESTS_KERNELMIDI_MOCK_H +#define G_TESTS_KERNELMIDI_MOCK_H + +namespace giada::m +{ +class KernelMidiMock +{ +public: + void send(uint32_t s) + { + sent.push_back(s); + } + + std::vector sent; +}; +} // namespace giada::m + +#endif \ No newline at end of file diff --git a/tests/recorder.cpp b/tests/recorder.cpp deleted file mode 100644 index 8f6ec2f..0000000 --- a/tests/recorder.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "../src/core/recorder.h" -#include "../src/core/action.h" -#include "../src/core/const.h" -#include "../src/core/types.h" -#include - -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); - } - } -} diff --git a/tests/waveManager.cpp b/tests/waveManager.cpp index 94c3270..bd32731 100644 --- a/tests/waveManager.cpp +++ b/tests/waveManager.cpp @@ -15,11 +15,13 @@ using namespace giada::m; TEST_CASE("waveManager") { /* Each SECTION the TEST_CASE is executed from the start. Any code between - this comment and the first SECTION macro is exectuted before each SECTION. */ + this comment and the first SECTION macro is executed before each SECTION. */ + + WaveManager waveManager; SECTION("test creation") { - waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav", + WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav", /*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR); REQUIRE(res.status == G_RES_OK); @@ -31,7 +33,7 @@ TEST_CASE("waveManager") SECTION("test recording") { - std::unique_ptr wave = waveManager::createEmpty(G_BUFFER_SIZE, + std::unique_ptr wave = waveManager.createEmpty(G_BUFFER_SIZE, G_MAX_IO_CHANS, G_SAMPLE_RATE, "test.wav"); REQUIRE(wave->getRate() == G_SAMPLE_RATE); @@ -43,11 +45,11 @@ TEST_CASE("waveManager") SECTION("test resampling") { - waveManager::Result res = waveManager::createFromFile(TEST_RESOURCES_DIR "test.wav", + WaveManager::Result res = waveManager.createFromFile(TEST_RESOURCES_DIR "test.wav", /*ID=*/0, /*sampleRate=*/G_SAMPLE_RATE, /*quality=*/SRC_LINEAR); int oldSize = res.wave->getBuffer().countFrames(); - waveManager::resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2); + waveManager.resample(*res.wave.get(), 1, G_SAMPLE_RATE * 2); REQUIRE(res.wave->getRate() == G_SAMPLE_RATE * 2); REQUIRE(res.wave->getBuffer().countFrames() == oldSize * 2);