--------------------------------------------------------------------------------
-0.15.2 ---
+0.15.3 --- 2018 . 12 . 24
+- Action recorder refactoring
+- Optional midimap parameters (thank you @tomek-szczesny)
+- Support for "inaudible" MIDI lightning events (thank you @tomek-szczesny)
+- Build AppImage for Linux on Travis CI instance
+- Huge optimization of the AppImage binary file
+- Fix Action Editor repaint on min/max zoom levels
+- "Resize recording" flag has been removed
+- Change text labels for channel operations
+- Smarter column assignment while loading a patch/project
+- Fix wrong resizer bar width between Action Editor widgets when zooming
+- Can't display custom channel name in Sample Channel (fixed)
+- Fix crash when cloning Sample Channel with audio data in it
+- Clone channel doesn't clone channel name (fix #219)
+
+
+0.15.2 --- 2018 . 09 . 05
- New sample-accurate Action Editor
- New MIDI Velocity Editor widget
- Ability to move MIDI events vertically in piano roll (i.e. change note)
src/core/const.h \
src/core/types.h \
src/core/range.h \
+ src/core/action.h \
src/core/channel.h \
src/core/channel.cpp \
src/core/sampleChannel.h \
src/core/conf.cpp \
src/core/kernelAudio.h \
src/core/kernelAudio.cpp \
- src/core/pluginHost.h \
+ src/core/pluginHost.h \
src/core/pluginHost.cpp \
src/core/mixerHandler.h \
src/core/mixerHandler.cpp \
src/core/graphics.cpp \
src/core/patch.h \
src/core/patch.cpp \
+ src/core/recorderHandler.h \
+ src/core/recorderHandler.cpp \
src/core/recorder.h \
src/core/recorder.cpp \
src/core/mixer.h \
src/glue/recorder.cpp \
src/glue/sampleEditor.h \
src/glue/sampleEditor.cpp \
- src/gui/dialogs/window.h \
+ src/glue/actionEditor.h \
+ src/glue/actionEditor.cpp \
+ src/gui/dialogs/window.h \
src/gui/dialogs/window.cpp \
src/gui/dialogs/gd_keyGrabber.h \
src/gui/dialogs/gd_keyGrabber.cpp \
src/gui/dialogs/bpmInput.cpp \
src/gui/dialogs/channelNameInput.h \
src/gui/dialogs/channelNameInput.cpp \
- src/gui/dialogs/gd_config.h \
+ src/gui/dialogs/gd_config.h \
src/gui/dialogs/gd_config.cpp \
src/gui/dialogs/gd_devInfo.h \
- src/gui/dialogs/gd_devInfo.cpp \
- src/gui/dialogs/pluginList.h \
+ src/gui/dialogs/gd_devInfo.cpp \
+ src/gui/dialogs/pluginList.h \
src/gui/dialogs/pluginList.cpp \
src/gui/dialogs/pluginWindow.h \
src/gui/dialogs/pluginWindow.cpp \
src/utils/string.cpp \
src/deps/rtaudio-mod/RtAudio.h \
src/deps/rtaudio-mod/RtAudio.cpp
-sourcesTests = \
+sourcesTests = \
tests/main.cpp \
tests/conf.cpp \
tests/wave.cpp \
ldAdd += -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \
-lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \
- -lfreetype
+ -lfreetype -lfontconfig -lXrender -lXfixes -lXcursor -lXinerama
endif
-# Giada - Your Hardcore Loopmachine
+<p align="center">
+ <img src="https://raw.githubusercontent.com/monocasual/giada/master/extras/giada-logotype.png" alt="Giada - Your Hardcore Loop Machine">
+</p>
-Official website: http://www.giadamusic.com | Travis CI status: [](https://travis-ci.org/monocasual/giada)
+<p align="center">
+<strong>Giada - Your Hardcore Loop Machine</strong> | Official website: <a href="https://www.giadamusic.com">giadamusic.com</a> | Travis CI status: <a href="https://travis-ci.org/monocasual/giada"><img src="https://travis-ci.org/monocasual/giada.svg?branch=master" alt="Build status"></a>
+</p>
## What is Giada?
Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets.
-➔➔➔ [See Giada in action!](http://www.youtube.com/user/GiadaLoopMachine)
+<p align="center">
+✦✦✦ <a href="http://www.youtube.com/user/GiadaLoopMachine">See Giada in action!</a> ✦✦✦
+</p>

--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_ACTION_H
+#define G_ACTION_H
+
+
+#include "types.h"
+#include "midiEvent.h"
+
+
+namespace giada {
+namespace m
+{
+struct Action
+{
+ int id; // For persistence only
+ int channel;
+ Frame frame;
+ MidiEvent event;
+ int pluginIndex;
+ int pluginParam;
+ const Action* prev;
+ const Action* next;
+
+ bool isVolumeEnvelope() const
+ {
+ return event.getStatus() == MidiEvent::ENVELOPE && pluginIndex == -1;
+ }
+};
+
+}} // giada::m::
+
+#endif
\ No newline at end of file
#include "wave.h"
#include "mixer.h"
#include "mixerHandler.h"
+#include "recorderHandler.h"
#include "conf.h"
#include "patch.h"
#include "waveFx.h"
using std::string;
-using namespace giada;
-using namespace giada::m;
+namespace giada {
+namespace m
+{
Channel::Channel(ChannelType type, ChannelStatus status, int bufferSize)
: guiChannel (nullptr),
type (type),
void Channel::copy(const Channel* src, pthread_mutex_t* pluginMutex)
{
- using namespace giada::m;
-
key = src->key;
volume = src->volume;
volume_i = src->volume_i;
volume_d = src->volume_d;
+ name = src->name;
pan = src->pan;
mute = src->mute;
solo = src->solo;
midiOutLmute = src->midiOutLmute;
midiOutLsolo = src->midiOutLsolo;
- /* clone plugins */
-
#ifdef WITH_VST
- for (unsigned i=0; i<src->plugins.size(); i++)
- pluginHost::clonePlugin(src->plugins.at(i), pluginHost::CHANNEL,
- pluginMutex, this);
+ for (Plugin* plugin : src->plugins)
+ pluginHost::clonePlugin(plugin, pluginHost::CHANNEL, pluginMutex, this);
#endif
- /* clone actions */
-
- for (unsigned i=0; i<recorder::global.size(); i++) {
- for (unsigned k=0; k<recorder::global.at(i).size(); k++) {
- recorder::action* a = recorder::global.at(i).at(k);
- if (a->chan == src->index) {
- recorder::rec(index, a->type, a->frame, a->iValue, a->fValue);
- hasActions = true;
- }
- }
- }
+ hasActions = recorderHandler::cloneActions(src->index, index);
}
/* -------------------------------------------------------------------------- */
-void Channel::readPatch(const string& path, int i)
+void Channel::readPatch(const string& path, const patch::channel_t& pch)
{
- channelManager::readPatch(this, i);
+ channelManager::readPatch(this, pch);
}
case ChannelStatus::OFF:
kernelMidi::sendMidiLightning(midiOutLplaying, midimap::stopped);
break;
- case ChannelStatus::PLAY:
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::playing);
- break;
case ChannelStatus::WAIT:
kernelMidi::sendMidiLightning(midiOutLplaying, midimap::waiting);
break;
case ChannelStatus::ENDING:
kernelMidi::sendMidiLightning(midiOutLplaying, midimap::stopping);
break;
+ case ChannelStatus::PLAY:
+ if ((mixer::isChannelAudible(this) && !(this->mute)) ||
+ !midimap::isDefined(midimap::playing_inaudible))
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::playing);
+ else
+ kernelMidi::sendMidiLightning(midiOutLplaying, midimap::playing_inaudible);
+ break;
default:
break;
}
midiBuffer.clear();
}
-
#endif
+
+}} // giada::m::
#include <string>
#include <pthread.h>
#include "types.h"
+#include "patch.h"
#include "mixer.h"
#include "midiMapConf.h"
#include "midiEvent.h"
#endif
-class Plugin;
-class MidiMapConf;
class geChannel;
+namespace giada {
+namespace m
+{
+class Plugin;
+
class Channel
{
public:
/* parseEvents
Prepares channel for rendering. This is called on each frame. */
- virtual void parseEvents(giada::m::mixer::FrameEvents fe) = 0;
+ virtual void parseEvents(mixer::FrameEvents fe) = 0;
/* process
Merges working buffers into 'out', plus plugin processing (if any). Warning:
inBuffer might be nullptr if no input devices are available for recording. */
- virtual void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
- bool audible, bool running) = 0;
+ virtual void process(AudioBuffer& out, const AudioBuffer& in, bool audible,
+ bool running) = 0;
/* start
Action to do when channel starts. doQuantize = false (don't quantize)
virtual void kill(int localFrame) = 0;
- /* set
+ /* set mute
What to do when channel is un/muted. */
virtual void setMute(bool value) = 0;
+ /* set solo
+ What to do when channel is un/soloed. */
+
+ virtual void setSolo(bool value) = 0;
+
/* empty
Frees any associated resources (e.g. waveform for SAMPLE). */
virtual void stopInputRec(int globalFrame) {};
- virtual void readPatch(const std::string& basePath, int i);
+ virtual void readPatch(const std::string& basePath, const patch::channel_t& pch);
virtual void writePatch(int i, bool isProject);
/* receiveMidi
Receives and processes midi messages from external devices. */
- virtual void receiveMidi(const giada::m::MidiEvent& midiEvent) {};
+ virtual void receiveMidi(const MidiEvent& midiEvent) {};
/* calcPanning
Given an audio channel (stereo: 0 or 1) computes the current panning value. */
Pointer to a gChannel object, part of the GUI. TODO - remove this and send
signals instead. */
- geChannel* guiChannel;
+ geChannel* guiChannel;
/* buffer
Working buffer for internal processing. */
- giada::m::AudioBuffer buffer;
+ AudioBuffer buffer;
- giada::ChannelType type;
- giada::ChannelStatus status;
- giada::ChannelStatus recStatus;
+ ChannelType type;
+ ChannelStatus status;
+ ChannelStatus recStatus;
/* previewMode
Whether the channel is in audio preview mode or not. */
- giada::PreviewMode previewMode;
+ PreviewMode previewMode;
float pan;
float volume; // global volume
the delta during volume changes (or the line slope between two volume
points). */
- float volume_i;
- float volume_d;
+ double volume_i;
+ double volume_d;
- bool hasActions; // has something recorded
- bool readActions; // read what's recorded
-
- bool midiIn; // enable midi input
- uint32_t midiInKeyPress;
- uint32_t midiInKeyRel;
- uint32_t midiInKill;
- uint32_t midiInArm;
- uint32_t midiInVolume;
- uint32_t midiInMute;
- uint32_t midiInSolo;
-
- /* midiInFilter
- Which MIDI channel should be filtered out when receiving MIDI messages. -1
- means 'all'. */
-
- int midiInFilter;
+ bool hasActions; // has something recorded
+ bool readActions; // read what's recorded
+
+ bool midiIn; // enable midi input
+ uint32_t midiInKeyPress;
+ uint32_t midiInKeyRel;
+ uint32_t midiInKill;
+ uint32_t midiInArm;
+ uint32_t midiInVolume;
+ uint32_t midiInMute;
+ uint32_t midiInSolo;
+
+ /* midiInFilter
+ Which MIDI channel should be filtered out when receiving MIDI messages. -1
+ means 'all'. */
+
+ int midiInFilter;
/* midiOutL*
* Enable MIDI lightning output, plus a set of midi lighting event to be sent
* else gets stripped out. */
bool midiOutL;
- uint32_t midiOutLplaying;
- uint32_t midiOutLmute;
- uint32_t midiOutLsolo;
+ uint32_t midiOutLplaying;
+ uint32_t midiOutLmute;
+ uint32_t midiOutLsolo;
#ifdef WITH_VST
- std::vector <Plugin*> plugins;
+ std::vector <Plugin*> plugins;
#endif
protected:
- Channel(giada::ChannelType type, giada::ChannelStatus status, int bufferSize);
+ Channel(ChannelType type, ChannelStatus status, int bufferSize);
#ifdef WITH_VST
#endif
};
+}} // giada::m::
+
#endif
#include "midiChannel.h"
#include "pluginHost.h"
#include "plugin.h"
+#include "action.h"
+#include "recorderHandler.h"
#include "channelManager.h"
{
namespace
{
-void writeActions_(int chanIndex, patch::channel_t& pch)
-{
- recorder::forEachAction([&] (const recorder::action* a) {
- if (a->chan != chanIndex)
- return;
- pch.actions.push_back(patch::action_t {
- a->type, a->frame, a->fValue, a->iValue
- });
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void writePlugins_(const Channel* ch, patch::channel_t& pch)
{
#ifdef WITH_VST
void readActions_(Channel* ch, const patch::channel_t& pch)
{
- for (const patch::action_t& ac : pch.actions) {
- recorder::rec(ch->index, ac.type, ac.frame, ac.iValue, ac.fValue);
- ch->hasActions = true;
- }
+ recorderHandler::readPatch(pch.actions);
+ ch->hasActions = pch.actions.size() > 0;
}
/* -------------------------------------------------------------------------- */
-int create(ChannelType type, int bufferSize, bool inputMonitorOn, Channel** out)
+Channel* create(ChannelType type, int bufferSize, bool inputMonitorOn)
{
if (type == ChannelType::SAMPLE)
- *out = new SampleChannel(inputMonitorOn, bufferSize);
+ return new SampleChannel(inputMonitorOn, bufferSize);
else
- *out = new MidiChannel(bufferSize);
- return G_RES_OK;
+ return new MidiChannel(bufferSize);
}
pch.midiOutLmute = ch->midiOutLmute;
pch.midiOutLsolo = ch->midiOutLsolo;
- writeActions_(ch->index, pch);
+ recorderHandler::writePatch(ch->index, pch.actions);
writePlugins_(ch, pch);
patch::channels.push_back(pch);
/* -------------------------------------------------------------------------- */
-void readPatch(Channel* ch, int i)
+void readPatch(Channel* ch, const patch::channel_t& pch)
{
- const patch::channel_t& pch = patch::channels.at(i);
-
ch->key = pch.key;
ch->armed = pch.armed;
ch->type = static_cast<ChannelType>(pch.type);
/* -------------------------------------------------------------------------- */
-void readPatch(SampleChannel* ch, const string& basePath, int i)
+void readPatch(SampleChannel* ch, const string& basePath, const patch::channel_t& pch)
{
- const patch::channel_t& pch = patch::channels.at(i);
-
ch->mode = static_cast<ChannelMode>(pch.mode);
ch->readActions = pch.recActive;
ch->recStatus = pch.recActive ? ChannelStatus::PLAY : ChannelStatus::OFF;
ch->midiInVeloAsVol = pch.midiInVeloAsVol;
ch->midiInReadActions = pch.midiInReadActions;
ch->midiInPitch = pch.midiInPitch;
- ch->inputMonitor = pch.inputMonitor;
+ ch->inputMonitor = pch.inputMonitor;
ch->setBoost(pch.boost);
- Wave* w = nullptr;
- int res = waveManager::create(basePath + pch.samplePath, &w);
+ Wave* w = nullptr;
+ int res = waveManager::create(basePath + pch.samplePath, &w);
if (res == G_RES_OK) {
ch->pushWave(w);
/* -------------------------------------------------------------------------- */
-void readPatch(MidiChannel* ch, int i)
+void readPatch(MidiChannel* ch, const patch::channel_t& pch)
{
- const patch::channel_t& pch = patch::channels.at(i);
-
ch->midiOut = pch.midiOut;
ch->midiOutChan = pch.midiOutChan;
}
#include "types.h"
+namespace giada {
+namespace m
+{
class Channel;
class SampleChannel;
class MidiChannel;
-
-namespace giada {
-namespace m {
+namespace patch
+{
+struct channel_t;
+}
namespace channelManager
{
-int create(ChannelType type, int bufferSize, bool inputMonitorOn, Channel** out);
+Channel* create(ChannelType type, int bufferSize, bool inputMonitorOn);
int writePatch(const Channel* ch, bool isProject);
void writePatch(const SampleChannel* ch, bool isProject, int index);
void writePatch(const MidiChannel* ch, bool isProject, int index);
-void readPatch(Channel* ch, int index);
-void readPatch(SampleChannel* ch, const std::string& basePath, int index);
-void readPatch(MidiChannel* ch, int index);
+void readPatch(Channel* ch, const patch::channel_t& pch);
+void readPatch(SampleChannel* ch, const std::string& basePath, const patch::channel_t& pch);
+void readPatch(MidiChannel* ch, const patch::channel_t& pch);
}}}; // giada::m::channelManager
if (jackState.running != jackStatePrev.running) {
if (jackState.running) {
if (!isRunning())
- glue_startSeq(false); // not from UI
+ c::transport::startSeq(false); // not from UI
}
else {
if (isRunning())
- glue_stopSeq(false); // not from UI
+ c::transport::stopSeq(false); // not from UI
}
}
if (jackState.bpm != jackStatePrev.bpm)
glue_setBpm(jackState.bpm);
if (jackState.frame == 0 && jackState.frame != jackStatePrev.frame)
- glue_rewindSeq(false, false); // not from UI, don't notify jack (avoid loop)
+ c::transport::rewindSeq(false, false); // not from UI, don't notify jack (avoid loop)
jackStatePrev = jackState;
}
bool recsStopOnChanHalt = false;
bool chansStopOnSeqHalt = false;
bool treatRecsAsLoops = false;
-bool resizeRecordings = true;
bool inputMonitorDefaultOn = false;
string pluginPath = "";
if (!storager::setBool(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, recsStopOnChanHalt)) return 0;
if (!storager::setBool(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, chansStopOnSeqHalt)) return 0;
if (!storager::setBool(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, treatRecsAsLoops)) return 0;
- if (!storager::setBool(jRoot, CONF_KEY_RESIZE_RECORDINGS, resizeRecordings)) return 0;
if (!storager::setBool(jRoot, CONF_KEY_INPUT_MONITOR_DEFAULT_ON, inputMonitorDefaultOn)) return 0;
if (!storager::setString(jRoot, CONF_KEY_PLUGINS_PATH, pluginPath)) return 0;
if (!storager::setString(jRoot, CONF_KEY_PATCHES_PATH, patchPath)) return 0;
json_object_set_new(jRoot, CONF_KEY_RECS_STOP_ON_CHAN_HALT, json_boolean(recsStopOnChanHalt));
json_object_set_new(jRoot, CONF_KEY_CHANS_STOP_ON_SEQ_HALT, json_boolean(chansStopOnSeqHalt));
json_object_set_new(jRoot, CONF_KEY_TREAT_RECS_AS_LOOPS, json_boolean(treatRecsAsLoops));
- json_object_set_new(jRoot, CONF_KEY_RESIZE_RECORDINGS, json_boolean(resizeRecordings));
json_object_set_new(jRoot, CONF_KEY_INPUT_MONITOR_DEFAULT_ON, json_boolean(inputMonitorDefaultOn));
json_object_set_new(jRoot, CONF_KEY_PLUGINS_PATH, json_string(pluginPath.c_str()));
json_object_set_new(jRoot, CONF_KEY_PATCHES_PATH, json_string(patchPath.c_str()));
extern bool recsStopOnChanHalt;
extern bool chansStopOnSeqHalt;
extern bool treatRecsAsLoops;
-extern bool resizeRecordings;
extern bool inputMonitorDefaultOn;
extern std::string pluginPath;
/* -- version --------------------------------------------------------------- */
#define G_APP_NAME "Giada"
-#define G_VERSION_STR "0.15.2"
+#define G_VERSION_STR "0.15.3"
#define G_VERSION_MAJOR 0
#define G_VERSION_MINOR 15
-#define G_VERSION_PATCH 2
+#define G_VERSION_PATCH 3
#define CONF_FILENAME "giada.conf"
/* -- MIN/MAX values -------------------------------------------------------- */
-#define G_MIN_BPM 20.0f
-#define G_MIN_BPM_STR "20.0"
-#define G_MAX_BPM 999.0f
-#define G_MAX_BPM_STR "999.0"
-#define G_MAX_BEATS 32
-#define G_MAX_BARS 32
-#define G_MAX_QUANTIZE 8
-#define G_MIN_DB_SCALE 60.0f
-#define G_MIN_COLUMN_WIDTH 140
-#define G_MAX_BOOST_DB 20.0f
-#define G_MIN_PITCH 0.1f
-#define G_MAX_PITCH 4.0f
-#define G_MAX_GRID_VAL 64
-#define G_MIN_BUF_SIZE 8
-#define G_MAX_BUF_SIZE 4096
-#define G_MIN_GUI_WIDTH 816
-#define G_MIN_GUI_HEIGHT 510
-#define G_MAX_IO_CHANS 2
-#define G_MAX_VELOCITY 0x7F
-#define G_MAX_MIDI_CHANS 16
+constexpr float G_MIN_BPM = 20.0f;
+constexpr auto G_MIN_BPM_STR = "20.0";
+constexpr float G_MAX_BPM = 999.0f;
+constexpr auto G_MAX_BPM_STR = "999.0";
+constexpr int G_MAX_BEATS = 32;
+constexpr int G_MAX_BARS = 32;
+constexpr int G_MAX_QUANTIZE = 8;
+constexpr float G_MIN_DB_SCALE = 60.0f;
+constexpr int G_MIN_COLUMN_WIDTH = 140;
+constexpr float G_MAX_BOOST_DB = 20.0f;
+constexpr float G_MIN_PITCH = 0.1f;
+constexpr float G_MAX_PITCH = 4.0f;
+constexpr int G_MAX_GRID_VAL = 64;
+constexpr int G_MIN_BUF_SIZE = 8;
+constexpr int G_MAX_BUF_SIZE = 4096;
+constexpr int G_MIN_GUI_WIDTH = 816;
+constexpr int G_MIN_GUI_HEIGHT = 510;
+constexpr int G_MAX_IO_CHANS = 2;
+constexpr int G_MAX_VELOCITY = 0x7F;
+constexpr int G_MAX_MIDI_CHANS = 16;
+constexpr int G_MAX_POLYPHONY = 32;
/* -- kernel audio ---------------------------------------------------------- */
-#define G_SYS_API_NONE 0x00 // 0000 0000
-#define G_SYS_API_JACK 0x01 // 0000 0001
-#define G_SYS_API_ALSA 0x02 // 0000 0010
-#define G_SYS_API_DS 0x04 // 0000 0100
-#define G_SYS_API_ASIO 0x08 // 0000 1000
-#define G_SYS_API_CORE 0x10 // 0001 0000
+#define G_SYS_API_NONE 0x00 // 0000 0000
+#define G_SYS_API_JACK 0x01 // 0000 0001
+#define G_SYS_API_ALSA 0x02 // 0000 0010
+#define G_SYS_API_DS 0x04 // 0000 0100
+#define G_SYS_API_ASIO 0x08 // 0000 1000
+#define G_SYS_API_CORE 0x10 // 0001 0000
#define G_SYS_API_PULSE 0x20 // 0010 0000
#define G_SYS_API_WASAPI 0x40 // 0100 0000
#define G_SYS_API_ANY 0x7F // 0111 1111
/* -- kernel midi ----------------------------------------------------------- */
-#define G_MIDI_API_JACK 0x01 // 0000 0001
-#define G_MIDI_API_ALSA 0x02 // 0000 0010
+#define G_MIDI_API_JACK 0x01 // 0000 0001
+#define G_MIDI_API_ALSA 0x02 // 0000 0010
#define MIDI_STOP 0xFC
#define MIDI_EOX 0xF7 // end of sysex
-/* channels */
-
-#define MIDI_CHAN_0 0x00 << 24
-#define MIDI_CHAN_1 0x01 << 24
-#define MIDI_CHAN_2 0x02 << 24
-#define MIDI_CHAN_3 0x03 << 24
-#define MIDI_CHAN_4 0x04 << 24
-#define MIDI_CHAN_5 0x05 << 24
-#define MIDI_CHAN_6 0x06 << 24
-#define MIDI_CHAN_7 0x07 << 24
-#define MIDI_CHAN_8 0x08 << 24
-#define MIDI_CHAN_9 0x09 << 24
-#define MIDI_CHAN_10 0x0A << 24
-#define MIDI_CHAN_11 0x0B << 24
-#define MIDI_CHAN_12 0x0C << 24
-#define MIDI_CHAN_13 0x0D << 24
-#define MIDI_CHAN_14 0x0E << 24
-#define MIDI_CHAN_15 0x0F << 24
-
-const int MIDI_CHANS[G_MAX_MIDI_CHANS] = {
- MIDI_CHAN_0, MIDI_CHAN_1, MIDI_CHAN_2, MIDI_CHAN_3,
- MIDI_CHAN_4, MIDI_CHAN_5, MIDI_CHAN_6, MIDI_CHAN_7,
- MIDI_CHAN_8, MIDI_CHAN_9, MIDI_CHAN_10, MIDI_CHAN_11,
- MIDI_CHAN_12, MIDI_CHAN_13, MIDI_CHAN_14, MIDI_CHAN_15
+/* Channels */
+
+constexpr int G_MIDI_CHANS[G_MAX_MIDI_CHANS] = {
+ 0x00 << 24, 0x01 << 24, 0x02 << 24, 0x03 << 24,
+ 0x04 << 24, 0x05 << 24, 0x06 << 24, 0x07 << 24,
+ 0x08 << 24, 0x09 << 24, 0x0A << 24, 0x0B << 24,
+ 0x0C << 24, 0x0D << 24, 0x0E << 24, 0x0F << 24
};
/* midi sync constants */
#define PATCH_KEY_COLUMN_WIDTH "width"
#define PATCH_KEY_COLUMN_CHANNELS "channels"
+#define G_PATCH_KEY_ACTION_ID "id"
+#define G_PATCH_KEY_ACTION_CHANNEL "channel"
+#define G_PATCH_KEY_ACTION_FRAME "frame"
+#define G_PATCH_KEY_ACTION_EVENT "event"
+#define G_PATCH_KEY_ACTION_PREV "prev"
+#define G_PATCH_KEY_ACTION_NEXT "next"
+
/* JSON config keys */
#define CONF_KEY_HEADER "header"
#define CONF_KEY_RECS_STOP_ON_CHAN_HALT "recs_stop_on_chan_halt"
#define CONF_KEY_CHANS_STOP_ON_SEQ_HALT "chans_stop_on_seq_halt"
#define CONF_KEY_TREAT_RECS_AS_LOOPS "treat_recs_as_loops"
-#define CONF_KEY_RESIZE_RECORDINGS "resize_recordings"
#define CONF_KEY_INPUT_MONITOR_DEFAULT_ON "input_monitor_default_on"
#define CONF_KEY_PLUGINS_PATH "plugins_path"
#define CONF_KEY_PATCHES_PATH "patches_path"
/* JSON midimaps keys */
-#define MIDIMAP_KEY_BRAND "brand"
-#define MIDIMAP_KEY_DEVICE "device"
-#define MIDIMAP_KEY_INIT_COMMANDS "init_commands"
-#define MIDIMAP_KEY_MUTE_ON "mute_on"
-#define MIDIMAP_KEY_MUTE_OFF "mute_off"
-#define MIDIMAP_KEY_SOLO_ON "solo_on"
-#define MIDIMAP_KEY_SOLO_OFF "solo_off"
-#define MIDIMAP_KEY_WAITING "waiting"
-#define MIDIMAP_KEY_PLAYING "playing"
-#define MIDIMAP_KEY_STOPPING "stopping"
-#define MIDIMAP_KEY_STOPPED "stopped"
-#define MIDIMAP_KEY_CHANNEL "channel"
-#define MIDIMAP_KEY_MESSAGE "message"
+#define MIDIMAP_KEY_BRAND "brand"
+#define MIDIMAP_KEY_DEVICE "device"
+#define MIDIMAP_KEY_INIT_COMMANDS "init_commands"
+#define MIDIMAP_KEY_MUTE_ON "mute_on"
+#define MIDIMAP_KEY_MUTE_OFF "mute_off"
+#define MIDIMAP_KEY_SOLO_ON "solo_on"
+#define MIDIMAP_KEY_SOLO_OFF "solo_off"
+#define MIDIMAP_KEY_WAITING "waiting"
+#define MIDIMAP_KEY_PLAYING "playing"
+#define MIDIMAP_KEY_PLAYING_INAUDIBLE "playing_inaudible"
+#define MIDIMAP_KEY_STOPPING "stopping"
+#define MIDIMAP_KEY_STOPPED "stopped"
+#define MIDIMAP_KEY_CHANNEL "channel"
+#define MIDIMAP_KEY_MESSAGE "message"
#endif
* -------------------------------------------------------------------------- */
+#include <thread>
#include <ctime>
#ifdef __APPLE__
#include <pwd.h>
#if defined(__linux__) && defined(WITH_VST)
#include <X11/Xlib.h> // For XInitThreads
#endif
+#include <FL/Fl.H>
#include "../utils/log.h"
#include "../utils/fs.h"
+#include "../utils/time.h"
#include "../utils/gui.h"
#include "../gui/dialogs/gd_mainWindow.h"
#include "../gui/dialogs/gd_warnings.h"
#include "../glue/main.h"
-#include "init.h"
#include "mixer.h"
#include "wave.h"
#include "const.h"
#include "midiMapConf.h"
#include "kernelMidi.h"
#include "kernelAudio.h"
+#include "init.h"
+
+extern std::atomic<bool> G_quit;
+extern gdMainWindow* G_MainWin;
-extern bool G_quit;
-extern gdMainWindow *G_MainWin;
+namespace giada {
+namespace m {
+namespace init
+{
+namespace
+{
+std::thread videoThread;
-using namespace giada::m;
+/* -------------------------------------------------------------------------- */
-void init_prepareParser()
+
+void videoThreadCallback_()
{
- time_t t;
- time (&t);
- gu_log("[init] Giada " G_VERSION_STR " - %s", ctime(&t));
+ while (G_quit.load() == false) {
+ if (m::kernelAudio::getStatus())
+ gu_refreshUI();
+ u::time::sleep(G_GUI_REFRESH_RATE);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
- conf::read();
- patch::init();
+void initConf_()
+{
+ if (!conf::read())
+ gu_log("[init] Can't read configuration file! Using default values\n");
+
+ patch::init();
+ midimap::init();
+ midimap::setDefault();
+
if (!gu_logInit(conf::logMode))
gu_log("[init] log init failed! Using default stdout\n");
- gu_log("[init] configuration file ready\n");
+ if (midimap::read(conf::midiMapPath) != MIDIMAP_READ_OK)
+ gu_log("[init] MIDI map read failed!\n");
}
/* -------------------------------------------------------------------------- */
-void init_prepareKernelAudio()
+void initAudio_()
{
kernelAudio::openDevice();
clock::init(conf::samplerate, conf::midiTCfps);
mixer::init(clock::getFramesInLoop(), kernelAudio::getRealBufSize());
- recorder::init();
+ recorder::init(&mixer::mutex);
#ifdef WITH_VST
#endif
+ if (!kernelAudio::getStatus())
+ return;
+
+ kernelAudio::startStream();
}
/* -------------------------------------------------------------------------- */
-void init_prepareKernelMIDI()
+void initMIDI_()
{
kernelMidi::setApi(conf::midiSystem);
kernelMidi::openOutDevice(conf::midiPortOut);
- kernelMidi::openInDevice(conf::midiPortIn);
+ kernelMidi::openInDevice(conf::midiPortIn);
}
/* -------------------------------------------------------------------------- */
-void init_prepareMidiMap()
+void initGUI_(int argc, char** argv)
{
- midimap::init();
- midimap::setDefault();
+ /* This enables the FLTK lock and start the runtime multithreading support. */
- if (midimap::read(conf::midiMapPath) != MIDIMAP_READ_OK)
- gu_log("[init_prepareMidiMap] MIDI map read failed!\n");
-}
+ Fl::lock();
-
-/* -------------------------------------------------------------------------- */
-
-
-void init_startGUI(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. */
gu_updateMainWinLabel(patch::name == "" ? G_DEFAULT_PATCH_NAME : patch::name);
- /* never update the GUI elements if kernelAudio::getStatus() is bad, segfaults
- * are around the corner */
-
- if (kernelAudio::getStatus())
- gu_updateControls();
- else
+ if (!kernelAudio::getStatus())
gdAlert("Your soundcard isn't configured correctly.\n"
"Check the configuration and restart Giada.");
+
+ gu_updateControls();
+
+ videoThread = std::thread(videoThreadCallback_);
}
+
/* -------------------------------------------------------------------------- */
-void init_startKernelAudio()
+void shutdownAudio_()
{
- if (kernelAudio::getStatus())
- kernelAudio::startStream();
+#ifdef WITH_VST
+
+ pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex);
+ pluginHost::close();
+ gu_log("[init] PluginHost cleaned up\n");
+
+#endif
+
+ if (kernelAudio::getStatus()) {
+ kernelAudio::closeDevice();
+ gu_log("[init] KernelAudio closed\n");
+ mixer::close();
+ gu_log("[init] Mixer closed\n");
+ }
}
/* -------------------------------------------------------------------------- */
-void init_shutdown()
+void shutdownGUI_()
{
- G_quit = true;
+ gu_closeAllSubwindows();
+ videoThread.join();
- /* store position and size of the main window for the next startup */
+ gu_log("[init] All subwindows and UI thread closed\n");
+}
+} // {anonymous}
- conf::mainWindowX = G_MainWin->x();
- conf::mainWindowY = G_MainWin->y();
- conf::mainWindowW = G_MainWin->w();
- conf::mainWindowH = G_MainWin->h();
- /* close any open subwindow, especially before cleaning PluginHost to
- * avoid mess */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
- gu_closeAllSubwindows();
- gu_log("[init] all subwindows closed\n");
- /* write configuration file */
+void startup(int argc, char** argv)
+{
+ time_t t;
+ time (&t);
+ gu_log("[init] Giada " G_VERSION_STR " - %s", ctime(&t));
- if (!conf::write())
- gu_log("[init] error while saving configuration file!\n");
- else
- gu_log("[init] configuration saved\n");
+ initConf_();
+ initAudio_();
+ initMIDI_();
+ initGUI_(argc, argv);
+}
- recorder::clearAll();
- gu_log("[init] Recorder cleaned up\n");
-#ifdef WITH_VST
+/* -------------------------------------------------------------------------- */
- pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex);
- pluginHost::close();
- gu_log("[init] PluginHost cleaned up\n");
-#endif
+void shutdown()
+{
+ G_quit.store(true);
- if (kernelAudio::getStatus()) {
- kernelAudio::closeDevice();
- gu_log("[init] KernelAudio closed\n");
- mixer::close();
- gu_log("[init] Mixer closed\n");
- }
+ shutdownGUI_();
+
+ if (!conf::write())
+ gu_log("[init] error while saving configuration file!\n");
+ else
+ gu_log("[init] configuration saved\n");
+
+ shutdownAudio_();
gu_log("[init] Giada " G_VERSION_STR " closed\n\n");
gu_logClose();
}
+}}} // giada::m::init
\ No newline at end of file
#define G_INIT_H
-void init_prepareParser();
-void init_startGUI(int argc, char** argv);
-void init_prepareKernelAudio();
-void init_prepareKernelMIDI();
-void init_prepareMidiMap();
-void init_startKernelAudio();
-void init_shutdown();
-
+namespace giada {
+namespace m {
+namespace init
+{
+void startup(int argc, char** argv);
+void shutdown();
+}}} // giada::m::init
#endif
midimap::message_t msg = midimap::initCommands.at(i);
if (msg.value != 0x0 && msg.channel != -1) {
gu_log("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", msg.channel, msg.value);
- send(msg.value | MIDI_CHANS[msg.channel]);
+ send(msg.value | G_MIDI_CHANS[msg.channel]);
}
}
}
void sendMidiLightning(uint32_t learn, const midimap::message_t& msg)
{
+ // Skip lightning message if not defined in midi map
+
+ if (!midimap::isDefined(msg))
+ {
+ gu_log("[KM] message skipped (not defined in midimap)");
+ return;
+ }
+
gu_log("[KM] learn=%#X, chan=%d, msg=%#X, offset=%d\n", learn, msg.channel,
msg.value, msg.offset);
* -------------------------------------------------------------------------- */
+#include <cassert>
#include "../utils/log.h"
+#include "recorder.h"
#include "midiChannelProc.h"
#include "channelManager.h"
#include "channel.h"
+#include "recorderHandler.h"
+#include "action.h"
#include "patch.h"
#include "const.h"
-#include "clock.h"
#include "conf.h"
#include "mixer.h"
#include "pluginHost.h"
using std::string;
-using namespace giada;
-using namespace giada::m;
+namespace giada {
+namespace m
+{
MidiChannel::MidiChannel(int bufferSize)
- : Channel (ChannelType::MIDI, ChannelStatus::OFF, bufferSize),
+ : Channel (ChannelType::MIDI, ChannelStatus::OFF, bufferSize),
midiOut (false),
- midiOutChan(MIDI_CHANS[0])
+ midiOutChan(G_MIDI_CHANS[0])
{
}
/* -------------------------------------------------------------------------- */
-void MidiChannel::process(giada::m::AudioBuffer& out,
- const giada::m::AudioBuffer& in, bool audible, bool running)
+void MidiChannel::process(AudioBuffer& out, const AudioBuffer& in, bool audible,
+ bool running)
{
midiChannelProc::process(this, out, in, audible);
}
/* -------------------------------------------------------------------------- */
-void MidiChannel::readPatch(const string& basePath, int i)
+void MidiChannel::setSolo(bool value)
{
- Channel::readPatch("", i);
- channelManager::readPatch(this, i);
+ midiChannelProc::setSolo(this, value);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiChannel::readPatch(const string& basePath, const patch::channel_t& pch)
+{
+ Channel::readPatch("", pch);
+ channelManager::readPatch(this, pch);
}
/* -------------------------------------------------------------------------- */
-void MidiChannel::sendMidi(recorder::action* a, int localFrame)
+void MidiChannel::sendMidi(const Action* a, int localFrame)
{
if (isPlaying() && !mute) {
- if (midiOut)
- kernelMidi::send(a->iValue | MIDI_CHANS[midiOutChan]);
-
+ if (midiOut) {
+ MidiEvent event = a->event;
+ event.setChannel(midiOutChan);
+ kernelMidi::send(event.getRaw());
+ }
#ifdef WITH_VST
- addVstMidiEvent(a->iValue, localFrame);
-#endif
- }
-}
-
-
-void MidiChannel::sendMidi(uint32_t data)
-{
- if (isPlaying() && !mute) {
- if (midiOut)
- kernelMidi::send(data | MIDI_CHANS[midiOutChan]);
-#ifdef WITH_VST
- addVstMidiEvent(data, 0);
+ addVstMidiEvent(a->event.getRaw(), localFrame);
#endif
}
}
void MidiChannel::receiveMidi(const MidiEvent& midiEvent)
{
+ namespace mrh = m::recorderHandler;
+ namespace mr = m::recorder;
+
if (!armed)
return;
- /* Now all messages are turned into Channel-0 messages. Giada doesn't care about
- holding MIDI channel information. Moreover, having all internal messages on
- channel 0 is way easier. */
+ /* Now all messages are turned into Channel-0 messages. Giada doesn't care
+ about holding MIDI channel information. Moreover, having all internal
+ messages on channel 0 is way easier. */
MidiEvent midiEventFlat(midiEvent);
midiEventFlat.setChannel(0);
#ifdef WITH_VST
- while (true) {
- if (pthread_mutex_trylock(&pluginHost::mutex_midi) != 0)
- continue;
- gu_log("[MidiChannel::processMidi] msg=%X\n", midiEventFlat.getRaw());
- addVstMidiEvent(midiEventFlat.getRaw(), 0);
- pthread_mutex_unlock(&pluginHost::mutex_midi);
- break;
- }
+ pthread_mutex_lock(&pluginHost::mutex_midi);
+ addVstMidiEvent(midiEventFlat.getRaw(), 0);
+ pthread_mutex_unlock(&pluginHost::mutex_midi);
#endif
- if (recorder::canRec(this, clock::isRunning(), mixer::recording)) {
- recorder::rec(index, G_ACTION_MIDI, clock::getCurrentFrame(), midiEventFlat.getRaw());
+ if (mr::isActive()) {
+ mrh::liveRec(index, midiEventFlat);
hasActions = true;
}
}
bool MidiChannel::canInputRec()
{
return false; // midi channels don't handle input audio
-}
\ No newline at end of file
+}
+
+}} // giada::m::
#include "channel.h"
-class MidiMapConf;
-class Patch;
-
-
+namespace giada {
+namespace m
+{
class MidiChannel : public Channel
{
public:
MidiChannel(int bufferSize);
void copy(const Channel* src, pthread_mutex_t* pluginMutex) override;
- void parseEvents(giada::m::mixer::FrameEvents fe) override;
- void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
- bool audible, bool running) override;
+ void parseEvents(mixer::FrameEvents fe) override;
+ void process(AudioBuffer& out, const AudioBuffer& in, bool audible, bool running) override;
void start(int frame, bool doQuantize, int velocity) override;
void kill(int localFrame) override;
void empty() override;
void stop() override {};
void rewindBySeq() override;
void setMute(bool value) override;
- void readPatch(const std::string& basePath, int i) override;
+ void setSolo(bool value) override;
+ void readPatch(const std::string& basePath, const patch::channel_t& pch) override;
void writePatch(int i, bool isProject) override;
- void receiveMidi(const giada::m::MidiEvent& midiEvent) override;
+ void receiveMidi(const MidiEvent& midiEvent) override;
bool canInputRec() override;
/* sendMidi
* send Midi event to the outside world. */
- void sendMidi(giada::m::recorder::action* a, int localFrame);
- void sendMidi(uint32_t data);
+ void sendMidi(const Action* a, int localFrame);
#ifdef WITH_VST
#endif
- bool midiOut; // enable midi output
- uint8_t midiOutChan; // midi output channel
+ bool midiOut; // enable midi output
+ int midiOutChan; // midi output channel
};
+}} // giada::m::
+
#endif
#include "pluginHost.h"
#include "kernelMidi.h"
#include "const.h"
+#include "action.h"
#include "midiChannelProc.h"
-
+#include "mixerHandler.h"
namespace giada {
namespace m {
ch->sendMidiLstatus();
}
}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void parseAction_(MidiChannel* ch, const recorder::action* a, int localFrame)
-{
- if (ch->isPlaying() && !ch->mute) {
- if (ch->midiOut)
- kernelMidi::send(a->iValue | MIDI_CHANS[ch->midiOutChan]);
-#ifdef WITH_VST
- ch->addVstMidiEvent(a->iValue, localFrame);
-#endif
- }
-}
-
}; // {anonymous}
{
if (fe.onFirstBeat)
onFirstBeat_(ch);
- for (const recorder::action* action : fe.actions)
- if (action->chan == ch->index && action->type == G_ACTION_MIDI)
- parseAction_(ch, action, fe.frameLocal);
+ for (const Action* action : fe.actions)
+ if (action->channel == ch->index)
+ ch->sendMidi(action, fe.frameLocal);
}
/* -------------------------------------------------------------------------- */
-void process(MidiChannel* ch, giada::m::AudioBuffer& out,
- const giada::m::AudioBuffer& in, bool audible)
+void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible)
{
- #ifdef WITH_VST
- pluginHost::processStack(ch->buffer, pluginHost::CHANNEL, ch);
- #endif
+#ifdef WITH_VST
+ pluginHost::processStack(ch->buffer, pluginHost::CHANNEL, ch);
+#endif
/* Process the plugin stack first, then quit if the channel is muted/soloed.
This way there's no risk of cutting midi event pairs such as note-on and
ch->addVstMidiEvent(MIDI_ALL_NOTES_OFF, 0);
#endif
}
+
+ // This is for processing playing_inaudible
+ ch->sendMidiLstatus();
+
ch->sendMidiLmute();
}
/* -------------------------------------------------------------------------- */
+void setSolo(MidiChannel* ch, bool v)
+{
+ ch->solo = v;
+ m::mh::updateSoloCount();
+
+ // This is for processing playing_inaudible
+ for (Channel* channel : mixer::channels)
+ channel->sendMidiLstatus();
+
+ ch->sendMidiLsolo();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void stopBySeq(MidiChannel* ch)
{
kill(ch, 0);
}
-}}};
\ No newline at end of file
+}}};
#include "audioBuffer.h"
+namespace giada {
+namespace m
+{
class MidiChannel;
-
-namespace giada {
-namespace m {
namespace midiChannelProc
{
/* parseEvents
void parseEvents(MidiChannel* ch, mixer::FrameEvents ev);
/**/
-void process(MidiChannel* ch, giada::m::AudioBuffer& out,
- const giada::m::AudioBuffer& in, bool audible);
+void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible);
/* kill
Stops a channel abruptly. */
Mutes/unmutes a channel. */
void setMute(MidiChannel* ch, bool v);
+void setSolo(MidiChannel* ch, bool v);
}}};
-#endif
\ No newline at end of file
+#endif
c::channel::toggleSolo(ch, false);
}
else if (pure == ch->midiInVolume) {
- float vf = midiEvent.getVelocity() / 127.0f;
+ float vf = midiEvent.getVelocity() / 127.0f; // TODO: u::math::map
gu_log(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n",
ch->index, pure, midiEvent.getVelocity(), vf);
c::channel::setVolume(ch, vf, false);
else {
SampleChannel* sch = static_cast<SampleChannel*>(ch);
if (pure == sch->midiInPitch) {
- float vf = midiEvent.getVelocity() / (127/4.0f); // [0-127] ~> [0.0-4.0]
+ float vf = midiEvent.getVelocity() / (127/4.0f); // [0-127] ~> [0.0-4.0] TODO: u::math::map
gu_log(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n",
sch->index, pure, midiEvent.getVelocity(), vf);
c::channel::setPitch(sch, vf);
if (pure == conf::midiInRewind) {
gu_log(" >>> rewind (master) (pure=0x%X)\n", pure);
- glue_rewindSeq(false);
+ c::transport::rewindSeq(false);
}
else if (pure == conf::midiInStartStop) {
gu_log(" >>> startStop (master) (pure=0x%X)\n", pure);
- glue_startStopSeq(false);
+ c::transport::startStopSeq(false);
}
else if (pure == conf::midiInActionRec) {
gu_log(" >>> actionRec (master) (pure=0x%X)\n", pure);
}
else if (pure == conf::midiInMetronome) {
gu_log(" >>> metronome (master) (pure=0x%X)\n", pure);
- glue_startStopMetronome(false);
+ c::transport::startStopMetronome(false);
}
else if (pure == conf::midiInVolumeIn) {
float vf = midiEvent.getVelocity() / 127.0f;
#include <cassert>
+#include "const.h"
#include "midiEvent.h"
void MidiEvent::setVelocity(int v)
{
- assert(v >= 0 && v < G_MAX_VELOCITY);
+ assert(v >= 0 && v <= G_MAX_VELOCITY);
m_velocity = v;
}
{
public:
- static const int NOTE_ON = 0x90;
- static const int NOTE_OFF = 0x80;
+ static const int NOTE_ON = 0x90;
+ static const int NOTE_OFF = 0x80;
+ static const int NOTE_KILL = 0x70;
+ static const int ENVELOPE = 0xB0;
MidiEvent();
MidiEvent(uint32_t raw);
{
/* Remove '0x' part from the original string. */
- string input = message->valueStr.replace(0, 2, "");
+ string input = message->valueStr;
+
+ 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
message_t playing;
message_t stopping;
message_t stopped;
+message_t playing_inaudible;
string midimapsPath;
vector<string> maps;
stopped.valueStr = "";
stopped.offset = -1;
stopped.value = 0;
+ playing_inaudible.channel = 0;
+ playing_inaudible.valueStr = "";
+ playing_inaudible.offset = -1;
+ playing_inaudible.value = 0;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isDefined(message_t msg)
+{
+ return (msg.offset!=-1);
}
}
if (!storager::setString(jRoot, MIDIMAP_KEY_BRAND, brand)) return MIDIMAP_UNREADABLE;
- if (!storager::setString(jRoot, MIDIMAP_KEY_DEVICE, device)) return MIDIMAP_UNREADABLE;
+ if (!storager::setString(jRoot, MIDIMAP_KEY_DEVICE, device)) return MIDIMAP_UNREADABLE;
if (!readInitCommands(jRoot)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &muteOn, MIDIMAP_KEY_MUTE_ON)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &muteOff, MIDIMAP_KEY_MUTE_OFF)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &soloOn, MIDIMAP_KEY_SOLO_ON)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &soloOff, MIDIMAP_KEY_SOLO_OFF)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &waiting, MIDIMAP_KEY_WAITING)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &playing, MIDIMAP_KEY_PLAYING)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &stopping, MIDIMAP_KEY_STOPPING)) return MIDIMAP_UNREADABLE;
- if (!readCommand(jRoot, &stopped, MIDIMAP_KEY_STOPPED)) return MIDIMAP_UNREADABLE;
-
- /* parse messages */
-
- parse(&muteOn);
- parse(&muteOff);
- parse(&soloOn);
- parse(&soloOff);
- parse(&waiting);
- parse(&playing);
- parse(&stopping);
- parse(&stopped);
+ if (readCommand(jRoot, &muteOn, MIDIMAP_KEY_MUTE_ON)) parse(&muteOn);
+ if (readCommand(jRoot, &muteOff, MIDIMAP_KEY_MUTE_OFF)) parse(&muteOff);
+ if (readCommand(jRoot, &soloOn, MIDIMAP_KEY_SOLO_ON)) parse(&soloOn);
+ if (readCommand(jRoot, &soloOff, MIDIMAP_KEY_SOLO_OFF)) parse(&soloOff);
+ if (readCommand(jRoot, &waiting, MIDIMAP_KEY_WAITING)) parse(&waiting);
+ if (readCommand(jRoot, &playing, MIDIMAP_KEY_PLAYING)) parse(&playing);
+ if (readCommand(jRoot, &stopping, MIDIMAP_KEY_STOPPING)) parse(&stopping);
+ if (readCommand(jRoot, &stopped, MIDIMAP_KEY_STOPPED)) parse(&stopped);
+ if (readCommand(jRoot, &playing_inaudible, MIDIMAP_KEY_PLAYING_INAUDIBLE)) parse(&playing_inaudible);
return MIDIMAP_READ_OK;
}
{
struct message_t
{
- int channel;
- std::string valueStr;
+ int channel;
+ std::string valueStr;
int offset;
uint32_t value;
};
extern message_t playing;
extern message_t stopping;
extern message_t stopped;
+extern message_t playing_inaudible;
/* midimapsPath
* path of midimap files, different between OSes. */
void init();
/* setDefault
-Set default values in case no maps are available/choosen. */
+Set default values in case no maps are available/chosen. */
void setDefault();
+/* isDefined
+Check whether a specific message has been defined within midi map file.*/
+
+bool isDefined(message_t msg);
+
/* read
Read a midi map from file 'file'. */
#include "sampleChannel.h"
#include "midiChannel.h"
#include "audioBuffer.h"
+#include "action.h"
#include "mixer.h"
prepareBuffers(out);
+ // TODO - move lock here
+
for (unsigned j=0; j<bufferSize; j++) {
processLineIn(in, j); // TODO - can go outside this loop
renderIO(out, in);
+ // TODO - move unlock here
+
/* Post processing. */
for (unsigned j=0; j<bufferSize; j++) {
finalizeOutput(out, j);
#include "../deps/rtaudio-mod/RtAudio.h"
+namespace giada {
+namespace m
+{
+struct Action;
class Channel;
-
-namespace giada {
-namespace m {
namespace mixer
{
struct FrameEvents
bool onBar;
bool onFirstBeat;
bool quantoPassed;
- std::vector<recorder::action*> actions;
+ std::vector<const Action*> actions;
};
extern std::vector<Channel*> channels;
Channel* addChannel(ChannelType type)
{
- Channel* ch = nullptr;
- channelManager::create(type, kernelAudio::getRealBufSize(),
- conf::inputMonitorDefaultOn, &ch);
- if (ch == nullptr)
- return nullptr;
+ Channel* ch = channelManager::create(type, kernelAudio::getRealBufSize(),
+ conf::inputMonitorDefaultOn);
while (true) {
if (pthread_mutex_trylock(&mixer::mutex) != 0)
#include "types.h"
-class Channel;
-class SampleChannel;
namespace giada {
-namespace m {
+namespace m
+{
+class Channel;
+class SampleChannel;
+
namespace mh
{
/* addChannel
#include "../utils/log.h"
#include "../utils/string.h"
#include "../utils/ver.h"
+#include "../utils/math.h"
#include "const.h"
#include "types.h"
#include "storager.h"
+#include "midiEvent.h"
#include "conf.h"
#include "mixer.h"
#include "patch.h"
void sanitize()
{
- bpm = bpm < G_MIN_BPM || bpm > G_MAX_BPM ? G_DEFAULT_BPM : bpm;
- bars = bars <= 0 || bars > G_MAX_BARS ? G_DEFAULT_BARS : bars;
- beats = beats <= 0 || beats > G_MAX_BEATS ? G_DEFAULT_BEATS : beats;
- quantize = quantize < 0 || quantize > G_MAX_QUANTIZE ? G_DEFAULT_QUANTIZE : quantize;
- masterVolIn = masterVolIn < 0.0f || masterVolIn > 1.0f ? G_DEFAULT_VOL : masterVolIn;
- masterVolOut = masterVolOut < 0.0f || masterVolOut > 1.0f ? G_DEFAULT_VOL : masterVolOut;
+ namespace um = u::math;
+
+ bpm = um::bound(bpm, G_MIN_BPM, G_MAX_BPM, G_DEFAULT_BPM);
+ bars = um::bound(bars, 1, G_MAX_BARS, G_DEFAULT_BARS);
+ beats = um::bound(beats, 1, G_MAX_BEATS, G_DEFAULT_BEATS);
+ quantize = um::bound(quantize, 0, G_MAX_QUANTIZE, G_DEFAULT_QUANTIZE);
+ masterVolIn = um::bound(masterVolIn, 0.0f, 1.0f, G_DEFAULT_VOL);
+ masterVolOut = um::bound(masterVolOut, 0.0f, 1.0f, G_DEFAULT_VOL);
samplerate = samplerate <= 0 ? G_DEFAULT_SAMPLERATE : samplerate;
- for (unsigned i=0; i<columns.size(); i++) {
- column_t* col = &columns.at(i);
- col->index = col->index < 0 ? 0 : col->index;
- col->width = col->width < G_MIN_COLUMN_WIDTH ? G_MIN_COLUMN_WIDTH : col->width;
+ for (column_t& col : columns) {
+ col.index = col.index < 0 ? 0 : col.index;
+ col.width = col.width < G_MIN_COLUMN_WIDTH ? G_MIN_COLUMN_WIDTH : col.width;
}
- for (unsigned i=0; i<channels.size(); i++) {
- channel_t* ch = &channels.at(i);
- ch->size = ch->size < G_GUI_CHANNEL_H_1 || ch->size > G_GUI_CHANNEL_H_4 ? G_GUI_CHANNEL_H_1 : ch->size;
- ch->volume = ch->volume < 0.0f || ch->volume > 1.0f ? G_DEFAULT_VOL : ch->volume;
- ch->pan = ch->pan < 0.0f || ch->pan > 1.0f ? 1.0f : ch->pan;
- ch->boost = ch->boost < 1.0f ? G_DEFAULT_BOOST : ch->boost;
- ch->pitch = ch->pitch < 0.1f || ch->pitch > G_MAX_PITCH ? G_DEFAULT_PITCH : ch->pitch;
+ for (channel_t& ch : channels) {
+ ch.size = um::bound(ch.size, G_GUI_CHANNEL_H_1, G_GUI_CHANNEL_H_4, G_GUI_CHANNEL_H_1);
+ ch.volume = um::bound(ch.volume, 0.0f, 1.0f, G_DEFAULT_VOL);
+ ch.pan = um::bound(ch.pan, 0.0f, 1.0f, 1.0f);
+ ch.boost = um::bound(ch.boost, 1.0f, G_MAX_BOOST_DB, G_DEFAULT_BOOST);
+ ch.pitch = um::bound(ch.pitch, 0.1f, G_MAX_PITCH, G_DEFAULT_PITCH);
+ ch.midiOutChan = um::bound(ch.midiOutChan, 0, G_MAX_MIDI_CHANS - 1, 0);
}
}
{
json_t* jActions = json_object_get(jContainer, PATCH_KEY_CHANNEL_ACTIONS);
if (!storager::checkArray(jActions, PATCH_KEY_CHANNEL_ACTIONS))
- return 0;
+ return false;
size_t actionIndex;
json_t* jAction;
json_array_foreach(jActions, actionIndex, jAction) {
if (!storager::checkObject(jAction, "")) // TODO pass actionIndex as string
- return 0;
+ return false;
action_t action;
- if (!storager::setInt (jAction, PATCH_KEY_ACTION_TYPE, action.type)) return 0;
- if (!storager::setInt (jAction, PATCH_KEY_ACTION_FRAME, action.frame)) return 0;
- if (!storager::setFloat (jAction, PATCH_KEY_ACTION_F_VALUE, action.fValue)) return 0;
- if (!storager::setUint32(jAction, PATCH_KEY_ACTION_I_VALUE, action.iValue)) return 0;
+
+ /* TODO - temporary code for backward compatibility with old actions.
+ To be removed in 0.16.0. */
+ if (u::ver::isLess(versionMajor, versionMinor, versionPatch, 0, 15, 3)) {
+
+ action.id = -1;
+ action.channel = channel->index;
+ if (!storager::setInt (jAction, "frame", action.frame)) return 0;
+ if (!storager::setUint32(jAction, "type", action.event)) return 0;
+ action.prev = -1;
+ action.next = -1;
+
+ if (action.event == 0x01) // KEY_PRESS
+ action.event = MidiEvent(MidiEvent::NOTE_ON, 0, 0).getRaw();
+ else if (action.event == 0x02) // KEY_REL
+ action.event = MidiEvent(MidiEvent::NOTE_OFF, 0, 0).getRaw();
+ else if (action.event == 0x04) // KEY_KILL
+ action.event = MidiEvent(MidiEvent::NOTE_KILL, 0, 0).getRaw();
+ else if (action.event == 0x20) // VOLUME not supported, sorry :)
+ continue;
+ else if (action.event == 0x40) // MIDI EVENT
+ if (!storager::setUint32(jAction, "i_value", action.event)) return 0;
+ }
+ else {
+ if (!storager::setInt (jAction, G_PATCH_KEY_ACTION_ID, action.id)) return 0;
+ if (!storager::setInt (jAction, G_PATCH_KEY_ACTION_CHANNEL, action.channel)) return 0;
+ if (!storager::setInt (jAction, G_PATCH_KEY_ACTION_FRAME, action.frame)) return 0;
+ if (!storager::setUint32(jAction, G_PATCH_KEY_ACTION_EVENT, action.event)) return 0;
+ if (!storager::setInt (jAction, G_PATCH_KEY_ACTION_PREV, action.prev)) return 0;
+ if (!storager::setInt (jAction, G_PATCH_KEY_ACTION_NEXT, action.next)) return 0;
+ }
channel->actions.push_back(action);
}
- return 1;
+ return true;
}
if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_INPUT_MONITOR, channel.inputMonitor)) return 0;
if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS, channel.midiInReadActions)) return 0;
if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_IN_PITCH, channel.midiInPitch)) return 0;
- if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, channel.midiOut)) return 0;
- if (!storager::setUint32(jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, channel.midiOutChan)) return 0;
+ if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MIDI_OUT, channel.midiOut)) return 0;
+ if (!storager::setInt (jChannel, PATCH_KEY_CHANNEL_MIDI_OUT_CHAN, channel.midiOutChan)) return 0;
if (!storager::setBool (jChannel, PATCH_KEY_CHANNEL_ARMED, channel.armed)) return 0;
readActions(jChannel, &channel);
/* -------------------------------------------------------------------------- */
-void writeActions(json_t*jContainer, vector<action_t>*actions)
+void writeActions(json_t* jContainer, vector<action_t>& actions)
{
json_t* jActions = json_array();
- for (unsigned k=0; k<actions->size(); k++) {
- json_t* jAction = json_object();
- action_t action = actions->at(k);
- json_object_set_new(jAction, PATCH_KEY_ACTION_TYPE, json_integer(action.type));
- json_object_set_new(jAction, PATCH_KEY_ACTION_FRAME, json_integer(action.frame));
- json_object_set_new(jAction, PATCH_KEY_ACTION_F_VALUE, json_real(action.fValue));
- json_object_set_new(jAction, PATCH_KEY_ACTION_I_VALUE, json_integer(action.iValue));
+ for (action_t action : actions) {
+ json_t* jAction = json_object();
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_ID, json_integer(action.id));
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_CHANNEL, json_integer(action.channel));
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_FRAME, json_integer(action.frame));
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_EVENT, json_integer(action.event));
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_PREV, json_integer(action.prev));
+ json_object_set_new(jAction, G_PATCH_KEY_ACTION_NEXT, json_integer(action.next));
json_array_append_new(jActions, jAction);
}
json_object_set_new(jContainer, PATCH_KEY_CHANNEL_ACTIONS, jActions);
json_object_set_new(jChannel, PATCH_KEY_CHANNEL_ARMED, json_boolean(channel.armed));
json_array_append_new(jChannels, jChannel);
- writeActions(jChannel, &channel.actions);
+ writeActions(jChannel, channel.actions);
#ifdef WITH_VST
{
struct action_t
{
- int type;
+ int id;
+ int channel;
int frame;
- float fValue;
- uint32_t iValue;
+ uint32_t event;
+ int prev;
+ int next;
};
#ifdef WITH_VST
uint32_t midiInReadActions;
uint32_t midiInPitch;
// midi channel
- uint32_t midiOut;
- uint32_t midiOutChan;
+ int midiOut; // TODO - should be bool
+ int midiOutChan;
std::vector<action_t> actions;
using std::string;
-using namespace giada::u;
+namespace giada {
+namespace m
+{
int Plugin::idGenerator = 1;
Plugin::Plugin(juce::AudioPluginInstance* plugin, double samplerate,
int buffersize)
: ui (nullptr),
- plugin(plugin),
- id (idGenerator++),
- bypass(false)
+ plugin(plugin),
+ id (idGenerator++),
+ bypass(false)
{
using namespace juce;
ui = nullptr;
}
+}} // giada::m::
+
+
#endif
#include "../deps/juce-config.h"
+namespace giada {
+namespace m
+{
class Plugin
{
private:
std::vector<uint32_t> midiInParams;
};
+}} // giada::m::
+
#endif
#endif // #ifdef WITH_VST
searchPath.add(juce::File(dir));
juce::PluginDirectoryScanner scanner(knownPluginList, format, searchPath,
- true, juce::File::nonexistent); // true: recursive
+ true, juce::File()); // true: recursive
juce::String name;
while (scanner.scanNextFile(false, name)) {
#include "audioBuffer.h"
-class Plugin;
-class Channel;
namespace giada {
-namespace m {
+namespace m
+{
+class Plugin;
+class Channel;
+
namespace pluginHost
{
enum stackType
* -------------------------------------------------------------------------- */
+#include <memory>
+#include <algorithm>
#include <cassert>
-#include <cmath>
#include "../utils/log.h"
-#include "const.h"
-#include "sampleChannel.h"
+#include "action.h"
+#include "channel.h"
#include "recorder.h"
+using std::map;
using std::vector;
{
namespace
{
-/* Composite
-A group of two actions (keypress+keyrel, muteon+muteoff) used during the overdub
-process. */
+/* actions
+The big map of actions {frame : actions[]}. This belongs to Recorder, but it
+is often parsed by Mixer. So every "write" action performed on it (add,
+remove, ...) must be guarded by a lock on the mixerMutex. Until a proper
+lock-free solution will be implemented. */
-Composite cmp;
+ActionMap actions;
-
-/* -------------------------------------------------------------------------- */
-
-
-/* fixOverdubTruncation
-Fixes underlying action truncation when overdubbing over a longer action. I.e.:
- Original: |#############|
- Overdub: ---|#######|---
- fix: |#||#######|--- */
-
-void fixOverdubTruncation(const Composite& comp, pthread_mutex_t* mixerMutex)
-{
- action* next = nullptr;
- int res = getNextAction(comp.a2.chan, comp.a1.type | comp.a2.type, comp.a2.frame,
- &next);
- if (res != 1 || next->type != comp.a2.type)
- return;
- gu_log("[recorder::fixOverdubTruncation] add truncation at frame %d, type=%d\n",
- next->frame, next->type);
- deleteAction(next->chan, next->frame, next->type, false, mixerMutex);
-}
-
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-vector<int> frames;
-vector<vector<action*>> global;
-vector<action*> actions; // used internally
-
-bool active = false;
-bool sortedActions = false;
+pthread_mutex_t* mixerMutex = nullptr;
+bool active = false;
+int actionId = 0;
/* -------------------------------------------------------------------------- */
-void init()
+void lock_(std::function<void()> f)
{
- active = false;
- sortedActions = false;
- clearAll();
+ assert(mixerMutex != nullptr);
+ pthread_mutex_lock(mixerMutex);
+ f();
+ pthread_mutex_unlock(mixerMutex);
}
/* -------------------------------------------------------------------------- */
+/* optimize
+Removes frames without actions. */
-bool canRec(Channel* ch, bool clockRunning, bool mixerRecording)
+void optimize_(ActionMap& map)
{
- /* Can record on a channel if:
- - recorder is on
- - mixer is running
- - mixer is not recording a take somewhere
- - channel is MIDI or SAMPLE type with data in it */
- return active && clockRunning && !mixerRecording &&
- (ch->type == ChannelType::MIDI || (ch->type == ChannelType::SAMPLE && ch->hasData()));
+ for (auto it = map.cbegin(); it != map.cend();)
+ it->second.size() == 0 ? it = map.erase(it) : ++it;
}
/* -------------------------------------------------------------------------- */
-void rec(int index, int type, int frame, uint32_t iValue, float fValue)
+void removeIf_(std::function<bool(const Action*)> f)
{
- /* allocating the action */
-
- action* a = (action*) malloc(sizeof(action)); /* TODO - AAARRRGGHHHHHH!!!! */
- a->chan = index;
- a->type = type;
- a->frame = frame;
- a->iValue = iValue;
- a->fValue = fValue;
-
- /* check if the frame exists in the stack. If it exists, we don't extend
- * the stack, but we add (or push) a new action to it. */
-
- int frameToExpand = frames.size();
- for (int i=0; i<frameToExpand; i++)
- if (frames.at(i) == frame) {
- frameToExpand = i;
- break;
- }
-
- /* espansione dello stack frames nel caso l'azione ricada in frame
- * non precedentemente memorizzati (frameToExpand == frames.size()).
- * Espandere frames è facile, basta aggiungere un frame in coda.
- * Espandere global è più complesso: bisogna prima allocare una
- * cella in global (per renderlo parallelo a frames) e poi
- * inizializzare il suo sub-stack (di action). */
-
- if (frameToExpand == (int) frames.size()) {
- frames.push_back(frame);
- global.push_back(actions); // array of actions added
- global.at(global.size()-1).push_back(a); // action added
- }
- else {
-
- /* no duplicates, please */
-
- for (unsigned t=0; t<global.at(frameToExpand).size(); t++) {
- action* ac = global.at(frameToExpand).at(t);
- if (ac->chan == index &&
- ac->type == type &&
- ac->frame == frame &&
- ac->iValue == iValue &&
- ac->fValue == fValue)
- return;
+ ActionMap temp = actions;
+
+ /*
+ for (auto& kv : temp) {
+ vector<Action*>& as = kv.second;
+ as.erase(std::remove_if(as.begin(), as.end(), f), as.end());
+ }*/
+ for (auto& kv : temp) {
+ auto i = std::begin(kv.second);
+ while (i != std::end(kv.second)) {
+ if (f(*i)) {
+ delete *i;
+ i = kv.second.erase(i);
+ }
+ else
+ ++i;
}
-
- global.at(frameToExpand).push_back(a); // expand array
}
+ optimize_(temp);
- sortedActions = false;
-
- gu_log("[recorder::rec] action recorded, type=%d frame=%d chan=%d iValue=%d (0x%X) fValue=%f\n",
- a->type, a->frame, a->chan, a->iValue, a->iValue, a->fValue);
- //print();
+ lock_([&](){ actions = std::move(temp); });
}
+} // {anonymous}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void clearChan(int index)
+void init(pthread_mutex_t* m)
{
- gu_log("[recorder::clearChan] clearing chan %d...\n", index);
-
- for (unsigned i=0; i<global.size(); i++) { // for each frame i
- unsigned j=0;
- while (true) {
- if (j == global.at(i).size()) break; // for each action j of frame i
- action* a = global.at(i).at(j);
- if (a->chan == index) {
- free(a);
- global.at(i).erase(global.at(i).begin() + j);
- }
- else
- j++;
- }
- }
- optimize();
- //print();
+ mixerMutex = m;
+ active = false;
+ actionId = 0;
+ clearAll();
}
/* -------------------------------------------------------------------------- */
-void clearAction(int index, char act)
+void debug()
{
- gu_log("[recorder::clearAction] clearing action %d from chan %d...\n", act, index);
- for (unsigned i=0; i<global.size(); i++) { // for each frame i
- unsigned j=0;
- while (true) { // for each action j of frame i
- if (j == global.at(i).size())
- break;
- action* a = global.at(i).at(j);
- if (a->chan == index && (act & a->type) == a->type) { // bitmask
- free(a);
- global.at(i).erase(global.at(i).begin() + j);
- }
- else
- j++;
+ int total = 0;
+ puts("-------------");
+ for (auto& kv : actions) {
+ printf("frame: %d\n", kv.first);
+ for (const Action* a : kv.second) {
+ total++;
+ printf(" this=%p - id=%d, frame=%d, channel=%d, value=0x%X, prev=%p, next=%p\n",
+ (void*) a, a->id, a->frame, a->channel, a->event.getRaw(), (void*) a->prev, (void*) a->next);
}
}
- optimize();
- //print();
+ printf("TOTAL: %d\n", total);
+ puts("-------------");
}
/* -------------------------------------------------------------------------- */
-void deleteAction(int chan, int frame, char type, bool checkValues,
- pthread_mutex_t* mixerMutex, uint32_t iValue, float fValue)
+void clearAll()
{
- /* find the frame 'frame' */
-
- bool found = false;
- for (unsigned i=0; i<frames.size() && !found; i++) {
-
- if (frames.at(i) != frame)
- continue;
-
- /* find the action in frame i */
-
- for (unsigned j=0; j<global.at(i).size(); j++) {
- action* a = global.at(i).at(j);
-
- /* action comparison logic */
-
- bool doit = (a->chan == chan && a->type == (type & a->type));
- if (checkValues)
- doit &= (a->iValue == iValue && a->fValue == fValue);
-
- if (!doit)
- continue;
-
- while (true) {
- if (pthread_mutex_trylock(mixerMutex)) {
- free(a);
- global.at(i).erase(global.at(i).begin() + j);
- pthread_mutex_unlock(mixerMutex);
- found = true;
- break;
- }
- else
- gu_log("[recorder::deleteAction] waiting for mutex...\n");
- }
- }
- }
- if (found) {
- optimize();
- gu_log("[recorder::deleteAction] action deleted, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n",
- type, frame, chan, iValue, iValue, fValue);
- }
- else
- gu_log("[recorder::deleteAction] unable to delete action, not found! type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n",
- type, frame, chan, iValue, iValue, fValue);
+ removeIf_([=](const Action* a) { return true; }); // TODO optimize this
}
/* -------------------------------------------------------------------------- */
-void deleteActions(int chan, int frame_a, int frame_b, char type,
- pthread_mutex_t* mixerMutex)
+void clearChannel(int channel)
{
- sortActions();
- vector<int> dels;
-
- for (unsigned i=0; i<frames.size(); i++)
- if (frames.at(i) > frame_a && frames.at(i) < frame_b)
- dels.push_back(frames.at(i));
-
- for (unsigned i=0; i<dels.size(); i++)
- deleteAction(chan, dels.at(i), type, false, mixerMutex); // false == don't check values
+ removeIf_([=](const Action* a) { return a->channel == channel; });
}
/* -------------------------------------------------------------------------- */
-void clearAll()
+void clearActions(int channel, int type)
{
- while (global.size() > 0) {
- for (unsigned i=0; i<global.size(); i++) {
- for (unsigned k=0; k<global.at(i).size(); k++)
- free(global.at(i).at(k)); // free action
- global.at(i).clear(); // free action container
- global.erase(global.begin() + i);
- }
- }
- global.clear();
- frames.clear();
+ removeIf_([=](const Action* a)
+ {
+ return a->channel == channel && a->event.getStatus() == type;
+ });
}
/* -------------------------------------------------------------------------- */
-void optimize()
+void deleteAction(const Action* target)
{
- /* do something until the i frame is empty. */
-
- unsigned i = 0;
- while (true) {
- if (i == global.size()) return;
- if (global.at(i).size() == 0) {
- global.erase(global.begin() + i);
- frames.erase(frames.begin() + i);
- }
- else
- i++;
- }
-
- sortActions();
+ removeIf_([=](const Action* a) { return a == target; });
}
/* -------------------------------------------------------------------------- */
-void sortActions()
+void updateKeyFrames(std::function<Frame(Frame old)> f)
{
- if (sortedActions)
- return;
- for (unsigned i=0; i<frames.size(); i++)
- for (unsigned j=0; j<frames.size(); j++)
- if (frames.at(j) > frames.at(i)) {
- std::swap(frames.at(j), frames.at(i));
- std::swap(global.at(j), global.at(i));
- }
- sortedActions = true;
- //print();
+ /* This stuff must be performed in a lock, because we are moving the vector
+ of actions from the real ActionMap to the temporary one. */
+
+ ActionMap temp;
+
+ lock_([&]()
+ {
+ for (auto& kv : actions) {
+ Frame frame = f(kv.first);
+ temp[frame] = std::move(kv.second); // Move std::vector<Action*>
+ for (const Action* action : temp[frame])
+ const_cast<Action*>(action)->frame = frame;
+ gu_log("[recorder::updateKeyFrames] %d -> %d\n", kv.first, frame);
+ }
+ actions = std::move(temp);
+ });
}
/* -------------------------------------------------------------------------- */
-void updateBpm(float oldval, float newval, int oldquanto)
+void updateEvent(const Action* a, MidiEvent e)
{
- for (unsigned i=0; i<frames.size(); i++) {
-
- float frame = ((float) frames.at(i)/newval) * oldval;
- frames.at(i) = (int) frame;
-
- /* the division up 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 ('scarto'): if it's lower
- * than 6 frames the new frame is collapsed with a quantized frame. */
- /** XXX - maybe 6 frames are too low */
-
- if (frames.at(i) != 0) {
- int scarto = oldquanto % frames.at(i);
- if (scarto > 0 && scarto <= 6)
- frames.at(i) = frames.at(i) + scarto;
- }
- }
-
- /* update structs */
-
- for (unsigned i=0; i<frames.size(); i++) {
- for (unsigned j=0; j<global.at(i).size(); j++) {
- action* a = global.at(i).at(j);
- a->frame = frames.at(i);
- }
- }
-
- //print();
+ assert(a != nullptr);
+ lock_([&] { const_cast<Action*>(a)->event = e; });
}
/* -------------------------------------------------------------------------- */
-void updateSamplerate(int systemRate, int patchRate)
+void updateSiblings(const Action* a, const Action* prev, const Action* next)
{
- /* diff ratio: systemRate / patchRate
- * e.g. 44100 / 96000 = 0.4... */
-
- if (systemRate == patchRate)
- return;
-
- gu_log("[recorder::updateSamplerate] systemRate (%d) != patchRate (%d), converting...\n", systemRate, patchRate);
-
- float ratio = systemRate / (float) patchRate;
- for (unsigned i=0; i<frames.size(); i++) {
-
- gu_log("[recorder::updateSamplerate] oldFrame = %d", frames.at(i));
-
- float newFrame = frames.at(i);
- newFrame = floorf(newFrame * ratio);
-
- frames.at(i) = (int) newFrame;
-
- gu_log(", newFrame = %d\n", frames.at(i));
- }
-
- /* update structs */
-
- for (unsigned i=0; i<frames.size(); i++) {
- for (unsigned j=0; j<global.at(i).size(); j++) {
- action* a = global.at(i).at(j);
- a->frame = frames.at(i);
- }
- }
+ assert(a != nullptr);
+ lock_([&]
+ {
+ const_cast<Action*>(a)->prev = prev;
+ const_cast<Action*>(a)->next = next;
+ if (prev != nullptr) const_cast<Action*>(prev)->next = a;
+ if (next != nullptr) const_cast<Action*>(next)->prev = a;
+ });
}
/* -------------------------------------------------------------------------- */
-void expand(int old_fpb, int new_fpb)
+void updateActionMap(ActionMap&& am)
{
- /* this algorithm requires multiple passages if we expand from e.g. 2
- * to 16 beats, precisely 16 / 2 - 1 = 7 times (-1 is the first group,
- * which exists yet). If we expand by a non-multiple, the result is zero,
- * due to float->int implicit cast */
-
- unsigned pass = (int) (new_fpb / old_fpb) - 1;
- if (pass == 0) pass = 1;
-
- unsigned init_fs = frames.size();
-
- for (unsigned z=1; z<=pass; z++) {
- for (unsigned i=0; i<init_fs; i++) {
- unsigned newframe = frames.at(i) + (old_fpb*z);
- frames.push_back(newframe);
- global.push_back(actions);
- for (unsigned k=0; k<global.at(i).size(); k++) {
- action* a = global.at(i).at(k);
- rec(a->chan, a->type, newframe, a->iValue, a->fValue);
- }
- }
- }
- gu_log("[recorder::expand] expanded recs\n");
- //print();
+ lock_([&](){ actions = am; });
}
/* -------------------------------------------------------------------------- */
-void shrink(int new_fpb)
+void updateActionId(int id)
{
- /* easier than expand(): here we delete eveything beyond old_framesPerBars. */
-
- unsigned i=0;
- while (true) {
- if (i == frames.size()) break;
-
- if (frames.at(i) >= new_fpb) {
- for (unsigned k=0; k<global.at(i).size(); k++)
- free(global.at(i).at(k)); // free action
- global.at(i).clear(); // free action container
- global.erase(global.begin() + i); // shrink global
- frames.erase(frames.begin() + i); // shrink frames
- }
- else
- i++;
- }
- optimize();
- gu_log("[recorder::shrink] shrinked recs\n");
- //print();
+ if (actionId <= id) // Never decrease it
+ actionId = id;
}
/* -------------------------------------------------------------------------- */
-bool hasActions(int chanIndex, int type)
+bool hasActions(int channel, int type)
{
- if (global.size() == 0)
- return false;
- for (unsigned i=0; i<global.size(); i++) {
- for (unsigned j=0; j<global.at(i).size(); j++) {
- if (global.at(i).at(j)->chan == chanIndex)
- if (type == -1 || global.at(i).at(j)->type == type)
- return true;
- }
- }
+ for (auto& kv : actions)
+ for (const Action* action : kv.second)
+ if (action->channel == channel && (type == 0 || type == action->event.getStatus()))
+ return true;
return false;
}
/* -------------------------------------------------------------------------- */
-int getNextAction(int chan, char type, int fromFrame, action** out,
- uint32_t iValue, uint32_t mask)
-{
- sortActions(); // mandatory
-
- /* Increase 'i' until it reaches 'fromFrame'. That's the point where to start
- to look for the next action. */
-
- unsigned i = 0;
- while (i < frames.size() && frames.at(i) <= fromFrame) i++;
+bool isActive() { return active; }
+void enable() { active = true; }
+void disable() { active = false; }
- /* No other actions past 'fromFrame': there are no more actions to look for.
- Return -1. */
- if (i == frames.size())
- return -1;
-
- for (; i<global.size(); i++) {
+/* -------------------------------------------------------------------------- */
- for (unsigned j=0; j<global.at(i).size(); j++) {
- action* a = global.at(i).at(j);
+const Action* makeAction(int id, int channel, Frame frame, MidiEvent e)
+{
+ return new Action{ id, channel, frame, e, -1, -1, nullptr, nullptr };
+}
- /* If the requested channel and type don't match, continue. */
- if (a->chan != chan || (type & a->type) != a->type)
- continue;
+/* -------------------------------------------------------------------------- */
- /* If no iValue has been specified (iValue == 0), then the next action has
- been found, return it. Otherwise, make sure the iValue matches the
- action's iValue, according to the mask provided. */
- if (iValue == 0 || (iValue != 0 && (a->iValue | mask) == (iValue | mask))) {
- *out = global.at(i).at(j);
- return 1;
- }
- }
- }
- return -2; // no 'type' actions found
+const Action* rec(int channel, Frame frame, MidiEvent 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. */
+
+ lock_([&]
+ {
+ actions[frame].push_back(makeAction(actionId++, channel, frame, event));
+ });
+ return actions[frame].back();
}
/* -------------------------------------------------------------------------- */
-int getAction(int chan, char action, int frame, struct action** out)
+void rec(const std::vector<const Action*>& as)
{
- for (unsigned i=0; i<global.size(); i++)
- for (unsigned j=0; j<global.at(i).size(); j++)
- if (frame == global.at(i).at(j)->frame &&
- action == global.at(i).at(j)->type &&
- chan == global.at(i).at(j)->chan)
- {
- *out = global.at(i).at(j);
- return 1;
- }
- return 0;
+ ActionMap temp = actions;
+
+ for (const Action* a : as) {
+ const_cast<Action*>(a)->id = actionId++;
+ temp[a->frame].push_back(a); // Memory is already allocated by recorderHandler
+ }
+
+ lock_([&](){ actions = std::move(temp); });
}
/* -------------------------------------------------------------------------- */
-void startOverdub(int index, char actionMask, int frame, unsigned bufferSize)
+vector<const Action*> getActionsOnFrame(Frame frame)
{
- /* prepare the composite struct */
-
- cmp.a1.type = G_ACTION_KEYPRESS;
- cmp.a2.type = G_ACTION_KEYREL;
- cmp.a1.chan = index;
- cmp.a2.chan = index;
- cmp.a1.frame = frame;
- // cmp.a2.frame doesn't exist yet
-
- /* avoid underlying action truncation: if action2.type == nextAction:
- * you are in the middle of a composite action, truncation needed */
-
- rec(index, cmp.a1.type, frame);
-
- action* act = nullptr;
- int res = getNextAction(index, cmp.a1.type | cmp.a2.type, cmp.a1.frame, &act);
- if (res == 1) {
- if (act->type == cmp.a2.type) {
- int truncFrame = cmp.a1.frame - bufferSize;
- if (truncFrame < 0)
- truncFrame = 0;
- gu_log("[recorder::startOverdub] add truncation at frame %d, type=%d\n", truncFrame, cmp.a2.type);
- rec(index, cmp.a2.type, truncFrame);
- }
- }
+ return actions.count(frame) ? actions[frame] : vector<const Action*>();
}
/* -------------------------------------------------------------------------- */
-void stopOverdub(int currentFrame, int totalFrames, pthread_mutex_t* mixerMutex)
+const Action* getClosestAction(int channel, Frame f, int type)
{
- cmp.a2.frame = currentFrame;
- bool ringLoop = false;
- bool nullLoop = false;
-
- /* Check for ring loops or null loops. Ring loop: a composite action with
- key_press at frame N and key_release at frame M, with M <= N.
- Null loop: a composite action that begins and ends on the very same frame,
- i.e. with 0 size. Very unlikely.
- If ring loop: record the last action at the end of the sequencer (that
- is 'totalFrames').
- If null loop: remove previous action and do nothing. Also make sure to avoid
- underlying action truncation, if the null loop occurs inside a composite
- action. */
-
- if (cmp.a2.frame < cmp.a1.frame) { // ring loop
- ringLoop = true;
- gu_log("[recorder::stopOverdub] ring loop! frame1=%d < frame2=%d\n", cmp.a1.frame, cmp.a2.frame);
- rec(cmp.a2.chan, cmp.a2.type, totalFrames);
- }
- else
- if (cmp.a2.frame == cmp.a1.frame) { // null loop
- nullLoop = true;
- gu_log("[recorder::stopOverdub] null loop! frame1=%d == frame2=%d\n", cmp.a1.frame, cmp.a2.frame);
- deleteAction(cmp.a1.chan, cmp.a1.frame, cmp.a1.type, false, mixerMutex); // false == don't check values
- fixOverdubTruncation(cmp, mixerMutex);
- }
-
- if (nullLoop)
- return;
+ const Action* out = nullptr;
+ forEachAction([&](const Action* a)
+ {
+ if (a->event.getStatus() != type || a->channel != channel)
+ return;
+ if (out == nullptr || (a->frame <= f && a->frame > out->frame))
+ out = a;
+ });
+ return out;
+}
- /* Remove any nested action between keypress----keyrel. */
- deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a1.type, mixerMutex);
- deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a2.type, mixerMutex);
+/* -------------------------------------------------------------------------- */
- if (ringLoop)
- return;
- /* Record second part of the composite action. Also make sure to avoid
- underlying action truncation, if keyrel happens inside a composite action. */
+ActionMap getActionMap() { return actions; }
- rec(cmp.a2.chan, cmp.a2.type, cmp.a2.frame);
- fixOverdubTruncation(cmp, mixerMutex);
-}
+int getLatestActionId() { return actionId; }
/* -------------------------------------------------------------------------- */
-vector<action*> getActionsOnFrame(int frame)
+vector<const Action*> getActionsOnChannel(int channel)
{
- for (size_t i=0; i<frames.size(); i++) {
- if (recorder::frames.at(i) != frame)
- continue;
- return global.at(i);
- }
- return vector<action*>();
+ vector<const Action*> out;
+ forEachAction([&](const Action* a)
+ {
+ if (a->channel == channel)
+ out.push_back(a);
+ });
+ return out;
}
/* -------------------------------------------------------------------------- */
-void forEachAction(std::function<void(const action*)> f)
+void forEachAction(std::function<void(const Action*)> f)
{
-
- for (const vector<action*> actions : recorder::global)
- for (const action* action : actions)
+ for (auto& kv : actions)
+ for (const Action* action : kv.second)
f(action);
}
+
}}}; // giada::m::recorder::
#define G_RECORDER_H
-#include <cstdint>
+#include <pthread.h>
+#include <map>
#include <vector>
#include <functional>
-#include <pthread.h>
-
-
-class Channel;
+#include "types.h"
+#include "midiEvent.h"
namespace giada {
-namespace m {
-namespace recorder
+namespace m
{
-/* action
- * struct containing fields to describe an atomic action. Note from
- * VST sdk: parameter values, like all VST parameters, are declared as
- * floats with an inclusive range of 0.0 to 1.0 (fValue). */
+struct Action;
-struct action
+namespace recorder
{
- int chan; // channel index, i.e. Channel->index
- int type;
- int frame; // redundant info, used by helper functions
- float fValue; // used only for envelopes (volumes, vst params).
- uint32_t iValue; // used only for MIDI events
-};
-
-/* Composite
-A group of two actions (keypress+keyrel, muteon+muteoff). */
+using ActionMap = std::map<Frame, std::vector<const Action*>>;
-struct Composite
-{
- action a1;
- action a2;
-};
+void debug();
+/* init
+Initializes the recorder: everything starts from here. */
-/* frames
-Frame counter sentinel. It tells which frames contain actions. E.g.:
- frames[0] = 155 // some actions on frame 155
- frames[1] = 2048 // some actions on frame 2048
-It always matches 'global''s size: frames.size() == global.size() */
+void init(pthread_mutex_t* mixerMutex);
-extern std::vector<int> frames;
+/* clearAll
+Deletes all recorded actions. */
-/* global
-Contains the actual actions. E.g.:
- global[0] = <actions>
- global[1] = <actions> */
+void clearAll();
-extern std::vector<std::vector<action*>> global;
-/* TODO - this frames vs global madness must be replaced with a map:
-std::map<int, vector<actions>> */
+/* clearChannel
+Clears all actions from a channel. */
-extern bool active;
-extern bool sortedActions; // are actions sorted via sortActions()?
+void clearChannel(int channel);
-/* init
- * everything starts from here. */
+/* clearActions
+Clears the actions by type from a channel. */
-void init();
+void clearActions(int channel, int type);
-/* hasActions
-Checks if the channel has at least one action recorded. Used after an
-action deletion. Type != -1: check if channel has actions of type 'type'.*/
-
-bool hasActions(int chanIndex, int type=-1);
-
-/* canRec
- * can a channel rec an action? Call this one BEFORE rec(). */
+/* deleteAction
+Deletes a specific action. */
-bool canRec(Channel* ch, bool clockRunning, bool mixerRecording);
+void deleteAction(const Action* a);
-/* rec
- * record an action. */
+/* updateKeyFrames
+Update all the key frames in the internal map of actions, according to a lambda
+function 'f'. */
-void rec(int chan, int action, int frame, uint32_t iValue=0, float fValue=0.0f);
+void updateKeyFrames(std::function<Frame(Frame old)> f);
-/* clearChan
- * clear all actions from a channel. */
+/* updateActionMap
+Replaces the current map of actions with a new one. Warning: 'am' will be moved
+as a replacement (no copy). */
-void clearChan(int chan);
+void updateActionMap(ActionMap&& am);
-/* clearAction
- * clear the 'action' action type from a channel. */
+/* updateEvent
+Changes the event in action 'a'. */
-void clearAction(int chan, char action);
+void updateEvent(const Action* a, MidiEvent e);
-/* deleteAction
- * delete ONE action. Useful in the action editor. 'type' can be a mask. */
+/* updateSiblings
+Changes previous and next actions in action 'a'. Mostly used for chained actions
+such as envelopes. */
-void deleteAction(int chan, int frame, char type, bool checkValues,
- pthread_mutex_t* mixerMutex, uint32_t iValue=0, float fValue=0.0);
+void updateSiblings(const Action* a, const Action* prev, const Action* next);
-/* deleteActions
-Deletes A RANGE of actions from frame_a to frame_b in channel 'chan' of type
-'type' (can be a bitmask). Exclusive range (frame_a, frame_b). */
+void updateActionId(int id);
-void deleteActions(int chan, int frame_a, int frame_b, char type,
- pthread_mutex_t* mixerMutex);
+/* hasActions
+Checks if the channel has at least one action recorded. */
-/* clearAll
- * delete everything. */
+bool hasActions(int channel, int type=0);
-void clearAll();
+/* isActive
+Is recorder recording something? */
-/* optimize
- * clear frames without actions. */
+bool isActive();
-void optimize();
+void enable();
+void disable();
-/* sortActions
- * sorts actions by frame, asc mode. */
+const Action* makeAction(int id, int channel, Frame frame, MidiEvent e);
-void sortActions();
+/* rec (1)
+Records an action and returns it. */
-/* updateBpm
- * reassign frames by calculating the new bpm value. */
+const Action* rec(int channel, Frame frame, MidiEvent e);
-void updateBpm(float oldval, float newval, int oldquanto);
+/* 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. */
-/* updateSamplerate
- * reassign frames taking in account the samplerate. If f_system ==
- * f_patch nothing changes, otherwise the conversion is mandatory. */
+void rec(const std::vector<const Action*>& actions);
-void updateSamplerate(int systemRate, int patchRate);
+/* forEachAction
+Applies a read-only callback on each action recorded. NEVER do anything inside
+the callback that might alter the ActionMap. */
-void expand(int old_fpb, int new_fpb);
-void shrink(int new_fpb);
+void forEachAction(std::function<void(const Action*)> f);
-/* getNextAction
-Returns the nearest action in chan 'chan' of type 'action' starting from
-'frame'. Action can be a bitmask. If iValue != 0 search for next action with
-iValue == iValue with 'mask' to ignore bytes. Useful for MIDI key_release.
-Mask example:
+/* getActionsOnFrame
+Returns a vector of actions recorded on frame 'f'. */
- iValue = 0x803D3F00
- mask = 0x0000FF00 // ignore byte 3
- action = 0x803D3200 // <--- this action will be found */
+std::vector<const Action*> getActionsOnFrame(Frame f);
-int getNextAction(int chan, char action, int frame, struct action** out,
- uint32_t iValue=0, uint32_t mask=0);
+/* getActionsOnChannel
+Returns a vector of actions belonging to channel 'ch'. */
-/* getAction
-Returns a pointer to action in chan 'chan' of type 'action' at frame 'frame'. */
+std::vector<const Action*> getActionsOnChannel(int ch);
-int getAction(int chan, char action, int frame, struct action** out);
+/* getClosestAction
+Given a frame 'f' returns the closest action. */
-/* getActionsOnFrame
-Returns a vector of actions that occur on frame 'frame'. */
+const Action* getClosestAction(int channel, Frame f, int type);
-std::vector<action*> getActionsOnFrame(int frame);
-/* start/stopOverdub
-These functions are used when you overwrite existing actions. For example:
-pressing Mute button on a channel with some existing mute actions. */
+int getLatestActionId();
-void startOverdub(int chan, char action, int frame, unsigned bufferSize);
-void stopOverdub(int currentFrame, int totalFrames, pthread_mutex_t* mixerMutex);
+/* getActionMap
+Returns a copy of the internal action map. Used only by recorderHandler. */
-/* forEachAction
-Applies a read-only callback on each action recorded. */
+ActionMap getActionMap();
-void forEachAction(std::function<void(const action*)> f);
}}}; // giada::m::recorder::
+
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <algorithm>
+#include <cmath>
+#include <cassert>
+#include "../utils/log.h"
+#include "../utils/ver.h"
+#include "recorder.h"
+#include "action.h"
+#include "clock.h"
+#include "const.h"
+#include "recorderHandler.h"
+
+
+namespace giada {
+namespace m {
+namespace recorderHandler
+{
+namespace
+{
+std::vector<const Action*> recs_;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+const Action* getActionById_(int id, const recorder::ActionMap& source)
+{
+ for (auto& kv : source)
+ for (const Action* action : kv.second)
+ 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->channel == a2->channel;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* 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, size_t i)
+{
+ for (auto it = recs_.begin() + i; it != recs_.end(); ++it) {
+
+ const Action* a2 = *it;
+
+ if (!areComposite_(a1, a2))
+ continue;
+
+ const_cast<Action*>(a1)->next = a2;
+ const_cast<Action*>(a2)->prev = a1;
+
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void consolidate_()
+{
+ for (auto it = recs_.begin(); it != recs_.end(); ++it)
+ consolidate_(*it, it - recs_.begin()); // Pass current index
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPatch_DEPR_(const std::vector<patch::action_t>& pactions)
+{
+ for (const patch::action_t paction : pactions)
+ recs_.push_back(recorder::makeAction(-1, paction.channel, paction.frame, MidiEvent(paction.event)));
+
+ consolidate();
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+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 oldval, float newval, int oldquanto)
+{
+ 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 ('scarto'): 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 = (old / newval) * oldval;
+ if (frame != 0) {
+ Frame delta = oldquanto % 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(int chanIndex, int newChanIndex)
+{
+ recorder::ActionMap temp = recorder::getActionMap();
+
+ bool cloned = false;
+ int actionId = recorder::getLatestActionId();
+
+ recorder::forEachAction([&](const Action* a)
+ {
+ if (a->channel == chanIndex) {
+ Action* clone = new Action(*a);
+ clone->id = ++actionId;
+ clone->channel = newChanIndex;
+ temp[clone->frame].push_back(clone);
+ cloned = true;
+ }
+ });
+
+ recorder::updateActionId(actionId);
+ recorder::updateActionMap(std::move(temp));
+
+ return cloned;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void liveRec(int channel, MidiEvent e)
+{
+ assert(e.isNoteOnOff()); // Can't record any other kind of events for now
+ recs_.push_back(recorder::makeAction(-1, channel, clock::getCurrentFrame(), e));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void consolidate()
+{
+ consolidate_();
+ recorder::rec(recs_);
+ recs_.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void writePatch(int chanIndex, std::vector<patch::action_t>& pactions)
+{
+ recorder::forEachAction([&] (const Action* a)
+ {
+ if (a->channel != chanIndex)
+ return;
+ pactions.push_back(patch::action_t {
+ a->id,
+ a->channel,
+ a->frame,
+ a->event.getRaw(),
+ a->prev != nullptr ? a->prev->id : -1,
+ a->next != nullptr ? a->next->id : -1
+ });
+ });
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void readPatch(const std::vector<patch::action_t>& pactions)
+{
+ if (u::ver::isLess(patch::versionMajor, patch::versionMinor, patch::versionPatch, 0, 15, 3)) {
+ readPatch_DEPR_(pactions);
+ return;
+ }
+
+ recorder::ActionMap temp = recorder::getActionMap();
+
+ /* First pass: add actions with no relationship (no prev/next). */
+
+ for (const patch::action_t paction : pactions) {
+ temp[paction.frame].push_back(recorder::makeAction(
+ paction.id,
+ paction.channel,
+ paction.frame,
+ MidiEvent(paction.event)));
+ recorder::updateActionId(paction.id + 1);
+ }
+
+ /* Second pass: fill in previous and next actions, if any. */
+
+ for (const patch::action_t paction : pactions) {
+ if (paction.next == -1 && paction.prev == -1)
+ continue;
+ Action* curr = const_cast<Action*>(getActionById_(paction.id, temp));
+ assert(curr != nullptr);
+ if (paction.next != -1) {
+ curr->next = getActionById_(paction.next, temp);
+ assert(curr->next != nullptr);
+ }
+ if (paction.prev != -1) {
+ curr->prev = getActionById_(paction.prev, temp);
+ assert(curr->prev != nullptr);
+ }
+ }
+
+ recorder::updateActionMap(std::move(temp));
+}
+}}}; // giada::m::recorderHandler::
+
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_RECORDER_HANDLER_H
+#define G_RECORDER_HANDLER_H
+
+
+#include "midiEvent.h"
+#include "patch.h"
+
+
+namespace giada {
+namespace m
+{
+struct Action;
+
+namespace recorderHandler
+{
+bool isBoundaryEnvelopeAction(const Action* a);
+
+/* updateBpm
+Changes actions position by calculating the new bpm value. */
+
+void updateBpm(float oldval, float newval, int oldquanto);
+
+/* 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 'chanIndex', giving them a new channel index. Returns
+whether any action has been cloned. */
+
+bool cloneActions(int chanIndex, int newChanIndex);
+
+/* liveRec
+Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
+
+void liveRec(int channel, MidiEvent e);
+
+void consolidate();
+
+void writePatch(int chanIndex, std::vector<patch::action_t>& pactions);
+void readPatch(const std::vector<patch::action_t>& pactions);
+
+}}}; // giada::m::recorderHandler::
+
+
+#endif
using std::string;
-using namespace giada;
-using namespace giada::m;
+namespace giada {
+namespace m
+{
SampleChannel::SampleChannel(bool inputMonitor, int bufferSize)
: Channel (ChannelType::SAMPLE, ChannelStatus::EMPTY, bufferSize),
- mode (ChannelMode::SINGLE_BASIC),
- wave (nullptr),
- tracker (0),
- trackerPreview (0),
- shift (0),
- qWait (false),
- inputMonitor (inputMonitor),
- boost (G_DEFAULT_BOOST),
- pitch (G_DEFAULT_PITCH),
- begin (0),
- end (0),
- midiInReadActions(0x0),
- midiInPitch (0x0),
- rsmp_state (nullptr)
+ mode (ChannelMode::SINGLE_BASIC),
+ wave (nullptr),
+ tracker (0),
+ trackerPreview (0),
+ shift (0),
+ qWait (false),
+ inputMonitor (inputMonitor),
+ boost (G_DEFAULT_BOOST),
+ pitch (G_DEFAULT_PITCH),
+ begin (0),
+ end (0),
+ midiInReadActions(0x0),
+ midiInPitch (0x0),
+ rsmp_state (nullptr)
{
rsmp_state = src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr);
if (rsmp_state == nullptr) {
/* -------------------------------------------------------------------------- */
-void SampleChannel::parseEvents(m::mixer::FrameEvents fe)
+void SampleChannel::parseEvents(mixer::FrameEvents fe)
{
sampleChannelProc::parseEvents(this, fe);
sampleChannelRec::parseEvents(this, fe);
/* -------------------------------------------------------------------------- */
-void SampleChannel::process(giada::m::AudioBuffer& out,
- const giada::m::AudioBuffer& in, bool audible, bool running)
+void SampleChannel::setSolo(bool value)
+{
+ sampleChannelProc::setSolo(this, value);
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleChannel::process(AudioBuffer& out, const AudioBuffer& in,
+ bool audible, bool running)
{
sampleChannelProc::process(this, out, in, audible, running);
}
/* -------------------------------------------------------------------------- */
-void SampleChannel::readPatch(const string& basePath, int i)
+void SampleChannel::readPatch(const string& basePath, const patch::channel_t& pch)
{
- Channel::readPatch("", i);
- channelManager::readPatch(this, basePath, i);
+ Channel::readPatch("", pch);
+ channelManager::readPatch(this, basePath, pch);
}
void SampleChannel::empty()
{
status = ChannelStatus::EMPTY;
- begin = 0;
- end = 0;
- tracker = 0;
- volume = G_DEFAULT_VOL;
- boost = G_DEFAULT_BOOST;
- hasActions = false;
+ begin = 0;
+ end = 0;
+ tracker = 0;
+ volume = G_DEFAULT_VOL;
+ boost = G_DEFAULT_BOOST;
+ hasActions = false;
delete wave;
wave = nullptr;
sendMidiLstatus();
wave = w;
begin = 0;
end = wave->getSize() - 1;
- name = wave->getBasename();
sendMidiLstatus();
}
/* -------------------------------------------------------------------------- */
-int SampleChannel::fillBuffer(giada::m::AudioBuffer& dest, int start, int offset)
+int SampleChannel::fillBuffer(AudioBuffer& dest, int start, int offset)
{
if (pitch == 1.0) return fillBufferCopy(dest, start, offset);
else return fillBufferResampled(dest, start, offset);
/* -------------------------------------------------------------------------- */
-int SampleChannel::fillBufferResampled(giada::m::AudioBuffer& dest, int start, int offset)
+int SampleChannel::fillBufferResampled(AudioBuffer& dest, int start, int offset)
{
rsmp_data.data_in = wave->getFrame(start); // Source data
rsmp_data.input_frames = end - start; // How many readable frames
/* -------------------------------------------------------------------------- */
-int SampleChannel::fillBufferCopy(giada::m::AudioBuffer& dest, int start, int offset)
+int SampleChannel::fillBufferCopy(AudioBuffer& dest, int start, int offset)
{
int used = dest.countFrames() - offset;
if (used + start > wave->getSize())
bool SampleChannel::isOnLastFrame() const
{
return tracker >= end;
-}
\ No newline at end of file
+}
+
+}} // giada::m::
#include "channel.h"
-class Patch;
class Wave;
+namespace giada {
+namespace m
+{
class SampleChannel : public Channel
{
public:
void copy(const Channel* src, pthread_mutex_t* pluginMutex) override;
void prepareBuffer(bool running) override;
- void parseEvents(giada::m::mixer::FrameEvents fe) override;
- void process(giada::m::AudioBuffer& out, const giada::m::AudioBuffer& in,
- bool audible, bool running) override;
- void readPatch(const std::string& basePath, int i) override;
+ void parseEvents(mixer::FrameEvents fe) override;
+ void process(AudioBuffer& out, const AudioBuffer& in, bool audible, bool running) override;
+ void readPatch(const std::string& basePath, const patch::channel_t& pch) override;
void writePatch(int i, bool isProject) override;
void start(int frame, bool doQuantize, int velocity) override;
bool recordKill() override;
void recordStop() override;
void setMute(bool value) override;
+ void setSolo(bool value) override;
void startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) override;
void stopReadingActions(bool running, bool treatRecsAsLoops,
bool recsStopOnChanHalt) override;
Returns how many frames have been used from the original Wave data. It also
resamples data if pitch != 1.0f. */
- int fillBuffer(giada::m::AudioBuffer& dest, int start, int offset);
+ int fillBuffer(AudioBuffer& dest, int start, int offset);
/* pushWave
Adds a new wave to an existing channel. */
/* bufferPreview
Extra buffer for audio preview. */
- giada::m::AudioBuffer bufferPreview;
+ AudioBuffer bufferPreview;
- giada::ChannelMode mode;
+ ChannelMode mode;
Wave* wave;
int tracker; // chan position
SRC_STATE* rsmp_state;
SRC_DATA rsmp_data;
- int fillBufferResampled(giada::m::AudioBuffer& dest, int start, int offset);
- int fillBufferCopy (giada::m::AudioBuffer& dest, int start, int offset);
+ int fillBufferResampled(AudioBuffer& dest, int start, int offset);
+ int fillBufferCopy (AudioBuffer& dest, int start, int offset);
};
+}} // giada::m::
+
+
#endif
#include "pluginHost.h"
#include "sampleChannel.h"
#include "sampleChannelProc.h"
+#include "mixerHandler.h"
namespace giada {
bool running)
{
assert(out.countSamples() == ch->buffer.countSamples());
- assert(in.countSamples() == ch->buffer.countSamples());
+ if (in.isAllocd())
+ assert(in.countSamples() == ch->buffer.countSamples());
/* If armed and input buffer is not empty (i.e. input device available) and
input monitor is on, copy input buffer to channel buffer: this enables the
void setMute(SampleChannel* ch, bool value)
{
ch->mute = value;
+
+ // This is for processing playing_inaudible
+ ch->sendMidiLstatus();
+
ch->sendMidiLmute();
}
/* -------------------------------------------------------------------------- */
+void setSolo(SampleChannel* ch, bool value)
+{
+ ch->solo = value;
+ m::mh::updateSoloCount();
+
+ // This is for processing playing_inaudible
+ for (Channel* channel : mixer::channels)
+ channel->sendMidiLstatus();
+
+ ch->sendMidiLsolo();
+
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity)
{
/* For one-shot modes, velocity drives the internal volume. */
void prepareBuffer(SampleChannel* ch, bool running)
{
- if (!ch->hasData())
- return;
+ namespace um = u::math;
+
ch->buffer.clear();
- if (ch->isPlaying()) {
- int framesUsed = ch->fillBuffer(ch->buffer, ch->tracker, 0);
- ch->tracker += framesUsed;
- if (ch->isOnLastFrame())
- onLastFrame_(ch, framesUsed * (1 / ch->pitch), running);
+
+ if (!ch->hasData() || !ch->isPlaying())
+ return;
+
+ Frame framesUsed = ch->fillBuffer(ch->buffer, ch->tracker, 0);
+ ch->tracker += framesUsed;
+
+ /* The "framesUsed * (1 / ch->pitch)" operation might yield results greater
+ than the current buffer size. So clamping is mandatory. */
+
+ if (ch->isOnLastFrame()) {
+ Frame min = 0;
+ Frame max = ch->buffer.countFrames() - 1;
+ framesUsed = static_cast<Frame>(framesUsed * (1 / ch->pitch));
+ onLastFrame_(ch, um::bound(framesUsed, min, max, max), running);
}
+
}
if (ch->isPreview())
processPreview_(ch, out);
}
-}}};
\ No newline at end of file
+}}};
#include "types.h"
+namespace giada {
+namespace m
+{
class SampleChannel;
-
-namespace giada {
-namespace m {
namespace sampleChannelProc
{
/**/
void parseEvents(SampleChannel* ch, mixer::FrameEvents ev);
/**/
-void process(SampleChannel* ch, giada::m::AudioBuffer& out,
- const giada::m::AudioBuffer& in, bool audible, bool running);
+void process(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in,
+ bool audible, bool running);
/* kill
Stops a channel abruptly. */
void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity);
void setMute(SampleChannel* ch, bool value);
+void setSolo(SampleChannel* ch, bool value);
}}};
-#endif
\ No newline at end of file
+#endif
#include <cassert>
+#include "../utils/math.h"
+#include "recorder.h"
+#include "recorderHandler.h"
#include "const.h"
#include "conf.h"
#include "clock.h"
+#include "action.h"
#include "kernelAudio.h"
#include "sampleChannel.h"
#include "sampleChannelRec.h"
bool recorderCanRec_(SampleChannel* ch)
{
- return recorder::canRec(ch, clock::isRunning(), mixer::recording);
+ /* Can record on a channel if:
+ - recorder is on
+ - mixer is running
+ - mixer is not recording a take somewhere
+ - channel is MIDI or SAMPLE type with data in it */
+
+ return recorder::isActive() && clock::isRunning() && !mixer::recording &&
+ (ch->type == ChannelType::MIDI || (ch->type == ChannelType::SAMPLE && ch->hasData()));
}
/* calcVolumeEnv
Computes any changes in volume done via envelope tool. */
-void calcVolumeEnv_(SampleChannel* ch, int globalFrame)
+void calcVolumeEnv_(SampleChannel* ch, const Action* a1)
{
- /* method: check this frame && next frame, then calculate delta */
+ assert(a1 != nullptr);
+ assert(a1->next != nullptr);
- recorder::action* a0 = nullptr;
- recorder::action* a1 = nullptr;
- int res;
+ const Action* a2 = a1->next;
- /* get this action on frame 'frame'. It's unlikely that the action
- * is not found. */
+ double vf1 = u::math::map<int, double>(a1->event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
+ double vf2 = u::math::map<int, double>(a2->event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
- res = recorder::getAction(ch->index, G_ACTION_VOLUME, globalFrame, &a0);
-
- assert(res != 0);
-
- /* get the action next to this one.
- * res == -1: a1 not found, this is the last one. Rewind the search
- * and use action at frame number 0 (actions[0]).
- * res == -2 G_ACTION_VOLUME not found. This should never happen */
-
- res = recorder::getNextAction(ch->index, G_ACTION_VOLUME, globalFrame, &a1);
- if (res == -1)
- res = recorder::getAction(ch->index, G_ACTION_VOLUME, 0, &a1);
-
- assert(res != -2);
-
- ch->volume_i = a0->fValue;
- ch->volume_d = ((a1->fValue - a0->fValue) / (a1->frame - a0->frame)) * 1.003f;
+ ch->volume_i = vf1;
+ ch->volume_d = a2->frame == a1->frame ? 0 : (vf2 - vf1) / (a2->frame - a1->frame);
}
/* -------------------------------------------------------------------------- */
-void parseAction_(SampleChannel* ch, const recorder::action* a, int localFrame,
- int globalFrame)
+void parseAction_(SampleChannel* ch, const Action* a, int localFrame, int globalFrame)
{
if (!ch->readActions)
return;
- switch (a->type) {
- case G_ACTION_KEYPRESS:
+ switch (a->event.getStatus()) {
+ case MidiEvent::NOTE_ON:
if (ch->isAnySingleMode()) {
ch->start(localFrame, false, 0);
/* This is not a user-generated event, so fill the first chunk of buffer.
ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, localFrame);
}
break;
- case G_ACTION_KEYREL:
+ case MidiEvent::NOTE_OFF:
if (ch->isAnySingleMode())
ch->stop();
break;
- case G_ACTION_KILL:
+ case MidiEvent::NOTE_KILL:
if (ch->isAnySingleMode())
ch->kill(localFrame);
break;
- case G_ACTION_VOLUME:
- calcVolumeEnv_(ch, globalFrame);
+ case MidiEvent::ENVELOPE:
+ calcVolumeEnv_(ch, a);
break;
}
}
if (!recorderCanRec_(ch))
return;
- /* SINGLE_PRESS mode needs overdub. Also, disable reading actions while
- overdubbing. */
- if (ch->mode == ChannelMode::SINGLE_PRESS) {
- recorder::startOverdub(ch->index, G_ACTION_KEYS, clock::getCurrentFrame(),
- kernelAudio::getRealBufSize());
+ /* Disable reading actions while recording SINGLE_PRESS mode. */
+ if (ch->mode == ChannelMode::SINGLE_PRESS)
ch->readActions = false;
- }
- else
- recorder::rec(ch->index, G_ACTION_KEYPRESS, clock::getCurrentFrame());
+
+ recorderHandler::liveRec(ch->index, MidiEvent(MidiEvent::NOTE_ON, 0, 0));
ch->hasActions = true;
}
quantize_(ch, fe.quantoPassed);
if (fe.onFirstBeat)
onFirstBeat_(ch, conf::recsStopOnChanHalt);
- for (const recorder::action* action : fe.actions)
- if (action->chan == ch->index)
+ for (const Action* action : fe.actions)
+ if (action->channel == ch->index)
parseAction_(ch, action, fe.frameLocal, fe.frameGlobal);
}
bool recordKill(SampleChannel* ch)
{
- /* Don't record G_ACTION_KILL actions for LOOP channels. */
+ /* Don't record NOTE_KILL actions for LOOP channels. */
if (recorderCanRec_(ch) && !ch->isAnyLoopMode()) {
- recorder::rec(ch->index, G_ACTION_KILL, clock::getCurrentFrame());
+ recorder::rec(ch->index, clock::getCurrentFrame(), MidiEvent(MidiEvent::NOTE_KILL, 0, 0));
ch->hasActions = true;
}
return true;
{
/* Record a stop event only if channel is SINGLE_PRESS. For any other mode
the stop event is meaningless. */
- if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS) {
- recorder::stopOverdub(clock::getCurrentFrame(), clock::getFramesInLoop(),
- &mixer::mutex);
- }
+ if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS)
+ recorderHandler::liveRec(ch->index, MidiEvent(MidiEvent::NOTE_OFF, 0, 0));
}
#define G_SAMPLE_CHANNEL_REC_H
-class SampleChannel;
namespace giada {
-namespace m {
+namespace m
+{
+class SampleChannel;
+
namespace sampleChannelRec
{
void parseEvents(SampleChannel* ch, mixer::FrameEvents fe);
Wave::Wave(const Wave& other)
: m_rate (other.m_rate),
m_bits (other.m_bits),
- m_logical (true), // a cloned wave does not exist on disk
+ m_logical (true), // A cloned wave does not exist on disk
m_edited (false),
m_path (other.m_path)
{
buffer.alloc(other.getSize(), other.getChannels());
- buffer.copyData(other.getFrame(0), other.getSize(), other.getChannels());
+ buffer.copyData(other.getFrame(0), other.getSize());
}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "../core/clock.h"
+#include "../core/const.h"
+#include "../core/sampleChannel.h"
+#include "../core/midiChannel.h"
+#include "../core/recorderHandler.h"
+#include "../core/recorder.h"
+#include "../core/action.h"
+#include "recorder.h"
+#include "actionEditor.h"
+
+
+using std::vector;
+
+
+namespace giada {
+namespace c {
+namespace actionEditor
+{
+namespace
+{
+Frame fixVerticalEnvActions_(Frame f, const m::Action* a1, const m::Action* a2)
+{
+ if (a1->frame == f) f += 1;
+ else if (a2->frame == f) f -= 1;
+ if (a1->frame == f || a2->frame == f)
+ return -1;
+ return f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* recordFirstEnvelopeAction_
+First action ever? Add actions at boundaries. */
+
+void recordFirstEnvelopeAction_(int channel, Frame frame, int value)
+{
+ namespace mr = m::recorder;
+
+ 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(channel, 0, e1);
+ const m::Action* a2 = mr::rec(channel, frame, e2);
+ const m::Action* a3 = mr::rec(channel, m::clock::getFramesInLoop() - 1, e1);
+ mr::updateSiblings(a1, a3, a2); // Circular loop (begin)
+ mr::updateSiblings(a2, a1, a3);
+ mr::updateSiblings(a3, a2, a1); // Circular loop (end)
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+/* recordNonFirstEnvelopeAction_
+Find action right before frame 'frame' and inject a new action in there.
+Vertical envelope points are forbidden. */
+
+void recordNonFirstEnvelopeAction_(int channel, Frame frame, int value)
+{
+ namespace mr = m::recorder;
+
+ m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
+ const m::Action* a1 = mr::getClosestAction(channel, frame, m::MidiEvent::ENVELOPE);
+ const m::Action* a3 = a1->next;
+ assert(a1 != nullptr);
+ assert(a3 != nullptr);
+ frame = fixVerticalEnvActions_(frame, a1, a3);
+ if (frame == -1) // Vertical points, nothing to do here
+ return;
+ const m::Action* a2 = mr::rec(channel, frame, e2);
+ mr::updateSiblings(a2, a1, a3);
+}
+}; // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void recordMidiAction(m::MidiChannel* ch, 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. */
+
+ int overflow = f2 - (m::clock::getFramesInLoop());
+ if (overflow > 0) {
+ f2 -= overflow;
+ f1 -= overflow;
+ }
+
+ m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity);
+ m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity);
+
+ const m::Action* a1 = mr::rec(ch->index, f1, e1);
+ const m::Action* a2 = mr::rec(ch->index, f2, e2);
+
+ mr::updateSiblings(a1, nullptr, a2);
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteMidiAction(m::MidiChannel* ch, const m::Action* a)
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ assert(a != nullptr);
+ assert(a->event.getStatus() == m::MidiEvent::NOTE_ON);
+
+ /* Send a note-off first in case we are deleting it in a middle of a
+ key_on/key_off sequence. */
+
+ if (a->next != nullptr) {
+ ch->sendMidi(a->next, 0);
+ mr::deleteAction(a->next);
+ }
+ mr::deleteAction(a);
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateMidiAction(m::MidiChannel* ch, const m::Action* a, int note, int velocity,
+ Frame f1, Frame f2)
+{
+ namespace mr = m::recorder;
+
+ mr::deleteAction(a->next);
+ mr::deleteAction(a);
+
+ recordMidiAction(ch, note, velocity, f1, f2);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordSampleAction(const m::SampleChannel* ch, int type, Frame f1, Frame f2)
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ if (ch->mode == ChannelMode::SINGLE_PRESS) {
+ m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::NOTE_ON, 0, 0);
+ m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::NOTE_OFF, 0, 0);
+ const m::Action* a1 = mr::rec(ch->index, f1, e1);
+ const m::Action* a2 = mr::rec(ch->index, f2 == 0 ? f1 + G_DEFAULT_ACTION_SIZE : f2, e2);
+ mr::updateSiblings(a1, nullptr, a2);
+ }
+ else {
+ m::MidiEvent e1 = m::MidiEvent(type, 0, 0);
+ mr::rec(ch->index, f1, e1);
+ }
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateSampleAction(m::SampleChannel* ch, const m::Action* a, int type, Frame f1,
+ Frame f2)
+{
+ namespace mr = m::recorder;
+
+ if (ch->mode == ChannelMode::SINGLE_PRESS)
+ mr::deleteAction(a->next);
+ mr::deleteAction(a);
+
+ recordSampleAction(ch, type, f1, f2);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void recordEnvelopeAction(m::Channel* ch, int frame, 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(ch->index, m::MidiEvent::ENVELOPE))
+ recordFirstEnvelopeAction_(ch->index, frame, value);
+ else
+ recordNonFirstEnvelopeAction_(ch->index, frame, value);
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteEnvelopeAction(m::Channel* ch, const m::Action* a)
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+ namespace mrh = m::recorderHandler;
+
+ assert(a != nullptr);
+
+ /* Delete a boundary action wipes out everything. If is volume, remember to
+ restore _i and _d members in channel. */
+
+ if (mrh::isBoundaryEnvelopeAction(a)) {
+ if (a->isVolumeEnvelope()) {
+ ch->volume_i = 1.0;
+ ch->volume_d = 0.0;
+ }
+ mr::clearActions(ch->index, a->event.getStatus());
+ return;
+ }
+
+ const m::Action* a1 = a->prev;
+ const m::Action* a3 = a->next;
+
+ /* Original status: a1--->a--->a3
+ Modified status: a1-------->a3 */
+
+ mr::deleteAction(a);
+ mr::updateSiblings(a1, a1->prev, a3);
+ mr::updateSiblings(a3, a1, a3->next);
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateEnvelopeAction(m::Channel* ch, const m::Action* a, int frame, int value)
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+ namespace mrh = m::recorderHandler;
+
+ assert(a != nullptr);
+
+ /* 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, m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value));
+ else {
+ deleteEnvelopeAction(ch, a);
+ recordEnvelopeAction(ch, frame, value);
+ }
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void deleteSampleAction(m::SampleChannel* ch, const m::Action* a)
+{
+ namespace mr = m::recorder;
+ namespace cr = c::recorder;
+
+ assert(a != nullptr);
+
+ if (a->next != nullptr) // For ChannelMode::SINGLE_PRESS combo
+ mr::deleteAction(a->next);
+ mr::deleteAction(a);
+
+ cr::updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+vector<const m::Action*> getActions(const m::Channel* ch)
+{
+ return m::recorder::getActionsOnChannel(ch->index);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateVelocity(const m::MidiChannel* ch, const m::Action* a, int value)
+{
+ namespace mr = m::recorder;
+
+ m::MidiEvent event(a->event);
+ event.setVelocity(value);
+
+ mr::updateEvent(a, event);
+}
+}}}; // giada::c::actionEditor::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2018 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_ACTION_EDITOR_H
+#define G_GLUE_ACTION_EDITOR_H
+
+
+#include <vector>
+#include "../core/types.h"
+
+
+namespace giada {
+namespace m
+{
+class Action;
+class SampleChannel;
+class MidiChannel;
+}
+namespace c {
+namespace actionEditor
+{
+std::vector<const m::Action*> getActions(const m::Channel* ch);
+
+/* MIDI actions. */
+
+void recordMidiAction(m::MidiChannel* ch, int note, int velocity, Frame f1, Frame f2=0);
+void deleteMidiAction(m::MidiChannel* ch, const m::Action* a);
+void updateMidiAction(m::MidiChannel* ch, const m::Action* a, int note, int velocity,
+ Frame f1, Frame f2);
+void updateVelocity(const m::MidiChannel* ch, const m::Action* a, int value);
+
+/* Sample Actions. */
+
+void recordSampleAction(const m::SampleChannel* ch, int type, Frame f1, Frame f2=0);
+void deleteSampleAction(m::SampleChannel* ch, const m::Action* a);
+void updateSampleAction(m::SampleChannel* ch, const m::Action* a, int type, Frame f1, Frame f2=0);
+
+/* Envelope actions (only volume for now). */
+
+void recordEnvelopeAction(m::Channel* ch, int frame, int value);
+void deleteEnvelopeAction(m::Channel* ch, const m::Action* a);
+void updateEnvelopeAction(m::Channel* ch, const m::Action* a, int frame, int value);
+}}}; // giada::c::actionEditor::
+
+#endif
#include "../core/channel.h"
#include "../core/sampleChannel.h"
#include "../core/midiChannel.h"
+#include "../core/recorder.h"
#include "../core/plugin.h"
#include "../core/waveManager.h"
#include "main.h"
namespace c {
namespace channel
{
-int loadChannel(SampleChannel* ch, const string& fname)
+int loadChannel(m::SampleChannel* ch, const string& fname)
{
using namespace giada::m;
/* -------------------------------------------------------------------------- */
-Channel* addChannel(int column, ChannelType type, int size)
+m::Channel* addChannel(int column, ChannelType type, int size)
{
- Channel* ch = m::mh::addChannel(type);
+ m::Channel* ch = m::mh::addChannel(type);
geChannel* gch = G_MainWin->keyboard->addChannel(column, ch, size);
ch->guiChannel = gch;
return ch;
/* -------------------------------------------------------------------------- */
-void deleteChannel(Channel* ch)
+void deleteChannel(m::Channel* ch)
{
using namespace giada::m;
if (!gdConfirmWin("Warning", "Delete channel: are you sure?"))
return;
- recorder::clearChan(ch->index);
+ recorder::clearChannel(ch->index);
ch->hasActions = false;
#ifdef WITH_VST
pluginHost::freeStack(pluginHost::CHANNEL, &mixer::mutex, ch);
/* -------------------------------------------------------------------------- */
-void freeChannel(Channel* ch)
+void freeChannel(m::Channel* ch)
{
if (ch->isPlaying()) {
if (!gdConfirmWin("Warning", "This action will stop the channel: are you sure?"))
return;
G_MainWin->keyboard->freeChannel(ch->guiChannel);
- m::recorder::clearChan(ch->index);
+ m::recorder::clearChannel(ch->index);
ch->empty();
/* delete any related subwindow */
/* -------------------------------------------------------------------------- */
-void toggleArm(Channel* ch, bool gui)
+void toggleArm(m::Channel* ch, bool gui)
{
ch->armed = !ch->armed;
if (!gui)
/* -------------------------------------------------------------------------- */
-void toggleInputMonitor(Channel* ch)
+void toggleInputMonitor(m::Channel* ch)
{
- SampleChannel* sch = static_cast<SampleChannel*>(ch);
+ m::SampleChannel* sch = static_cast<m::SampleChannel*>(ch);
sch->inputMonitor = !sch->inputMonitor;
}
/* -------------------------------------------------------------------------- */
-int cloneChannel(Channel* src)
+int cloneChannel(m::Channel* src)
{
using namespace giada::m;
/* -------------------------------------------------------------------------- */
-void setVolume(Channel* ch, float v, bool gui, bool editor)
+void setVolume(m::Channel* ch, float v, bool gui, bool editor)
{
ch->volume = v;
/* -------------------------------------------------------------------------- */
-void setPitch(SampleChannel* ch, float val)
+void setPitch(m::SampleChannel* ch, float val)
{
ch->setPitch(val);
gdSampleEditor* gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
/* -------------------------------------------------------------------------- */
-void setPanning(SampleChannel* ch, float val)
+void setPanning(m::SampleChannel* ch, float val)
{
ch->setPan(val);
gdSampleEditor* gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
/* -------------------------------------------------------------------------- */
-void toggleMute(Channel* ch, bool gui)
+void toggleMute(m::Channel* ch, bool gui)
{
ch->setMute(!ch->mute);
if (!gui) {
/* -------------------------------------------------------------------------- */
-void toggleSolo(Channel* ch, bool gui)
+void toggleSolo(m::Channel* ch, bool gui)
{
- ch->solo = !ch->solo;
- m::mh::updateSoloCount();
+ ch->setSolo(!ch->solo);
if (!gui) {
Fl::lock();
ch->guiChannel->solo->value(ch->solo);
/* -------------------------------------------------------------------------- */
-void kill(Channel* ch)
+void kill(m::Channel* ch)
{
ch->kill(0); // on frame 0: it's a user-generated event
}
/* -------------------------------------------------------------------------- */
-void setBoost(SampleChannel* ch, float val)
+void setBoost(m::SampleChannel* ch, float val)
{
ch->setBoost(val);
gdSampleEditor *gdEditor = static_cast<gdSampleEditor*>(gu_getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
/* -------------------------------------------------------------------------- */
-void setName(Channel* ch, const string& name)
+void setName(m::Channel* ch, const string& name)
{
ch->name = name;
ch->guiChannel->update();
/* -------------------------------------------------------------------------- */
-void toggleReadingActions(Channel* ch, bool gui)
+void toggleReadingActions(m::Channel* ch, bool gui)
{
/* When you call startReadingRecs with conf::treatRecsAsLoops, the
/* -------------------------------------------------------------------------- */
-void startReadingActions(Channel* ch, bool gui)
+void startReadingActions(m::Channel* ch, bool gui)
{
using namespace giada::m;
/* -------------------------------------------------------------------------- */
-void stopReadingActions(Channel* ch, bool gui)
+void stopReadingActions(m::Channel* ch, bool gui)
{
using namespace giada::m;
}
}
-}}}; // giada::c::channel::
\ No newline at end of file
+}}}; // giada::c::channel::
-/* -----------------------------------------------------------------------------
+ /* -----------------------------------------------------------------------------
*
* Giada - Your Hardcore Loopmachine
*
#include "../core/types.h"
-class Channel;
-class SampleChannel;
class gdSampleEditor;
namespace giada {
-namespace c {
+namespace m
+{
+class Channel;
+class SampleChannel;
+}
+namespace c {
namespace channel
{
/* addChannel
Adds an empty new channel to the stack. Returns the new channel. */
-Channel* addChannel(int column, ChannelType type, int size);
+m::Channel* addChannel(int column, ChannelType type, int size);
/* loadChannel
Fills an existing channel with a wave. */
-int loadChannel(SampleChannel* ch, const std::string& fname);
+int loadChannel(m::SampleChannel* ch, const std::string& fname);
/* deleteChannel
Removes a channel from Mixer. */
-void deleteChannel(Channel* ch);
+void deleteChannel(m::Channel* ch);
/* freeChannel
Unloads the sample from a sample channel. */
-void freeChannel(Channel* ch);
+void freeChannel(m::Channel* ch);
/* cloneChannel
Makes an exact copy of Channel *ch. */
-int cloneChannel(Channel* ch);
+int cloneChannel(m::Channel* ch);
/* toggle/set*
Toggles or set several channel properties. If gui == true the signal comes from
a manual interaction on the GUI, otherwise it's a MIDI/Jack/external signal. */
-void toggleArm(Channel* ch, bool gui=true);
-void toggleInputMonitor(Channel* ch);
-void kill(Channel* ch);
-void toggleMute(Channel* ch, bool gui=true);
-void toggleSolo(Channel* ch, bool gui=true);
-void setVolume(Channel* ch, float v, bool gui=true, bool editor=false);
-void setName(Channel* ch, const std::string& name);
-void setPitch(SampleChannel* ch, float val);
-void setPanning(SampleChannel* ch, float val);
-void setBoost(SampleChannel* ch, float val);
+void toggleArm(m::Channel* ch, bool gui=true);
+void toggleInputMonitor(m::Channel* ch);
+void kill(m::Channel* ch);
+void toggleMute(m::Channel* ch, bool gui=true);
+void toggleSolo(m::Channel* ch, bool gui=true);
+void setVolume(m::Channel* ch, float v, bool gui=true, bool editor=false);
+void setName(m::Channel* ch, const std::string& name);
+void setPitch(m::SampleChannel* ch, float val);
+void setPanning(m::SampleChannel* ch, float val);
+void setBoost(m::SampleChannel* ch, float val);
/* toggleReadingRecs
Handles the 'R' button. If gui == true the signal comes from an user interaction
on the GUI, otherwise it's a MIDI/Jack/external signal. */
-void toggleReadingActions(Channel* ch, bool gui=true);
-void startReadingActions(Channel* ch, bool gui=true);
-void stopReadingActions(Channel* ch, bool gui=true);
+void toggleReadingActions(m::Channel* ch, bool gui=true);
+void startReadingActions(m::Channel* ch, bool gui=true);
+void stopReadingActions(m::Channel* ch, bool gui=true);
}}}; // giada::c::channel::
#include "../core/clock.h"
#include "../core/sampleChannel.h"
#include "../core/midiChannel.h"
+#include "../core/recorderHandler.h"
#include "main.h"
#include "channel.h"
#include "transport.h"
namespace giada {
-namespace c {
+namespace c {
namespace io
{
-void keyPress(Channel* ch, bool ctrl, bool shift, int velocity)
+void keyPress(m::Channel* ch, bool ctrl, bool shift, int velocity)
{
/* Everything occurs on frame 0 here: they are all user-generated events. */
if (ctrl)
/* -------------------------------------------------------------------------- */
-void keyRelease(Channel* ch, bool ctrl, bool shift)
+void keyRelease(m::Channel* ch, bool ctrl, bool shift)
{
if (!ctrl && !shift) {
ch->recordStop();
void startStopActionRec(bool gui)
{
- m::recorder::active ? stopActionRec(gui) : startActionRec(gui);
+ m::recorder::isActive() ? stopActionRec(gui) : startActionRec(gui);
}
void startActionRec(bool gui)
{
- using namespace giada::m;
-
- if (kernelAudio::getStatus() == false)
+ if (m::kernelAudio::getStatus() == false)
return;
- recorder::active = true;
+ m::recorder::enable();
- if (!clock::isRunning())
- glue_startSeq(false); // update gui
+ if (!m::clock::isRunning())
+ c::transport::startSeq(false); // update gui
if (!gui) {
Fl::lock();
void stopActionRec(bool gui)
{
- /* Stop the recorder and sort newly recorder actions. */
-
- m::recorder::active = false;
- m::recorder::sortActions();
+ m::recorder::disable();
+ m::recorderHandler::consolidate();
- for (Channel* ch : m::mixer::channels)
- {
+ for (m::Channel* ch : m::mixer::channels) {
if (ch->type == ChannelType::MIDI)
continue;
G_MainWin->keyboard->setChannelWithActions(static_cast<geSampleChannel*>(ch->guiChannel));
}
if (!clock::isRunning())
- glue_startSeq(false); // update gui anyway
+ transport::startSeq(false); // update gui anyway
Fl::lock();
if (!gui)
#define G_GLUE_IO_H
-class Channel;
-class SampleChannel;
-class MidiChannel;
-
namespace giada {
-namespace c {
+namespace m
+{
+class Plugin;
+class Channel;
+}
+namespace c {
namespace io
{
/* keyPress / keyRelease
event comes from the main window (mouse, keyboard or MIDI), otherwise the event
comes from the action recorder. */
-void keyPress (Channel* ch, bool ctrl, bool shift, int velocity);
-void keyRelease(Channel* ch, bool ctrl, bool shift);
+void keyPress (m::Channel* ch, bool ctrl, bool shift, int velocity);
+void keyRelease(m::Channel* ch, bool ctrl, bool shift);
/* start/stopActionRec
Handles the action recording. If gui == true the signal comes from an user
#include "../core/clock.h"
#include "../core/kernelMidi.h"
#include "../core/kernelAudio.h"
+#include "../core/recorder.h"
+#include "../core/recorderHandler.h"
#include "../core/conf.h"
#include "../core/const.h"
#ifdef WITH_VST
float vPre = clock::getBpm();
clock::setBpm(f);
- recorder::updateBpm(vPre, f, clock::getQuanto());
+ recorderHandler::updateBpm(vPre, f, clock::getQuanto());
mixer::allocVirtualInput(clock::getFramesInLoop());
gu_refreshActionEditor();
/* -------------------------------------------------------------------------- */
-void glue_setBeats(int beats, int bars, bool expand)
+void glue_setBeats(int beats, int bars)
{
/* Never change this stuff while recording audio */
if (mixer::recording)
return;
- /* Temp vars to store old data (they are necessary) */
-
- int oldBeats = clock::getBeats();
- unsigned oldTotalFrames = clock::getFramesInLoop();
-
clock::setBeats(beats);
clock::setBars(bars);
clock::updateFrameBars();
mixer::allocVirtualInput(clock::getFramesInLoop());
- /* Update recorded actions, if 'expand' required and an expansion is taking
- place. */
-
- if (expand && clock::getBeats() > oldBeats)
- recorder::expand(oldTotalFrames, clock::getFramesInLoop());
-
G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars());
gu_refreshActionEditor(); // in case the action editor is open
}
/* -------------------------------------------------------------------------- */
-void glue_rewindSeq(bool gui, bool notifyJack)
-{
- mh::rewindSequencer();
-
- /* FIXME - potential desync when Quantizer is enabled from this point on.
- Mixer would wait, while the following calls would be made regardless of its
- state. */
-
-#ifdef __linux__
- if (notifyJack)
- kernelAudio::jackSetPosition(0);
-#endif
-
- if (conf::midiSync == MIDI_SYNC_CLOCK_M)
- kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void glue_quantize(int val)
{
clock::setQuantize(val);
ch->empty();
ch->guiChannel->reset();
}
- recorder::init();
+ recorder::clearAll();
return;
}
void glue_clearAllActions()
{
- recorder::init();
+ recorder::clearAll();
for (Channel* ch : mixer::channels)
ch->hasActions = false;
gu_updateControls();
mixer::close();
clock::init(conf::samplerate, conf::midiTCfps);
mixer::init(clock::getFramesInLoop(), kernelAudio::getRealBufSize());
- recorder::init();
+ recorder::init(&mixer::mutex);
#ifdef WITH_VST
pluginHost::freeAllStacks(&mixer::channels, &mixer::mutex);
#endif
/* -------------------------------------------------------------------------- */
-/* never expand or shrink recordings (last param of setBeats = false):
- * this is live manipulation */
-
void glue_beatsMultiply()
{
- glue_setBeats(clock::getBeats() * 2, clock::getBars(), false);
+ glue_setBeats(clock::getBeats() * 2, clock::getBars());
}
void glue_beatsDivide()
{
- glue_setBeats(clock::getBeats() / 2, clock::getBars(), false);
+ glue_setBeats(clock::getBeats() / 2, clock::getBars());
}
void glue_setBpm(float v);
-void glue_setBeats(int beats, int bars, bool expand);
+void glue_setBeats(int beats, int bars);
void glue_quantize(int val);
void glue_setOutVol(float v, bool gui=true);
void glue_setInVol(float v, bool gui=true);
#ifdef WITH_VST
+namespace giada {
+namespace m
+{
class Plugin;
class Channel;
-
-
-namespace giada {
-namespace c {
+}
+namespace c {
namespace plugin
{
-Plugin* addPlugin(Channel* ch, int index, int stackType);
-void swapPlugins(Channel* ch, int indexP1, int indexP2, int stackType);
-void freePlugin(Channel* ch, int index, int stackType);
-void setParameter(Plugin* p, int index, float value, bool gui=true);
-void setProgram(Plugin* p, int index);
+m::Plugin* addPlugin(m::Channel* ch, int index, int stackType);
+void swapPlugins(m::Channel* ch, int indexP1, int indexP2, int stackType);
+void freePlugin(m::Channel* ch, int index, int stackType);
+void setParameter(m::Plugin* p, int index, float value, bool gui=true);
+void setProgram(m::Plugin* p, int index);
/* setPluginPathCb
Callback attached to the DirBrowser for adding new Plug-in search paths in the
#include "../core/clock.h"
#include "../core/kernelMidi.h"
#include "../core/channel.h"
+#include "../core/recorderHandler.h"
#include "../core/recorder.h"
+#include "../core/action.h"
#include "../core/mixer.h"
#include "../core/sampleChannel.h"
#include "../core/midiChannel.h"
namespace giada {
-namespace c {
+namespace c {
namespace recorder
{
-namespace
-{
-void updateChannel(geChannel* gch, bool refreshActionEditor=true)
-{
- gch->ch->hasActions = m::recorder::hasActions(gch->ch->index);
- if (gch->ch->type == ChannelType::SAMPLE) {
- geSampleChannel* gsch = static_cast<geSampleChannel*>(gch);
- gsch->ch->hasActions ? gsch->showActionButton() : gsch->hideActionButton();
- }
- if (refreshActionEditor)
- gu_refreshActionEditor(); // refresh a.editor window, it could be open
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
void clearAllActions(geChannel* gch)
{
if (!gdConfirmWin("Warning", "Clear all actions: are you sure?"))
return;
- m::recorder::clearChan(gch->ch->index);
+ gch->ch->kill(0);
+ m::recorder::clearChannel(gch->ch->index);
updateChannel(gch);
}
{
if (!gdConfirmWin("Warning", "Clear all volume actions: are you sure?"))
return;
- m::recorder::clearAction(gch->ch->index, G_ACTION_VOLUME);
+ m::recorder::clearActions(gch->ch->index, m::MidiEvent::ENVELOPE);
updateChannel(gch);
}
{
if (!gdConfirmWin("Warning", "Clear all start/stop actions: are you sure?"))
return;
- m::recorder::clearAction(gch->ch->index, G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL);
+ gch->ch->kill(0);
+ m::recorder::clearActions(gch->ch->index, m::MidiEvent::NOTE_ON);
+ m::recorder::clearActions(gch->ch->index, m::MidiEvent::NOTE_OFF);
+ m::recorder::clearActions(gch->ch->index, m::MidiEvent::NOTE_KILL);
updateChannel(gch);
}
/* -------------------------------------------------------------------------- */
-bool midiActionCanFit(int chan, int note, int frame_a, int frame_b)
-{
- namespace mr = m::recorder;
-
- /* TODO - This is insane, to say the least. Let's wait for recorder refactoring... */
-
- vector<mr::Composite> comps = getMidiActions(chan);
- for (mr::Composite c : comps)
- if (frame_b >= c.a1.frame && c.a2.frame >= frame_a && m::MidiEvent(c.a1.iValue).getNote() == note)
- return false;
- return true;
-}
-
-
-bool sampleActionCanFit(const SampleChannel* ch, int frame_a, int frame_b)
-{
- namespace mr = m::recorder;
-
- /* TODO - Even more insanity... Let's wait for recorder refactoring... */
-
- vector<mr::Composite> comps = getSampleActions(ch);
- for (mr::Composite c : comps)
- if (frame_b >= c.a1.frame && c.a2.frame >= frame_a)
- return false;
- return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordMidiAction(int chan, int note, int velocity, int frame_a, int frame_b)
-{
- if (frame_b == 0)
- frame_b = frame_a + G_DEFAULT_ACTION_SIZE;
-
- /* Avoid frame overflow. */
-
- int overflow = frame_b - (m::clock::getFramesInLoop());
- if (overflow > 0) {
- frame_b -= overflow;
- frame_a -= overflow;
- }
-
- /* Prepare MIDI events. Due to some nasty restrictions on the ancient Recorder,
- checks for overlapping are done by the caller. TODO ... */
-
- m::MidiEvent event_a = m::MidiEvent(m::MidiEvent::NOTE_ON, note, velocity);
- m::MidiEvent event_b = m::MidiEvent(m::MidiEvent::NOTE_OFF, note, velocity);
-
- m::recorder::rec(chan, G_ACTION_MIDI, frame_a, event_a.getRaw());
- m::recorder::rec(chan, G_ACTION_MIDI, frame_b, event_b.getRaw());
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void deleteMidiAction(MidiChannel* ch, m::recorder::action a1, m::recorder::action a2)
-{
- m::recorder::deleteAction(ch->index, a1.frame, G_ACTION_MIDI, true,
- &m::mixer::mutex, a1.iValue, 0.0);
-
- /* If action 1 is not orphaned, send a note-off first in case we are deleting
- it in a middle of a key_on/key_off sequence. Conversely, orphaned actions
- should not play, so no need to fire the note-off. */
-
- if (a2.frame != -1) {
- ch->sendMidi(a2.iValue);
- m::recorder::deleteAction(ch->index, a2.frame, G_ACTION_MIDI, true,
- &m::mixer::mutex, a2.iValue, 0.0);
- }
-
- ch->hasActions = m::recorder::hasActions(ch->index);
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordSampleAction(SampleChannel* ch, int type, int frame_a, int frame_b)
-{
- if (ch->mode == ChannelMode::SINGLE_PRESS) {
- m::recorder::rec(ch->index, G_ACTION_KEYPRESS, frame_a);
- m::recorder::rec(ch->index, G_ACTION_KEYREL, frame_b == 0 ? frame_a + G_DEFAULT_ACTION_SIZE : frame_b);
- }
- else
- m::recorder::rec(ch->index, type, frame_a);
-
- updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordEnvelopeAction(Channel* ch, int type, int frame, float fValue)
+void updateChannel(geChannel* gch, bool refreshActionEditor)
{
- namespace mr = m::recorder;
-
- if (!mr::hasActions(ch->index, type)) { // First action ever? Add actions at boundaries.
- mr::rec(ch->index, type, 0, 0, 1.0);
- mr::rec(ch->index, type, m::clock::getFramesInLoop() - 1, 0, 1.0);
- }
- mr::rec(ch->index, type, frame, 0, fValue);
-
- updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void deleteEnvelopeAction(Channel* ch, m::recorder::action a, bool moved)
-{
- namespace mr = m::recorder;
-
- /* Deleting first or last action: clear everything. Otherwise delete the
- selected action only. */
-
- if (!moved && (a.frame == 0 || a.frame == m::clock::getFramesInLoop() - 1))
- mr::clearAction(ch->index, a.type);
- else
- mr::deleteAction(ch->index, a.frame, a.type, false, &m::mixer::mutex);
-
- updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void deleteSampleAction(SampleChannel* ch, m::recorder::action a1,
- m::recorder::action a2)
-{
- namespace mr = m::recorder;
-
- /* if SINGLE_PRESS delete both the keypress and the keyrelease pair. */
-
- if (ch->mode == ChannelMode::SINGLE_PRESS) {
- mr::deleteAction(ch->index, a1.frame, G_ACTION_KEYPRESS, false, &m::mixer::mutex);
- mr::deleteAction(ch->index, a2.frame, G_ACTION_KEYREL, false, &m::mixer::mutex);
- }
- else
- mr::deleteAction(ch->index, a1.frame, a1.type, false, &m::mixer::mutex);
-
- updateChannel(ch->guiChannel, /*refreshActionEditor=*/false);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-vector<m::recorder::Composite> getSampleActions(const SampleChannel* ch)
-{
- namespace mr = m::recorder;
-
- vector<mr::Composite> out;
-
- mr::sortActions();
- mr::forEachAction([&](const mr::action* a1)
- {
- /* Exclude:
- - actions beyond clock::getFramesInLoop();
- - actions that don't belong to channel ch;
- - actions != G_ACTION_KEYPRESS, G_ACTION_KEYREL or G_ACTION_KILL;
- - G_ACTION_KEYREL actions in a SINGLE_PRESS context. */
-
- if (a1->frame > m::clock::getFramesInLoop() ||
- a1->chan != ch->index ||
- a1->type & ~(G_ACTION_KEYPRESS | G_ACTION_KEYREL | G_ACTION_KILL) ||
- (ch->mode == ChannelMode::SINGLE_PRESS && a1->type == G_ACTION_KEYREL))
- return;
-
- mr::Composite cmp;
- cmp.a1 = *a1;
- cmp.a2.frame = -1;
-
- /* If SINGLE_PRESS mode and the current action is G_ACTION_KEYPRESS, let's
- fetch the corresponding G_ACTION_KEYREL. */
-
- if (ch->mode == ChannelMode::SINGLE_PRESS && a1->type == G_ACTION_KEYPRESS) {
- m::recorder::action* a2 = nullptr;
- mr::getNextAction(ch->index, G_ACTION_KEYREL, a1->frame, &a2);
- if (a2 != nullptr)
- cmp.a2 = *a2;
- }
-
- out.push_back(cmp);
- });
-
- return out;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-vector<m::recorder::action> getEnvelopeActions(const Channel* ch, int type)
-{
- namespace mr = m::recorder;
-
- vector<mr::action> out;
-
- mr::sortActions();
- mr::forEachAction([&](const mr::action* a)
- {
- /* Exclude:
- - actions beyond clock::getFramesInLoop();
- - actions that don't belong to channel ch;
- - actions with wrong type. */
-
- if (a->frame > m::clock::getFramesInLoop() ||
- a->chan != ch->index ||
- a->type != type)
- return;
-
- out.push_back(*a);
- });
-
- return out;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setVelocity(const Channel* ch, m::recorder::action a, int value)
-{
- /* TODO - this is super ugly: delete the action and add a new one with the
- modified values. This shit will go away as soon as we'll refactor m::recorder
- for good. */
-
- m::MidiEvent event = m::MidiEvent(a.iValue);
- event.setVelocity(value);
-
- m::recorder::deleteAction(ch->index, a.frame, G_ACTION_MIDI, true,
- &m::mixer::mutex, a.iValue, 0.0);
- m::recorder::rec(ch->index, G_ACTION_MIDI, a.frame, event.getRaw());
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-vector<m::recorder::Composite> getMidiActions(int chan)
-{
- vector<m::recorder::Composite> out;
-
- m::recorder::sortActions();
-
- for (unsigned i=0; i<m::recorder::frames.size(); i++) {
-
- if (m::recorder::frames.at(i) > m::clock::getFramesInLoop())
- continue;
-
- for (unsigned j=0; j<m::recorder::global.at(i).size(); j++) {
-
- m::recorder::action* a1 = m::recorder::global.at(i).at(j);
- m::recorder::action* a2 = nullptr;
-
- m::MidiEvent a1midi(a1->iValue);
-
- /* Skip action if:
- - does not belong to this channel
- - is not a MIDI action (we only want MIDI things here)
- - is not a MIDI Note On type. We don't want any other kind of action here */
-
- if (a1->chan != chan || a1->type != G_ACTION_MIDI ||
- a1midi.getStatus() != m::MidiEvent::NOTE_ON)
- continue;
-
- /* Prepare the composite action. Action 1 exists for sure, so fill it up
- right away. */
-
- m::recorder::Composite cmp;
- cmp.a1 = *a1;
-
- /* Search for the next action. Must have: same channel, G_ACTION_MIDI,
- greater than a1->frame and with MIDI properties of note_off (0x80), same
- note of a1 and random velocity: we don't care about it (and so we mask it
- with 0x0000FF00). */
-
- m::recorder::getNextAction(chan, G_ACTION_MIDI, a1->frame, &a2,
- m::MidiEvent(m::MidiEvent::NOTE_OFF, a1midi.getNote(), 0x0).getRaw(),
- 0x0000FF00);
-
- /* If action 2 has been found, add it to the composite duo. Otherwise
- set the action 2 frame to -1: it should be intended as "orphaned". */
-
- if (a2 != nullptr)
- cmp.a2 = *a2;
- else
- cmp.a2.frame = -1;
-
- out.push_back(cmp);
- }
+ gch->ch->hasActions = m::recorder::hasActions(gch->ch->index);
+ if (gch->ch->type == ChannelType::SAMPLE) {
+ geSampleChannel* gsch = static_cast<geSampleChannel*>(gch);
+ gsch->ch->hasActions ? gsch->showActionButton() : gsch->hideActionButton();
}
-
- return out;
+ if (refreshActionEditor)
+ gu_refreshActionEditor(); // refresh a.editor window, it could be open
}
}}} // giada::c::recorder::
\ No newline at end of file
#define G_GLUE_RECORDER_H
-#include <vector>
-#include "../core/recorder.h"
-
-
-class SampleChannel;
-class MidiChannel;
class geChannel;
namespace giada {
-namespace c {
+namespace c {
namespace recorder
{
void clearAllActions(geChannel* gch);
void clearVolumeActions(geChannel* gch);
void clearStartStopActions(geChannel* gch);
-
-
-
-/* MOVE ALL THESE FUNCTIONS TO c::actionEditor*/
-
-
-bool midiActionCanFit(int chan, int note, int frame_a, int frame_b);
-bool sampleActionCanFit(const SampleChannel* ch, int frame_a, int frame_b);
-
-/* recordMidiAction
-Records a new MIDI action at frame_a. If frame_b == 0, uses the default action
-size. This function is designed for the Piano Roll (not for live recording). */
-
-void recordMidiAction(int chan, int note, int velocity, int frame_a, int frame_b=0);
-
-void recordEnvelopeAction(Channel* ch, int type, int frame, float fValue);
-
-void recordSampleAction(SampleChannel* ch, int type, int frame_a, int frame_b=0);
-
-void setVelocity(const Channel* ch, m::recorder::action a, int value);
-
-/* getMidiActions
-Returns a list of Composite actions, ready to be displayed in a MIDI note
-editor as pairs of NoteOn+NoteOff. */
-
-std::vector<m::recorder::Composite> getMidiActions(int channel);
-
-std::vector<m::recorder::action> getEnvelopeActions(const Channel* ch, int type);
-
-/* getSampleActions
-Returns a list of Composite actions, ready to be displayed in a Sample Action
-Editor. If actions are not keypress+keyrelease combos, the second action in
-the Composite struct if left empty (with action2.frame = -1). */
-
-std::vector<m::recorder::Composite> getSampleActions(const SampleChannel* ch);
-
-void deleteMidiAction(MidiChannel* ch, m::recorder::action a1, m::recorder::action a2);
-
-void deleteSampleAction(SampleChannel* ch, m::recorder::action a1,
- m::recorder::action a2);
-
-void deleteEnvelopeAction(Channel* ch, m::recorder::action a, bool moved);
-
+void updateChannel(geChannel* gch, bool refreshActionEditor=true);
}}} // giada::c::recorder::
#endif
#include "sampleEditor.h"
-extern gdMainWindow *G_MainWin;
+extern gdMainWindow* G_MainWin;
namespace giada {
/* -------------------------------------------------------------------------- */
-void setBeginEnd(SampleChannel* ch, int b, int e)
+void setBeginEnd(m::SampleChannel* ch, int b, int e)
{
ch->setBegin(b);
ch->setEnd(e);
/* -------------------------------------------------------------------------- */
-void cut(SampleChannel* ch, int a, int b)
+void cut(m::SampleChannel* ch, int a, int b)
{
copy(ch, a, b);
if (!m::wfx::cut(*ch->wave, a, b)) {
/* -------------------------------------------------------------------------- */
-void copy(SampleChannel* ch, int a, int b)
+void copy(m::SampleChannel* ch, int a, int b)
{
if (m_waveBuffer != nullptr)
delete m_waveBuffer;
/* -------------------------------------------------------------------------- */
-void paste(SampleChannel* ch, int a)
+void paste(m::SampleChannel* ch, int a)
{
if (!isWaveBufferFull()) {
gu_log("[sampleEditor::paste] Buffer is empty, nothing to paste\n");
/* -------------------------------------------------------------------------- */
-void silence(SampleChannel* ch, int a, int b)
+void silence(m::SampleChannel* ch, int a, int b)
{
m::wfx::silence(*ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void fade(SampleChannel* ch, int a, int b, int type)
+void fade(m::SampleChannel* ch, int a, int b, int type)
{
m::wfx::fade(*ch->wave, a, b, type);
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void smoothEdges(SampleChannel* ch, int a, int b)
+void smoothEdges(m::SampleChannel* ch, int a, int b)
{
m::wfx::smooth(*ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void reverse(SampleChannel* ch, int a, int b)
+void reverse(m::SampleChannel* ch, int a, int b)
{
m::wfx::reverse(*ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void normalizeHard(SampleChannel* ch, int a, int b)
+void normalizeHard(m::SampleChannel* ch, int a, int b)
{
m::wfx::normalizeHard(*ch->wave, a, b);
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void trim(SampleChannel* ch, int a, int b)
+void trim(m::SampleChannel* ch, int a, int b)
{
if (!m::wfx::trim(*ch->wave, a, b)) {
gdAlert("Unable to trim the sample!");
/* -------------------------------------------------------------------------- */
-void setPlayHead(SampleChannel* ch, int f)
+void setPlayHead(m::SampleChannel* ch, int f)
{
ch->trackerPreview = f;
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void setPreview(SampleChannel* ch, PreviewMode mode)
+void setPreview(m::SampleChannel* ch, PreviewMode mode)
{
ch->previewMode = mode;
gdSampleEditor* gdEditor = getSampleEditorWindow();
/* -------------------------------------------------------------------------- */
-void rewindPreview(SampleChannel* ch)
+void rewindPreview(m::SampleChannel* ch)
{
geWaveform* waveform = getSampleEditorWindow()->waveTools->waveform;
if (waveform->isSelected() && ch->trackerPreview != waveform->getSelectionA())
/* -------------------------------------------------------------------------- */
-void toNewChannel(SampleChannel* ch, int a, int b)
+void toNewChannel(m::SampleChannel* ch, int a, int b)
{
- SampleChannel* newCh = static_cast<SampleChannel*>(c::channel::addChannel(
+ m::SampleChannel* newCh = static_cast<m::SampleChannel*>(c::channel::addChannel(
ch->guiChannel->getColumnIndex(), ChannelType::SAMPLE, G_GUI_CHANNEL_H_1));
Wave* wave = nullptr;
/* -------------------------------------------------------------------------- */
-void shift(SampleChannel* ch, int offset)
+void shift(m::SampleChannel* ch, int offset)
{
m::wfx::shift(*ch->wave, offset - ch->shift);
ch->shift = offset;
#include "../core/types.h"
-class SampleChannel;
class geWaveform;
namespace giada {
-namespace c {
+namespace m
+{
+class SampleChannel;
+}
+namespace c {
namespace sampleEditor
{
/* setBeginEnd
Sets start/end points in the sample editor. */
-void setBeginEnd(SampleChannel* ch, int b, int e);
+void setBeginEnd(m::SampleChannel* ch, int b, int e);
-void cut(SampleChannel* ch, int a, int b);
-void copy(SampleChannel* ch, int a, int b);
+void cut(m::SampleChannel* ch, int a, int b);
+void copy(m::SampleChannel* ch, int a, int b);
/* paste
Pastes what's defined in m_copyBuffer into channel 'ch' at point 'a'. If
m_copyBuffer is empty, does nothing. */
-void paste(SampleChannel* ch, int a);
+void paste(m::SampleChannel* ch, int a);
-void trim(SampleChannel* ch, int a, int b);
-void reverse(SampleChannel* ch, int a, int b);
-void normalizeHard(SampleChannel* ch, int a, int b);
-void silence(SampleChannel* ch, int a, int b);
-void fade(SampleChannel* ch, int a, int b, int type);
-void smoothEdges(SampleChannel* ch, int a, int b);
-void shift(SampleChannel* ch, int offset);
+void trim(m::SampleChannel* ch, int a, int b);
+void reverse(m::SampleChannel* ch, int a, int b);
+void normalizeHard(m::SampleChannel* ch, int a, int b);
+void silence(m::SampleChannel* ch, int a, int b);
+void fade(m::SampleChannel* ch, int a, int b, int type);
+void smoothEdges(m::SampleChannel* ch, int a, int b);
+void shift(m::SampleChannel* ch, int offset);
bool isWaveBufferFull();
/* setPlayHead
Changes playhead's position. Used in preview. */
-void setPlayHead(SampleChannel* ch, int f);
+void setPlayHead(m::SampleChannel* ch, int f);
-void setPreview(SampleChannel* ch, PreviewMode mode);
-void rewindPreview(SampleChannel* ch);
+void setPreview(m::SampleChannel* ch, PreviewMode mode);
+void rewindPreview(m::SampleChannel* ch);
/* toNewChannel
Copies the selected range into a new sample channel. */
-void toNewChannel(SampleChannel* ch, int a, int b);
+void toNewChannel(m::SampleChannel* ch, int a, int b);
}}}; // giada::c::sampleEditor::
#endif
#include "../core/mixer.h"
#include "../core/mixerHandler.h"
#include "../core/channel.h"
+#include "../core/recorderHandler.h"
#include "../core/pluginHost.h"
#include "../core/plugin.h"
#include "../core/conf.h"
#ifdef WITH_VST
-static void glue_fillPatchGlobalsPlugins__(vector <Plugin*>* host, vector<m::patch::plugin_t>* patch)
+static void glue_fillPatchGlobalsPlugins__(vector <m::Plugin*>* host, vector<m::patch::plugin_t>* patch)
{
using namespace giada::m;
using namespace giada::m;
for (unsigned i=0; i<G_MainWin->keyboard->getTotalColumns(); i++) {
- geColumn *gCol = G_MainWin->keyboard->getColumn(i);
+ geColumn* gCol = G_MainWin->keyboard->getColumn(i);
patch::column_t pCol;
pCol.index = gCol->getIndex();
pCol.width = gCol->w();
for (int k=0; k<gCol->countChannels(); k++) {
- Channel *colChannel = gCol->getChannel(k);
- for (unsigned j=0; j<mixer::channels.size(); j++) {
- Channel *mixerChannel = mixer::channels.at(j);
+ Channel* colChannel = gCol->getChannel(k);
+ for (const Channel* mixerChannel : mixer::channels) {
if (colChannel == mixerChannel) {
pCol.channels.push_back(mixerChannel->index);
break;
}
-static string glue_makeUniqueSamplePath__(const string& base, const SampleChannel* ch)
+static string glue_makeUniqueSamplePath__(const string& base, const m::SampleChannel* ch)
{
using namespace giada::m;
return;
}
- /* Close all other windows. This prevents segfault if plugin windows GUIs are
+ /* Close all other windows. This prevents problems if plugin windows are
open. */
gu_closeAllSubwindows();
browser->setStatusBar(0.1f);
/* Add common stuff, columns and channels. Also increment the progress bar by
- 0.8 / total_channels steps. */
+ 0.8 / total_channels steps. */
float steps = 0.8 / patch::channels.size();
for (const patch::column_t& col : patch::columns) {
G_MainWin->keyboard->addColumn(col.width);
- unsigned k = 0;
for (const patch::channel_t& pch : patch::channels) {
if (pch.column == col.index) {
Channel* ch = c::channel::addChannel(pch.column, static_cast<ChannelType>(pch.type), pch.size);
- ch->readPatch(basePath, k);
+ ch->readPatch(basePath, pch);
}
browser->setStatusBar(steps);
- k++;
}
}
- /* Prepare Mixer. */
+ /* Prepare Mixer and Recorder. The latter has to recompute the actions'
+ positions if the current samplerate != patch samplerate.*/
mh::updateSoloCount();
mh::readPatch();
-
- /* Let recorder recompute the actions' positions if the current
- samplerate != patch samplerate. */
-
- recorder::updateSamplerate(conf::samplerate, patch::samplerate);
+ recorderHandler::updateSamplerate(conf::samplerate, patch::samplerate);
/* Save patchPath by taking the last dir of the broswer, in order to reuse it
the next time. */
if (fullPath.empty())
return;
- int res = c::channel::loadChannel(static_cast<SampleChannel*>(browser->getChannel()),
+ int res = c::channel::loadChannel(static_cast<m::SampleChannel*>(browser->getChannel()),
fullPath);
if (res == G_RES_OK) {
#define G_GLUE_STORAGE_H
-void glue_loadPatch (void *data);
-void glue_savePatch (void *data);
-void glue_saveProject(void *data);
-void glue_saveSample (void *data);
-void glue_loadSample (void *data);
+void glue_loadPatch (void* data);
+void glue_savePatch (void* data);
+void glue_saveProject(void* data);
+void glue_saveSample (void* data);
+void glue_loadSample (void* data);
#endif
#include "../gui/elems/mainWindow/mainTransport.h"
#include "../gui/dialogs/gd_mainWindow.h"
#include "../core/clock.h"
+#include "../core/conf.h"
+#include "../core/const.h"
#include "../core/kernelAudio.h"
+#include "../core/kernelMidi.h"
#include "../core/mixerHandler.h"
#include "../core/mixer.h"
#include "../core/recorder.h"
#include "transport.h"
-extern gdMainWindow *G_MainWin;
-
+extern gdMainWindow* G_MainWin;
+
using namespace giada::m;
-void glue_startStopSeq(bool gui)
+namespace giada {
+namespace c {
+namespace transport
+{
+void startStopSeq(bool gui)
{
- clock::isRunning() ? glue_stopSeq(gui) : glue_startSeq(gui);
+ clock::isRunning() ? stopSeq(gui) : startSeq(gui);
}
/* -------------------------------------------------------------------------- */
-void glue_startSeq(bool gui)
+void startSeq(bool gui)
{
clock::start();
#endif
if (!gui) {
- Fl::lock();
- G_MainWin->mainTransport->updatePlay(1);
- Fl::unlock();
+ Fl::lock();
+ G_MainWin->mainTransport->updatePlay(1);
+ Fl::unlock();
}
}
/* -------------------------------------------------------------------------- */
-void glue_stopSeq(bool gui)
+void stopSeq(bool gui)
{
mh::stopSequencer();
kernelAudio::jackStop();
#endif
- /* what to do if we stop the sequencer and some action recs are active?
- * Deactivate the button and delete any 'rec on' status */
+ /* What to do if we stop the sequencer and some action recs are active?
+ Deactivate the button and delete any 'rec on' status. */
- if (recorder::active) {
- recorder::active = false;
- Fl::lock();
- G_MainWin->mainTransport->updateRecAction(0);
- Fl::unlock();
+ if (recorder::isActive()) {
+ recorder::disable();
+ Fl::lock();
+ G_MainWin->mainTransport->updateRecAction(0);
+ Fl::unlock();
}
- /* if input recs are active (who knows why) we must deactivate them.
- * One might stop the sequencer while an input rec is running. */
+ /* If input recs are active (who knows why) we must deactivate them. One
+ might stop the sequencer while an input rec is running. */
if (mixer::recording) {
mh::stopInputRec();
- Fl::lock();
- G_MainWin->mainTransport->updateRecInput(0);
- Fl::unlock();
+ Fl::lock();
+ G_MainWin->mainTransport->updateRecInput(0);
+ Fl::unlock();
}
if (!gui) {
- Fl::lock();
- G_MainWin->mainTransport->updatePlay(0);
- Fl::unlock();
- }
+ Fl::lock();
+ G_MainWin->mainTransport->updatePlay(0);
+ Fl::unlock();
+ }
}
/* -------------------------------------------------------------------------- */
-void glue_startStopMetronome(bool gui)
+void rewindSeq(bool gui, bool notifyJack)
+{
+ mh::rewindSequencer();
+
+ /* FIXME - potential desync when Quantizer is enabled from this point on.
+ Mixer would wait, while the following calls would be made regardless of its
+ state. */
+
+#ifdef __linux__
+ if (notifyJack)
+ kernelAudio::jackSetPosition(0);
+#endif
+
+ if (conf::midiSync == MIDI_SYNC_CLOCK_M)
+ kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void startStopMetronome(bool gui)
{
mixer::metronome = !mixer::metronome;
if (!gui) {
Fl::unlock();
}
}
+
+}}} // giada::c::transport::
\ No newline at end of file
#define G_GLUE_TRANSPORT_H
-/* start, stop, rewind sequencer
-If gui == true the signal comes from an user interaction on the GUI,
-otherwise it's a MIDI/Jack/external signal. */
-
-void glue_startStopSeq(bool gui=true);
-void glue_startSeq(bool gui=true);
-void glue_stopSeq(bool gui=true);
-void glue_rewindSeq(bool gui=true, bool notifyJack=true);
-void glue_startStopMetronome(bool gui=true);
+namespace giada {
+namespace c {
+namespace transport
+{
+void startStopSeq(bool gui=true);
+void startSeq(bool gui=true);
+void stopSeq(bool gui=true);
+void rewindSeq(bool gui=true, bool notifyJack=true);
+void startStopMetronome(bool gui=true);
+}}} // giada::c::transport::
#endif
namespace giada {
namespace v
{
-gdBaseActionEditor::gdBaseActionEditor(Channel* ch)
+gdBaseActionEditor::gdBaseActionEditor(m::Channel* ch)
: gdWindow (640, 284),
ch (ch),
ratio (G_DEFAULT_ZOOM_RATIO)
/* -------------------------------------------------------------------------- */
+const std::vector<const m::Action*>& gdBaseActionEditor::getActions()
+{
+ return m_actions;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void gdBaseActionEditor::computeWidth()
{
fullWidth = frameToPixel(m::clock::getFramesInSeq());
void gdBaseActionEditor::zoomIn()
{
- if (ratio / 2 > MIN_RATIO) {
- ratio /= 2;
+ float ratioPrev = ratio;
+
+ ratio /= 2;
+ if (ratio < MIN_RATIO)
+ ratio = MIN_RATIO;
+
+ if (ratioPrev != ratio) {
rebuild();
centerViewportIn();
redraw();
}
- else
- ratio = MIN_RATIO;
}
void gdBaseActionEditor::zoomOut()
{
- if (ratio * 2 < MAX_RATIO) {
- ratio *= 2;
+ float ratioPrev = ratio;
+
+ ratio *= 2;
+ if (ratio > MAX_RATIO)
+ ratio = MAX_RATIO;
+
+ if (ratioPrev != ratio) {
rebuild();
centerViewportOut();
redraw();
}
- else
- ratio = MAX_RATIO;
}
int gdBaseActionEditor::getActionType() const
{
if (actionType->value() == 0)
- return G_ACTION_KEYPRESS;
+ return m::MidiEvent::NOTE_ON;
else
if (actionType->value() == 1)
- return G_ACTION_KEYREL;
+ return m::MidiEvent::NOTE_OFF;
else
if (actionType->value() == 2)
- return G_ACTION_KILL;
+ return m::MidiEvent::NOTE_KILL;
assert(false);
return -1;
#include "../window.h"
-class Channel;
class geChoice;
class geButton;
class geScroll;
namespace giada {
+namespace m
+{
+class Channel;
+class Action;
+}
namespace v
{
class geGridTool;
static constexpr float MIN_RATIO = 25.0f;
static constexpr float MAX_RATIO = 40000.0f;
- gdBaseActionEditor(Channel* ch);
+ std::vector<const m::Action*> m_actions;
+
+ gdBaseActionEditor(m::Channel* ch);
void zoomIn();
void zoomOut();
Frame pixelToFrame(Pixel p, bool snap=true) const;
int getActionType() const;
+ const std::vector<const m::Action*>& getActions();
+
geChoice* actionType;
geGridTool* gridTool;
geButton* zoomInBtn;
geButton* zoomOutBtn;
geScroll* viewport; // widget container
- Channel* ch;
+ m::Channel* ch;
float ratio;
- Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
+ Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range
};
}} // giada::v::
#include <string>
#include "../../../core/graphics.h"
#include "../../../core/midiChannel.h"
+#include "../../../glue/actionEditor.h"
#include "../../elems/basics/scroll.h"
#include "../../elems/basics/button.h"
#include "../../elems/basics/resizerBar.h"
namespace giada {
namespace v
{
-gdMidiActionEditor::gdMidiActionEditor(MidiChannel* ch)
+gdMidiActionEditor::gdMidiActionEditor(m::MidiChannel* ch)
: gdBaseActionEditor(ch)
{
computeWidth();
viewport = new geScroll(8, 36, w()-16, h()-44);
- ne = new geNoteEditor(viewport->x(), viewport->y(), this);
- viewport->add(ne);
- viewport->add(new geResizerBar(ne->x(), ne->y()+ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+ m_ne = new geNoteEditor(viewport->x(), viewport->y(), this);
+ m_ner = new geResizerBar(m_ne->x(), m_ne->y()+m_ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H);
+ viewport->add(m_ne);
+ viewport->add(m_ner);
- ve = new geVelocityEditor(viewport->x(), ne->y()+ne->h()+RESIZER_BAR_H, ch);
- viewport->add(ve);
- viewport->add(new geResizerBar(ve->x(), ve->y()+ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+ m_ve = new geVelocityEditor(viewport->x(), m_ne->y()+m_ne->h()+RESIZER_BAR_H, ch);
+ m_ver = new geResizerBar(m_ve->x(), m_ve->y()+m_ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H);
+ viewport->add(m_ve);
+ viewport->add(m_ver);
end();
prepareWindow();
+ rebuild();
}
void gdMidiActionEditor::rebuild()
{
+ m_actions = c::actionEditor::getActions(ch);
computeWidth();
- ne->rebuild();
- ve->rebuild();
+ m_ne->rebuild();
+ m_ner->size(m_ne->w(), m_ner->h());
+ m_ve->rebuild();
+ m_ver->size(m_ve->w(), m_ver->h());
}
}} // giada::v::
#include "baseActionEditor.h"
-class MidiChannel;
+class geResizerBar;
namespace giada {
{
private:
- geNoteEditor* ne;
- geVelocityEditor* ve;
+ geNoteEditor* m_ne;
+ geResizerBar* m_ner;
+
+ geVelocityEditor* m_ve;
+ geResizerBar* m_ver;
public:
- gdMidiActionEditor(MidiChannel* ch);
+ gdMidiActionEditor(m::MidiChannel* ch);
void rebuild() override;
};
#include <string>
#include "../../../core/const.h"
+#include "../../../core/midiEvent.h"
#include "../../../core/graphics.h"
#include "../../../core/sampleChannel.h"
+#include "../../../glue/actionEditor.h"
#include "../../elems/basics/scroll.h"
#include "../../elems/basics/button.h"
#include "../../elems/basics/resizerBar.h"
namespace giada {
namespace v
{
-gdSampleActionEditor::gdSampleActionEditor(SampleChannel* ch)
+gdSampleActionEditor::gdSampleActionEditor(m::SampleChannel* ch)
: gdBaseActionEditor(ch)
{
computeWidth();
viewport = new geScroll(8, 36, w()-16, h()-44);
- ac = new geSampleActionEditor(viewport->x(), viewport->y(), ch);
- viewport->add(ac);
- viewport->add(new geResizerBar(ac->x(), ac->y()+ac->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+ m_ae = new geSampleActionEditor(viewport->x(), viewport->y(), ch);
+ m_aer = new geResizerBar(m_ae->x(), m_ae->y()+m_ae->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H);
+ viewport->add(m_ae);
+ viewport->add(m_aer);
- vc = new geEnvelopeEditor(viewport->x(), ac->y()+ac->h()+RESIZER_BAR_H, G_ACTION_VOLUME, "volume", ch);
- viewport->add(vc);
- viewport->add(new geResizerBar(vc->x(), vc->y()+vc->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H));
+ m_ee = new geEnvelopeEditor(viewport->x(), m_ae->y()+m_ae->h()+RESIZER_BAR_H, "volume", ch);
+ m_eer = new geResizerBar(m_ee->x(), m_ee->y()+m_ee->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H);
+ viewport->add(m_ee);
+ viewport->add(m_eer);
end();
prepareWindow();
+ rebuild();
}
bool gdSampleActionEditor::canChangeActionType()
{
- SampleChannel* sch = static_cast<SampleChannel*>(ch);
+ m::SampleChannel* sch = static_cast<m::SampleChannel*>(ch);
return sch->mode != ChannelMode::SINGLE_PRESS && !sch->isAnyLoopMode();
}
void gdSampleActionEditor::rebuild()
{
+ m_actions = c::actionEditor::getActions(ch);
canChangeActionType() ? actionType->activate() : actionType->deactivate();
computeWidth();
- ac->rebuild();
- vc->rebuild();
+ m_ae->rebuild();
+ m_aer->size(m_ae->w(), m_aer->h());
+ m_ee->rebuild();
+ m_eer->size(m_ee->w(), m_eer->h());
}
}} // giada::v::
#include "baseActionEditor.h"
-class SampleChannel;
-class geSampleActionEditor;
-class geEnvelopeEditor;
+class geResizerBar;
namespace giada {
namespace v
{
+class geSampleActionEditor;
+class geEnvelopeEditor;
+
class gdSampleActionEditor : public gdBaseActionEditor
{
private:
- geSampleActionEditor* ac;
- geEnvelopeEditor* vc;
+ geSampleActionEditor* m_ae;
+ geResizerBar* m_aer;
+
+ geEnvelopeEditor* m_ee;
+ geResizerBar* m_eer;
bool canChangeActionType();
public:
- gdSampleActionEditor(SampleChannel* ch);
+ gdSampleActionEditor(m::SampleChannel* ch);
void rebuild() override;
};
gdBeatsInput::gdBeatsInput()
- : gdWindow(180, 60, "Beats")
+ : gdWindow(180, 36, "Beats")
{
if (conf::beatsX)
resize(conf::beatsX, conf::beatsY, w(), h());
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");
- resizeRec = new geCheck(8, 40, 12, 12, "resize recorded actions");
+ 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");
end();
beats->maximum_size(2);
ok->shortcut(FL_Enter);
ok->callback(cb_update, (void*)this);
-
- resizeRec->value(conf::resizeRecordings);
gu_setFavicon(this);
setId(WID_BEATS);
{
conf::beatsX = x();
conf::beatsY = y();
- conf::resizeRecordings = resizeRec->value();
}
{
if (!strcmp(beats->value(), "") || !strcmp(bars->value(), ""))
return;
- glue_setBeats(atoi(beats->value()), atoi(bars->value()), resizeRec->value());
+ glue_setBeats(atoi(beats->value()), atoi(bars->value()));
do_callback();
}
geInput* beats;
geInput* bars;
geButton* ok;
- geCheck* resizeRec;
public:
#include "../window.h"
+#include "../../../core/plugin.h"
+#include "../../../core/channel.h"
class Fl_Group;
-class Channel;
class geCheck;
class geBrowser;
class geButton;
{
protected:
- Channel* channel;
+ giada::m::Channel* channel;
Fl_Group* groupTop;
geCheck* hiddenFiles;
std::string getSelectedItem() const;
std::string getCurrentPath() const;
- Channel* getChannel() const;
+ giada::m::Channel* getChannel() const;
void fireCallback() const;
/* setStatusBar
#include "browserBase.h"
-class Channel;
-
-
class gdBrowserDir : public gdBrowserBase
{
private:
using std::string;
+using namespace giada;
gdBrowserLoad::gdBrowserLoad(int x, int y, int w, int h, const string& title,
- const string& path, void (*cb)(void*), Channel* ch)
+ const string& path, void (*cb)(void*), m::Channel* ch)
: gdBrowserBase(x, y, w, h, title, path, cb)
{
channel = ch;
#include "browserBase.h"
-class Channel;
-
-
class gdBrowserLoad : public gdBrowserBase
{
private:
public:
gdBrowserLoad(int x, int y, int w, int h, const std::string& title,
- const std::string& path, void (*callback)(void*), Channel* ch);
+ const std::string& path, void (*callback)(void*), giada::m::Channel* ch);
};
using std::string;
+using namespace giada::m;
gdBrowserSave::gdBrowserSave(int x, int y, int w, int h, const string& title,
#include "browserBase.h"
-class Channel;
class geInput;
gdBrowserSave(int x, int y, int w, int h, const std::string& title,
const std::string& path, const std::string& name, void (*callback)(void*),
- Channel* ch);
+ giada::m::Channel* ch);
std::string getName() const;
};
using namespace giada;
-gdChannelNameInput::gdChannelNameInput(Channel* ch)
+gdChannelNameInput::gdChannelNameInput(m::Channel* ch)
: gdWindow(400, 64, "New channel name"),
m_ch (ch)
{
#include "window.h"
-class Channel;
class geInput;
class geButton;
void cb_update();
void cb_cancel();
- Channel* m_ch;
+ giada::m::Channel* m_ch;
geInput* m_name;
geButton* m_ok;
public:
- gdChannelNameInput(Channel* ch);
+ gdChannelNameInput(giada::m::Channel* ch);
~gdChannelNameInput();
};
using std::string;
+using namespace giada;
-gdKeyGrabber::gdKeyGrabber(Channel *ch)
+gdKeyGrabber::gdKeyGrabber(m::Channel* ch)
: gdWindow(300, 126, "Key configuration"), ch(ch)
{
set_modal();
/* -------------------------------------------------------------------------- */
-void gdKeyGrabber::cb_clear (Fl_Widget *w, void *p) { ((gdKeyGrabber*)p)->__cb_clear(); }
-void gdKeyGrabber::cb_cancel(Fl_Widget *w, void *p) { ((gdKeyGrabber*)p)->__cb_cancel(); }
+void gdKeyGrabber::cb_clear (Fl_Widget* w, void* p) { ((gdKeyGrabber*)p)->cb_clear(); }
+void gdKeyGrabber::cb_cancel(Fl_Widget* w, void* p) { ((gdKeyGrabber*)p)->cb_cancel(); }
/* -------------------------------------------------------------------------- */
-void gdKeyGrabber::__cb_cancel()
+void gdKeyGrabber::cb_cancel()
{
do_callback();
}
/* -------------------------------------------------------------------------- */
-void gdKeyGrabber::__cb_clear()
+void gdKeyGrabber::cb_clear()
{
updateText(0);
setButtonLabel(0);
#include "window.h"
-class Channel;
class geBox;
class geButton;
{
private:
- Channel *ch;
+ giada::m::Channel* ch;
- geBox *text;
- geButton *clear;
- geButton *cancel;
+ geBox* text;
+ geButton* clear;
+ geButton* cancel;
- static void cb_clear (Fl_Widget *w, void *p);
- static void cb_cancel(Fl_Widget *w, void *p);
- inline void __cb_clear ();
- inline void __cb_cancel();
+ static void cb_clear (Fl_Widget* w, void* p);
+ static void cb_cancel(Fl_Widget* w, void* p);
+ void cb_clear ();
+ void cb_cancel();
void setButtonLabel(int key);
void updateText(int key);
public:
- gdKeyGrabber(Channel *ch);
+ gdKeyGrabber(giada::m::Channel* ch);
int handle(int e);
};
#include <FL/Fl.H>
#include "../../core/const.h"
+#include "../../core/conf.h"
#include "../../core/init.h"
#include "../../utils/gui.h"
#include "../elems/basics/boxtypes.h"
#include "gd_mainWindow.h"
-extern gdMainWindow *G_MainWin;
+extern gdMainWindow* G_MainWin;
+
+
+using namespace giada;
gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** argv)
Fl::set_boxtype(G_CUSTOM_UP_BOX, g_customUpBox, 1, 1, 2, 2);
Fl::set_boxtype(G_CUSTOM_DOWN_BOX, g_customDownBox, 1, 1, 2, 2);
- Fl::set_boxtype(FL_BORDER_BOX, G_CUSTOM_BORDER_BOX);
- Fl::set_boxtype(FL_UP_BOX, G_CUSTOM_UP_BOX);
- Fl::set_boxtype(FL_DOWN_BOX, G_CUSTOM_DOWN_BOX);
+ Fl::set_boxtype(FL_BORDER_BOX, G_CUSTOM_BORDER_BOX);
+ Fl::set_boxtype(FL_UP_BOX, G_CUSTOM_UP_BOX);
+ Fl::set_boxtype(FL_DOWN_BOX, G_CUSTOM_DOWN_BOX);
size_range(G_MIN_GUI_WIDTH, G_MIN_GUI_HEIGHT);
{
if (!gdConfirmWin("Warning", "Quit Giada: are you sure?"))
return;
- init_shutdown();
+
+ m::conf::mainWindowX = x();
+ m::conf::mainWindowY = y();
+ m::conf::mainWindowW = w();
+ m::conf::mainWindowH = h();
+
hide();
delete this;
}
#include "midiInputBase.h"
-class Channel;
class geScroll;
class geCheck;
class geChoice;
{
private:
- Channel* ch;
+ giada::m::Channel* ch;
geScroll* container;
geCheck* enable;
public:
- gdMidiInputChannel(Channel* ch);
+ gdMidiInputChannel(giada::m::Channel* ch);
~gdMidiInputChannel();
};
#include "midiOutputMidiCh.h"
-gdMidiOutputMidiCh::gdMidiOutputMidiCh(MidiChannel* ch)
+using namespace giada;
+
+
+gdMidiOutputMidiCh::gdMidiOutputMidiCh(m::MidiChannel* ch)
: gdMidiOutputBase(300, 168), ch(ch)
{
setTitle(ch->index+1);
class geCheck *enableOut;
class geChoice *chanListOut;
- class MidiChannel *ch;
+ class giada::m::MidiChannel *ch;
public:
- gdMidiOutputMidiCh(class MidiChannel *ch);
+ gdMidiOutputMidiCh(class giada::m::MidiChannel *ch);
};
#include "midiOutputSampleCh.h"
-gdMidiOutputSampleCh::gdMidiOutputSampleCh(SampleChannel* ch)
+using namespace giada;
+
+
+gdMidiOutputSampleCh::gdMidiOutputSampleCh(m::SampleChannel* ch)
: gdMidiOutputBase(300, 140), ch(ch)
{
setTitle(ch->index+1);
#include "midiOutputBase.h"
-class SampleChannel;
-
-
class gdMidiOutputSampleCh : public gdMidiOutputBase
{
private:
- SampleChannel* ch;
+ giada::m::SampleChannel* ch;
/* cb_close
Override parent method, we need to do more stuff on close. */
public:
- gdMidiOutputSampleCh(SampleChannel* ch);
+ gdMidiOutputSampleCh(giada::m::SampleChannel* ch);
};
#endif
using namespace giada::c;
-gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, int stackType, Channel *ch)
+gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, int stackType, Channel* ch)
: gdWindow(X, Y, W, H, "Available plugins"), ch(ch), stackType(stackType)
{
/* top area */
/* -------------------------------------------------------------------------- */
-void gdPluginChooser::cb_close(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_close(); }
-void gdPluginChooser::cb_add(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_add(); }
-void gdPluginChooser::cb_sort(Fl_Widget *v, void *p) { ((gdPluginChooser*)p)->__cb_sort(); }
+void gdPluginChooser::cb_close(Fl_Widget* v, void* p) { ((gdPluginChooser*)p)->cb_close(); }
+void gdPluginChooser::cb_add(Fl_Widget* v, void* p) { ((gdPluginChooser*)p)->cb_add(); }
+void gdPluginChooser::cb_sort(Fl_Widget* v, void* p) { ((gdPluginChooser*)p)->cb_sort(); }
/* -------------------------------------------------------------------------- */
-void gdPluginChooser::__cb_close()
+void gdPluginChooser::cb_close()
{
do_callback();
}
/* -------------------------------------------------------------------------- */
-void gdPluginChooser::__cb_sort()
+void gdPluginChooser::cb_sort()
{
pluginHost::sortPlugins(sortMethod->value());
browser->refresh();
/* -------------------------------------------------------------------------- */
-void gdPluginChooser::__cb_add()
+void gdPluginChooser::cb_add()
{
int index = browser->value() - 3; // subtract header lines
if (index < 0)
#include "window.h"
-class Channel;
class geChoice;
class geButton;
class geButton;
{
private:
- Channel *ch; // ch == nullptr ? masterOut
+ giada::m::Channel* ch; // ch == nullptr ? masterOut
int stackType;
- geChoice *sortMethod;
- geButton *addBtn;
- geButton *cancelBtn;
- gePluginBrowser *browser;
+ geChoice* sortMethod;
+ geButton* addBtn;
+ geButton* cancelBtn;
+ gePluginBrowser* browser;
- static void cb_close(Fl_Widget *w, void *p);
- static void cb_add (Fl_Widget *w, void *p);
- static void cb_sort (Fl_Widget *w, void *p);
- inline void __cb_close();
- inline void __cb_add ();
- inline void __cb_sort ();
+ static void cb_close(Fl_Widget* w, void* p);
+ static void cb_add (Fl_Widget* w, void* p);
+ static void cb_sort (Fl_Widget* w, void* p);
+ void cb_close();
+ void cb_add ();
+ void cb_sort ();
public:
- gdPluginChooser(int x, int y, int w, int h, int stackType, Channel *ch=nullptr);
- ~gdPluginChooser();
+ gdPluginChooser(int x, int y, int w, int h, int stackType, giada::m::Channel* ch=nullptr);
+ ~gdPluginChooser();
};
using namespace giada;
-gdPluginList::gdPluginList(int stackType, Channel* ch)
+gdPluginList::gdPluginList(int stackType, m::Channel* ch)
: gdWindow(468, 204), ch(ch), stackType(stackType)
{
using namespace giada::m;
class Fl_Scroll;
-class Channel;
class geButton;
public:
- Channel* ch; // ch == nullptr ? masterOut
+ giada::m::Channel* ch; // ch == nullptr ? masterOut
int stackType;
- gdPluginList(int stackType, Channel* ch=nullptr);
+ gdPluginList(int stackType, giada::m::Channel* ch=nullptr);
~gdPluginList();
/* special callback, passed to browser. When closed (i.e. plugin
#include "pluginWindow.h"
-gdPluginWindow::gdPluginWindow(Plugin* p)
+using namespace giada;
+
+
+gdPluginWindow::gdPluginWindow(m::Plugin* p)
: gdWindow(450, 156), m_plugin(p)
{
set_non_modal();
#define GD_PLUGIN_WINDOW_H
+#include "../../core/plugin.h"
#include "window.h"
-class Plugin;
class geBox;
class geSlider;
class geLiquidScroll;
{
private:
- Plugin* m_plugin;
+ giada::m::Plugin* m_plugin;
geLiquidScroll* m_list;
public:
- gdPluginWindow(Plugin* p);
+ gdPluginWindow(giada::m::Plugin* p);
void updateParameter(int index, bool changeSlider=false);
void updateParameters(bool changeSlider=false);
#endif
-class Plugin;
-
-
class gdPluginWindowGUI : public gdWindow
{
private:
- Plugin* m_plugin;
+ giada::m::Plugin* m_plugin;
static void cb_close (Fl_Widget* v, void* p);
static void cb_refresh(void* data);
public:
- gdPluginWindowGUI(Plugin* p);
+ gdPluginWindowGUI(giada::m::Plugin* p);
~gdPluginWindowGUI();
};
using namespace giada;
-gdSampleEditor::gdSampleEditor(SampleChannel* ch)
+gdSampleEditor::gdSampleEditor(m::SampleChannel* ch)
: gdWindow(640, 480),
ch(ch)
{
#define GD_EDITOR_H
+#include "../../core/sampleChannel.h"
#include "window.h"
public:
- gdSampleEditor(SampleChannel* ch);
+ gdSampleEditor(giada::m::SampleChannel* ch);
~gdSampleEditor();
void updateInfo();
geCheck* loop;
geBox* info;
- SampleChannel* ch;
+ giada::m::SampleChannel* ch;
};
namespace v
{
geBaseAction::geBaseAction(Pixel X, Pixel Y, Pixel W, Pixel H, bool resizable,
- giada::m::recorder::action a1, giada::m::recorder::action a2)
+ const m::Action* a1, const m::Action* a2)
: Fl_Box (X, Y, W, H),
m_resizable(resizable),
onRightEdge(false),
namespace giada {
+namespace m
+{
+class Action;
+}
namespace v
{
class geBaseAction : public Fl_Box
{
-private:
+protected:
bool m_resizable;
static const Pixel HANDLE_WIDTH = 6;
geBaseAction(Pixel x, Pixel y, Pixel w, Pixel h, bool resizable,
- m::recorder::action a1, m::recorder::action a2);
+ const m::Action* a1, const m::Action* a2);
int handle(int e) override;
bool altered;
Pixel pick;
- m::recorder::action a1;
- m::recorder::action a2;
+ const m::Action* a1;
+ const m::Action* a2;
};
}} // giada::v::
namespace giada {
namespace v
{
-geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, Channel* ch)
+geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, m::Channel* ch)
: Fl_Group(x, y, w, h),
- m_ch (ch),
- m_base (static_cast<gdBaseActionEditor*>(window())),
- m_action(nullptr)
+ m_ch (ch),
+ m_base (static_cast<gdBaseActionEditor*>(window())),
+ m_action(nullptr)
{
}
#include <FL/Fl_Group.H>
-class Channel;
-
-
namespace giada {
namespace v
{
protected:
- Channel* m_ch;
+ m::Channel* m_ch;
gdBaseActionEditor* m_base;
public:
- geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, Channel* ch);
+ geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, m::Channel* ch);
/* updateActions
Rebuild the actions widgets from scratch. */
#include "../../../utils/math.h"
#include "../../../core/const.h"
#include "../../../core/conf.h"
+#include "../../../core/action.h"
+#include "../../../core/recorder.h"
#include "../../../core/sampleChannel.h"
-#include "../../../glue/recorder.h"
+#include "../../../glue/actionEditor.h"
#include "../../dialogs/actionEditor/baseActionEditor.h"
#include "envelopePoint.h"
#include "envelopeEditor.h"
namespace giada {
namespace v
{
-geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, int actionType, const char* l,
- SampleChannel* ch)
-: geBaseActionEditor(x, y, 200, m::conf::envelopeEditorH, ch),
- m_actionType (actionType)
+geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l,
+ m::SampleChannel* ch)
+: geBaseActionEditor(x, y, 200, m::conf::envelopeEditorH, ch)
{
copy_label(l);
- rebuild();
}
for (int i=0; i<children(); i++) {
geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
if (m_action == nullptr)
- p->position(p->x(), valueToY(p->a1.fValue));
+ p->position(p->x(), valueToY(p->a1->event.getVelocity()));
if (i > 0) {
x2 = p->x() + side;
y2 = p->y() + side;
void geEnvelopeEditor::rebuild()
{
namespace mr = m::recorder;
- namespace cr = c::recorder;
+ namespace ca = c::actionEditor;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- vector<mr::action> actions = cr::getEnvelopeActions(m_ch, m_actionType);
-
- for (mr::action a : actions) {
- gu_log("[geEnvelopeEditor::rebuild] Action %d\n", a.frame);
- add(new geEnvelopePoint(frameToX(a.frame), valueToY(a.fValue), a));
+ for (const m::Action* a : m_base->getActions()) {
+ if (a->event.getStatus() != m::MidiEvent::ENVELOPE)
+ continue;
+ add(new geEnvelopePoint(frameToX(a->frame), valueToY(a->event.getVelocity()), a));
}
resizable(nullptr);
}
-Pixel geEnvelopeEditor::valueToY(float value) const
+Pixel geEnvelopeEditor::valueToY(int value) const
{
- return u::math::map<float, Pixel>(value, 0.0, 1.0, y() + (h() - geEnvelopePoint::SIDE), y());
+ return u::math::map<int, Pixel>(value, 0, G_MAX_VELOCITY, y() + (h() - geEnvelopePoint::SIDE), y());
}
-float geEnvelopeEditor::yToValue(Pixel pixel) const
+int geEnvelopeEditor::yToValue(Pixel pixel, Pixel offset) const
{
- return u::math::map<Pixel, float>(pixel, h() - geEnvelopePoint::SIDE, 0, 0.0, 1.0);
+ return u::math::map<Pixel, int>(pixel, h() - offset, 0, 0, G_MAX_VELOCITY);
}
void geEnvelopeEditor::onAddAction()
{
Frame f = m_base->pixelToFrame(Fl::event_x() - x());
- float v = yToValue(Fl::event_y() - y());
- c::recorder::recordEnvelopeAction(m_ch, m_actionType, f, v);
- rebuild();
+ int v = yToValue(Fl::event_y() - y());
+
+ c::actionEditor::recordEnvelopeAction(m_ch, f, v);
+
+ m_base->rebuild();
}
void geEnvelopeEditor::onDeleteAction()
{
- c::recorder::deleteEnvelopeAction(m_ch, m_action->a1, /*moved=*/false);
- rebuild();
+ c::actionEditor::deleteEnvelopeAction(m_ch, m_action->a1);
+
+ m_base->rebuild();
}
void geEnvelopeEditor::onRefreshAction()
{
Frame f = m_base->pixelToFrame((m_action->x() - x()) + geEnvelopePoint::SIDE / 2);
- float v = yToValue(m_action->y() - y());
- c::recorder::deleteEnvelopeAction(m_ch, m_action->a1, /*moved=*/true);
- c::recorder::recordEnvelopeAction(m_ch, m_actionType, f, v);
- rebuild();
+ float v = yToValue(m_action->y() - y(), geEnvelopePoint::SIDE);
+ c::actionEditor::updateEnvelopeAction(m_ch, m_action->a1, f, v);
+
+ m_base->rebuild();
}
}} // giada::v::
\ No newline at end of file
#include "baseActionEditor.h"
-class SampleChannel;
namespace giada {
+namespace m
+{
+class SampleChannel;
+}
namespace v
{
class geEnvelopePoint;
{
private:
- /* m_actionType
- What type of action this envelope editor is dealing with. */
-
- int m_actionType;
-
void onAddAction() override;
void onDeleteAction() override;
void onMoveAction() override;
void onRefreshAction() override;
Pixel frameToX(Frame frame) const;
- Pixel valueToY(float value) const;
- float yToValue(Pixel pixel) const;
+ Pixel valueToY(int value) const;
+ int yToValue(Pixel pixel, Pixel offset=0) const;
bool isFirstPoint() const;
bool isLastPoint() const;
public:
- geEnvelopeEditor(Pixel x, Pixel y, int actionType, const char* l, SampleChannel* ch);
+ geEnvelopeEditor(Pixel x, Pixel y, const char* l, m::SampleChannel* ch);
~geEnvelopeEditor();
void draw() override;
namespace giada {
namespace v
{
-geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, m::recorder::action a)
- : geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, {})
+geEnvelopePoint::geEnvelopePoint(Pixel X, Pixel Y, const m::Action* a)
+ : geBaseAction(X, Y, SIDE, SIDE, /*resizable=*/false, a, nullptr)
{
}
static const Pixel SIDE = 12;
- geEnvelopePoint(Pixel x, Pixel y, m::recorder::action a);
+ geEnvelopePoint(Pixel x, Pixel y, const m::Action* a);
void draw() override;
};
: geScroll(x, y, 200, 422),
m_base (base)
{
- pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, static_cast<MidiChannel*>(m_base->ch));
-
- rebuild();
+ pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, static_cast<m::MidiChannel*>(m_base->ch));
size(m_base->fullWidth, m::conf::pianoRollH);
#include <FL/fl_draw.H>
#include "../../../core/const.h"
+#include "../../../core/action.h"
#include "../../../core/midiEvent.h"
#include "../../../utils/math.h"
#include "pianoItem.h"
namespace giada {
namespace v
{
-gePianoItem::gePianoItem(Pixel X, Pixel Y, Pixel W, Pixel H, m::recorder::action a1,
- m::recorder::action a2)
+gePianoItem::gePianoItem(Pixel X, Pixel Y, Pixel W, Pixel H, const m::Action* a1,
+ const m::Action* a2)
: geBaseAction(X, Y, W, H, /*resizable=*/true, a1, a2),
- orphaned (a2.frame == -1)
+ m_ringLoop (a2 != nullptr && a1->frame > a2->frame),
+ m_orphaned (a2 == nullptr)
{
+ m_resizable = isResizable();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool gePianoItem::isResizable() const
+{
+ return !(m_ringLoop || m_orphaned);
}
Pixel by = y() + 2;
Pixel bh = h() - 3;
- if (orphaned) {
- fl_rect(x(), by, MIN_WIDTH, bh, color);
- fl_line(x(), by, x() + MIN_WIDTH, by + bh);
+ if (m_orphaned) {
+ fl_rect(x(), by, w(), bh, color);
+ fl_line(x(), by, x() + w(), by + bh);
}
else {
Pixel vh = calcVelocityH();
- fl_rectf(x(), by + (bh - vh), w(), vh, color);
- fl_rect(x(), by, w(), bh, color);
+ if (m_ringLoop) {
+ fl_rect(x(), by, MIN_WIDTH, bh, color);
+ fl_line(x() + MIN_WIDTH, by + bh/2, x() + w(), by + bh/2);
+ fl_rectf(x(), by + (bh - vh), MIN_WIDTH, vh, color);
+ }
+ else {
+ fl_rect(x(), by, w(), bh, color);
+ fl_rectf(x(), by + (bh - vh), w(), vh, color);
+ }
}
}
Pixel gePianoItem::calcVelocityH() const
{
- int v = m::MidiEvent(a1.iValue).getVelocity();
+ int v = a1->event.getVelocity();
return u::math::map<int, Pixel>(v, 0, G_MAX_VELOCITY, 0, h() - 3);
}
}} // giada::v::
\ No newline at end of file
#define GE_PIANO_ITEM_H
-#include "../../../core/recorder.h"
#include "baseAction.h"
namespace giada {
+namespace m
+{
+class Action;
+}
namespace v
{
class gdActionEditor;
{
private:
+ bool m_ringLoop;
+ bool m_orphaned;
+
Pixel calcVelocityH() const;
public:
- gePianoItem(int x, int y, int w, int h, m::recorder::action a1,
- m::recorder::action a2);
+ gePianoItem(int x, int y, int w, int h, const m::Action* a1, const m::Action* a2);
void draw() override;
- bool orphaned;
+ bool isResizable() const;
};
}} // giada::v::
#include "../../../core/conf.h"
#include "../../../core/const.h"
#include "../../../core/clock.h"
+#include "../../../core/action.h"
+#include "../../../core/midiEvent.h"
#include "../../../core/midiChannel.h"
#include "../../../utils/log.h"
#include "../../../utils/string.h"
#include "../../../utils/math.h"
-#include "../../../glue/recorder.h"
+#include "../../../glue/actionEditor.h"
#include "../../dialogs/actionEditor/baseActionEditor.h"
#include "pianoItem.h"
#include "noteEditor.h"
namespace giada {
namespace v
{
-gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, MidiChannel* ch)
+gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, m::MidiChannel* ch)
: geBaseActionEditor(X, Y, W, 40, ch),
pick (0)
{
position(x(), m::conf::pianoRollY == -1 ? y()-(h()/2) : m::conf::pianoRollY);
- rebuild();
}
{
Frame frame = m_base->pixelToFrame(Fl::event_x() - x());
int note = yToNote(Fl::event_y() - y());
- c::recorder::recordMidiAction(m_ch->index, note, G_MAX_VELOCITY, frame);
+ c::actionEditor::recordMidiAction(static_cast<m::MidiChannel*>(m_ch), note, G_MAX_VELOCITY, frame);
m_base->rebuild(); // Rebuild velocityEditor as well
}
void gePianoRoll::onDeleteAction()
{
- c::recorder::deleteMidiAction(static_cast<MidiChannel*>(m_ch), m_action->a1, m_action->a2);
+ c::actionEditor::deleteMidiAction(static_cast<m::MidiChannel*>(m_ch), m_action->a1);
m_base->rebuild(); // Rebuild velocityEditor as well
}
void gePianoRoll::onResizeAction()
{
- if (static_cast<gePianoItem*>(m_action)->orphaned)
+ if (!static_cast<gePianoItem*>(m_action)->isResizable())
return;
Pixel ex = Fl::event_x();
void gePianoRoll::onRefreshAction()
{
- namespace cr = c::recorder;
-
- if (static_cast<gePianoItem*>(m_action)->orphaned)
- return;
+ namespace ca = c::actionEditor;
Pixel p1 = m_action->x() - x();
Pixel p2 = m_action->x() + m_action->w() - x();
}
else if (m_action->onLeftEdge) {
f1 = m_base->pixelToFrame(p1);
- f2 = m_action->a2.frame;
+ f2 = m_action->a2->frame;
+ if (f1 == f2) // If snapping makes an action fall onto the other
+ f1 -= G_DEFAULT_ACTION_SIZE;
}
else if (m_action->onRightEdge) {
- f1 = m_action->a1.frame;
+ f1 = m_action->a1->frame;
f2 = m_base->pixelToFrame(p2);
+ if (f1 == f2) // If snapping makes an action fall onto the other
+ f2 += G_DEFAULT_ACTION_SIZE;
}
- assert(f1 != 0 && f2 != 0);
+ assert(f2 != 0);
int note = yToNote(m_action->y() - y());
- int velocity = m::MidiEvent(m_action->a1.iValue).getVelocity();
+ int velocity = m_action->a1->event.getVelocity();
- /* TODO - less then optimal. Let's wait for recorder refactoring... */
-
- int oldNote = m::MidiEvent(m_action->a1.iValue).getNote();
- cr::deleteMidiAction(static_cast<MidiChannel*>(m_ch), m_action->a1, m_action->a2);
- if (cr::midiActionCanFit(m_ch->index, note, f1, f2))
- cr::recordMidiAction(m_ch->index, note, velocity, f1, f2);
- else
- cr::recordMidiAction(m_ch->index, oldNote, velocity, m_action->a1.frame, m_action->a2.frame);
+ ca::updateMidiAction(static_cast<m::MidiChannel*>(m_ch), m_action->a1, note,
+ velocity, f1, f2);
m_base->rebuild(); // Rebuild velocityEditor as well
}
}
+Pixel gePianoRoll::getPianoItemW(Pixel px, const m::Action* a1, const m::Action* a2) const
+{
+ if (a2 != nullptr) { // Regular
+ if (a1->frame > a2->frame) // Ring-loop
+ return m_base->loopWidth - (px - x());
+ return m_base->frameToPixel(a2->frame - a1->frame);
+ }
+ return geBaseAction::MIN_WIDTH; // Orphaned
+}
+
+
/* -------------------------------------------------------------------------- */
void gePianoRoll::rebuild()
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
+ namespace ca = c::actionEditor;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, (MAX_KEYS + 1) * CELL_H);
- vector<mr::Composite> actions = cr::getMidiActions(m_ch->index);
- for (mr::Composite comp : actions)
+ for (const m::Action* action : m_base->getActions())
{
- m::MidiEvent e1 = comp.a1.iValue;
- m::MidiEvent e2 = comp.a2.iValue;
+ if (action->event.getStatus() == m::MidiEvent::NOTE_OFF)
+ continue;
+
+ const m::Action* a1 = action;
+ const m::Action* a2 = action->next;
- gu_log("[gePianoRoll::rebuild] ((0x%X, 0x%X, f=%d) - (0x%X, 0x%X, f=%d))\n",
- e1.getStatus(), e1.getNote(), comp.a1.frame,
- e2.getStatus(), e2.getNote(), comp.a2.frame
- );
+ assert(a1 != nullptr); // a2 might be null if orphaned
- Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
- Pixel py = y() + noteToY(e1.getNote());
- Pixel pw = m_base->frameToPixel(comp.a2.frame - comp.a1.frame);
+ Pixel px = x() + m_base->frameToPixel(a1->frame);
+ Pixel py = y() + noteToY(a1->event.getNote());
Pixel ph = CELL_H;
+ Pixel pw = getPianoItemW(px, a1, a2);
- add(new gePianoItem(px, py, pw, ph, comp.a1, comp.a2));
+ add(new gePianoItem(px, py, pw, ph, a1, a2));
}
drawSurface1();
#include "baseActionEditor.h"
-class MidiChannel;
namespace giada {
+namespace m
+{
+class MidiChannel;
+}
namespace v
{
class gePianoRoll : public geBaseActionEditor
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;
public:
static const Pixel CELL_H = 20;
static const Pixel CELL_W = 40;
- gePianoRoll(Pixel x, Pixel y, Pixel w, MidiChannel* ch);
+ gePianoRoll(Pixel x, Pixel y, Pixel w, m::MidiChannel* ch);
void draw() override;
int handle(int e) override;
#include <FL/fl_draw.H>
#include "../../../core/const.h"
+#include "../../../core/action.h"
#include "../../../core/sampleChannel.h"
#include "sampleAction.h"
namespace v
{
geSampleAction::geSampleAction(Pixel X, Pixel Y, Pixel W, Pixel H,
- const SampleChannel* ch, m::recorder::action a1, m::recorder::action a2)
+ const m::SampleChannel* ch, const m::Action* a1, const m::Action* a2)
: geBaseAction(X, Y, W, H, ch->mode == ChannelMode::SINGLE_PRESS, a1, a2),
m_ch (ch)
{
fl_rectf(x(), y(), w(), h(), color);
}
else {
- if (a1.type == G_ACTION_KILL)
+ if (a1->event.getStatus() == m::MidiEvent::NOTE_KILL)
fl_rect(x(), y(), MIN_WIDTH, h(), color);
else {
fl_rectf(x(), y(), MIN_WIDTH, h(), color);
- if (a1.type == G_ACTION_KEYPRESS)
+ if (a1->event.getStatus() == m::MidiEvent::NOTE_ON)
fl_rectf(x()+3, y()+h()-11, w()-6, 8, G_COLOR_GREY_4);
else
- if (a1.type == G_ACTION_KEYREL)
+ if (a1->event.getStatus() == m::MidiEvent::NOTE_OFF)
fl_rectf(x()+3, y()+3, w()-6, 8, G_COLOR_GREY_4);
}
}
#include "baseAction.h"
-class SampleChannel;
namespace giada {
+namespace m
+{
+class SampleChannel;
+class Action;
+}
namespace v
{
class geSampleAction : public geBaseAction
{
private:
- const SampleChannel* m_ch;
+ const m::SampleChannel* m_ch;
public:
- geSampleAction(Pixel x, Pixel y, Pixel w, Pixel h, const SampleChannel* ch,
- m::recorder::action a1, m::recorder::action a2);
+ geSampleAction(Pixel x, Pixel y, Pixel w, Pixel h, const m::SampleChannel* ch,
+ const m::Action* a1, const m::Action* a2);
void draw() override;
};
#include <FL/Fl.H>
#include <FL/fl_draw.H>
+#include "../../../core/recorder.h"
#include "../../../core/const.h"
#include "../../../core/conf.h"
+#include "../../../core/action.h"
#include "../../../core/sampleChannel.h"
#include "../../../utils/log.h"
-#include "../../../glue/recorder.h"
+#include "../../../glue/actionEditor.h"
#include "../../dialogs/actionEditor/baseActionEditor.h"
#include "sampleAction.h"
#include "sampleActionEditor.h"
namespace giada {
namespace v
{
-geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, SampleChannel* ch)
+geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, m::SampleChannel* ch)
: geBaseActionEditor(x, y, 200, m::conf::sampleActionEditorH, ch)
{
- rebuild();
}
void geSampleActionEditor::rebuild()
{
namespace mr = m::recorder;
- namespace cr = c::recorder;
+ namespace ca = c::actionEditor;
- const SampleChannel* ch = static_cast<const SampleChannel*>(m_ch);
+ const m::SampleChannel* ch = static_cast<const m::SampleChannel*>(m_ch);
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- vector<mr::Composite> comps = cr::getSampleActions(ch);
+ for (const m::Action* a1 : m_base->getActions()) {
- for (mr::Composite comp : comps) {
- gu_log("[geSampleActionEditor::rebuild] Action [%d, %d)\n",
- comp.a1.frame, comp.a2.frame);
- Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
+ if (a1->event.getStatus() == m::MidiEvent::ENVELOPE || isNoteOffSinglePress(a1))
+ continue;
+
+ const m::Action* a2 = a1->next;
+
+ Pixel px = x() + m_base->frameToPixel(a1->frame);
Pixel py = y() + 4;
Pixel pw = 0;
Pixel ph = h() - 8;
- if (comp.a2.frame != -1)
- pw = m_base->frameToPixel(comp.a2.frame - comp.a1.frame);
+ if (a2 != nullptr && ch->mode == ChannelMode::SINGLE_PRESS)
+ pw = m_base->frameToPixel(a2->frame - a1->frame);
- geSampleAction* a = new geSampleAction(px, py, pw, ph, ch, comp.a1, comp.a2);
- add(a);
- resizable(a);
+ geSampleAction* gsa = new geSampleAction(px, py, pw, ph, ch, a1, a2);
+ add(gsa);
+ resizable(gsa);
}
/* If channel is LOOP_ANY, deactivate it: a loop mode channel cannot hold
void geSampleActionEditor::onAddAction()
{
Frame f = m_base->pixelToFrame(Fl::event_x() - x());
- c::recorder::recordSampleAction(static_cast<SampleChannel*>(m_ch),
+ c::actionEditor::recordSampleAction(static_cast<m::SampleChannel*>(m_ch),
m_base->getActionType(), f);
- rebuild();
+
+ m_base->rebuild();
}
void geSampleActionEditor::onDeleteAction()
{
- c::recorder::deleteSampleAction(static_cast<SampleChannel*>(m_ch), m_action->a1, m_action->a2);
- rebuild();
+ c::actionEditor::deleteSampleAction(static_cast<m::SampleChannel*>(m_ch), m_action->a1);
+
+ m_base->rebuild();
}
void geSampleActionEditor::onRefreshAction()
{
- namespace cr = c::recorder;
+ namespace ca = c::actionEditor;
- SampleChannel* ch = static_cast<SampleChannel*>(m_ch);
+ m::SampleChannel* ch = static_cast<m::SampleChannel*>(m_ch);
Pixel p1 = m_action->x() - x();
Pixel p2 = m_action->x() + m_action->w() - x();
Frame f1 = 0;
Frame f2 = 0;
- int type = m_action->a1.type;
+ int type = m_action->a1->event.getStatus();
if (!m_action->isOnEdges()) {
f1 = m_base->pixelToFrame(p1);
}
else if (m_action->onLeftEdge) {
f1 = m_base->pixelToFrame(p1);
- f2 = m_action->a2.frame;
+ f2 = m_action->a2->frame;
}
else if (m_action->onRightEdge) {
- f1 = m_action->a1.frame;
+ f1 = m_action->a1->frame;
f2 = m_base->pixelToFrame(p2);
}
- /* TODO - less then optimal. Let's wait for recorder refactoring... */
+ ca::updateSampleAction(ch, m_action->a1, type, f1, f2);
+
+ m_base->rebuild();
+}
- cr::deleteSampleAction(ch, m_action->a1, m_action->a2);
- if (cr::sampleActionCanFit(ch, f1, f2))
- cr::recordSampleAction(ch, type, f1, f2);
- else
- cr::recordSampleAction(ch, type, m_action->a1.frame, m_action->a2.frame);
-
- rebuild();
+
+/* -------------------------------------------------------------------------- */
+
+
+bool geSampleActionEditor::isNoteOffSinglePress(const m::Action* a)
+{
+ const m::SampleChannel* ch = static_cast<const m::SampleChannel*>(m_ch);
+ return ch->mode == ChannelMode::SINGLE_PRESS && a->event.getStatus() == m::MidiEvent::NOTE_OFF;
}
+
}} // giada::v::
\ No newline at end of file
#include "baseActionEditor.h"
-class SampleChannel;
-
-
namespace giada {
+namespace m
+{
+class SampleChannel;
+class Action;
+}
namespace v
{
class geSampleAction;
-
class geSampleActionEditor : public geBaseActionEditor
{
private:
void onResizeAction() override;
void onRefreshAction() override;
+ bool isNoteOffSinglePress(const m::Action* a);
+
public:
- geSampleActionEditor(Pixel x, Pixel y, SampleChannel* ch);
+ geSampleActionEditor(Pixel x, Pixel y, m::SampleChannel* ch);
~geSampleActionEditor();
void draw() override;
* -------------------------------------------------------------------------- */
+#include <cassert>
#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include "../../../utils/log.h"
#include "../../../utils/math.h"
#include "../../../core/const.h"
#include "../../../core/conf.h"
+#include "../../../core/action.h"
#include "../../../core/clock.h"
#include "../../../core/midiChannel.h"
-#include "../../../glue/recorder.h"
+#include "../../../glue/actionEditor.h"
#include "../../dialogs/actionEditor/baseActionEditor.h"
#include "envelopePoint.h"
#include "velocityEditor.h"
namespace giada {
namespace v
{
-geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, MidiChannel* ch)
+geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, m::MidiChannel* ch)
: geBaseActionEditor(x, y, 200, m::conf::velocityEditorH, ch)
{
- rebuild();
}
for (int i=0; i<children(); i++) {
geEnvelopePoint* p = static_cast<geEnvelopePoint*>(child(i));
if (m_action == nullptr)
- p->position(p->x(), valueToY(m::MidiEvent(p->a1.iValue).getVelocity()));
+ p->position(p->x(), valueToY(p->a1->event.getVelocity()));
Pixel x1 = p->x() + side;
Pixel y1 = p->y();
Pixel y2 = y() + h();
int geVelocityEditor::yToValue(Pixel px) const
{
- return u::math::map<Pixel, int>(px, h() - geEnvelopePoint::SIDE, 1, 0, G_MAX_VELOCITY);
+ return u::math::map<Pixel, int>(px, h() - geEnvelopePoint::SIDE, 0, 0, G_MAX_VELOCITY);
}
void geVelocityEditor::rebuild()
{
- namespace mr = m::recorder;
- namespace cr = c::recorder;
+ namespace ca = c::actionEditor;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- vector<mr::Composite> actions = cr::getMidiActions(m_ch->index);
- for (mr::Composite comp : actions)
+ for (const m::Action* action : m_base->getActions())
{
- gu_log("[geVelocityEditor::rebuild] f=%d\n", comp.a1.frame);
+ if (action->event.getStatus() == m::MidiEvent::NOTE_OFF)
+ continue;
+
+ //gu_log("[geVelocityEditor::rebuild] f=%d\n", action->frame);
- Pixel px = x() + m_base->frameToPixel(comp.a1.frame);
- Pixel py = y() + valueToY(m::MidiEvent(comp.a1.iValue).getVelocity());
+ Pixel px = x() + m_base->frameToPixel(action->frame);
+ Pixel py = y() + valueToY(action->event.getVelocity());
- add(new geEnvelopePoint(px, py, comp.a1));
+ add(new geEnvelopePoint(px, py, action));
}
resizable(nullptr);
void geVelocityEditor::onRefreshAction()
{
- c::recorder::setVelocity(m_ch, m_action->a1, yToValue(m_action->y() - y()));
+ c::actionEditor::updateVelocity(static_cast<m::MidiChannel*>(m_ch), m_action->a1,
+ yToValue(m_action->y() - y()));
m_base->rebuild(); // Rebuild pianoRoll as well
}
#include "baseActionEditor.h"
-class MidiChannel;
namespace giada {
+namespace m
+{
+class MidiChannel;
+}
namespace v
{
class geEnvelopePoint;
public:
- geVelocityEditor(Pixel x, Pixel y, MidiChannel* ch);
+ geVelocityEditor(Pixel x, Pixel y, m::MidiChannel* ch);
~geVelocityEditor();
void draw() override;
geResizerBar::geResizerBar(int X, int Y, int W, int H, int minSize, bool type)
- : Fl_Box (X, Y, W, H),
- m_type (type),
- m_minSize(minSize),
- m_lastPos(0),
- m_hover (false)
+ : Fl_Box (X, Y, W, H),
+ m_type (type),
+ m_minSize(minSize),
+ m_lastPos(0),
+ m_hover (false)
{
- 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);
+ if (m_type == VERTICAL) {
+ m_origSize = H;
+ labelsize(H);
+ }
+ else {
+ m_origSize = W;
+ labelsize(W);
+ }
+ align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
+ labelfont(FL_COURIER);
+ visible_focus(0);
}
void geResizerBar::handleDrag(int diff)
{
- Fl_Scroll* grp = static_cast<Fl_Scroll*>(parent());
- int top;
- int bot;
- if (m_type == VERTICAL) {
- top = y();
- bot = y()+h();
- }
- else {
- top = x();
- bot = x()+w();
- }
-
- // First pass: find widget directly above us with common edge
- // Possibly clamp 'diff' if widget would get too small..
-
- for (int t=0; t<grp->children(); t++) {
- Fl_Widget* wd = grp->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 height
- break; // done with first pass
- }
- }
- }
-
- // Second pass: find widgets below us, move based on clamped diff
-
- for (int t=0; t<grp->children(); t++) {
- Fl_Widget* wd = grp->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());
- }
- }
-
- // Change our position last
-
- if (m_type == VERTICAL)
- resize(x(), y()+diff, w(), h());
- else
- resize(x()+diff, y(), w(), h());
-
- grp->init_sizes();
- grp->redraw();
+ Fl_Scroll* grp = static_cast<Fl_Scroll*>(parent());
+ int top;
+ int bot;
+ if (m_type == VERTICAL) {
+ top = y();
+ bot = y()+h();
+ }
+ else {
+ top = x();
+ bot = x()+w();
+ }
+
+ // First pass: find widget directly above us with common edge
+ // Possibly clamp 'diff' if widget would get too small..
+
+ for (int t=0; t<grp->children(); t++) {
+ Fl_Widget* wd = grp->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 height
+ break; // done with first pass
+ }
+ }
+ }
+
+ // Second pass: find widgets below us, move based on clamped diff
+
+ for (int t=0; t<grp->children(); t++) {
+ Fl_Widget* wd = grp->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());
+ }
+ }
+
+ // Change our position last
+
+ if (m_type == VERTICAL)
+ resize(x(), y()+diff, w(), h());
+ else
+ resize(x()+diff, y(), w(), h());
+
+ grp->init_sizes();
+ grp->redraw();
}
int geResizerBar::handle(int e)
{
- int ret = 0;
- int this_y;
- if (m_type == VERTICAL)
- this_y = Fl::event_y_root();
- else
- this_y = Fl::event_x_root();
- switch (e) {
- case FL_FOCUS:
- ret = 1;
- break;
- case FL_ENTER:
- ret = 1;
- fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
- m_hover = true;
- redraw();
- break;
- case FL_LEAVE:
- ret = 1;
- fl_cursor(FL_CURSOR_DEFAULT);
- m_hover = false;
- redraw();
- break;
- case FL_PUSH:
- ret = 1;
- m_lastPos = this_y;
- break;
- case FL_DRAG:
- handleDrag(this_y-m_lastPos);
- m_lastPos = this_y;
- ret = 1;
- break;
- default: break;
- }
- return(Fl_Box::handle(e) | ret);
+ int ret = 0;
+ int this_y;
+ if (m_type == VERTICAL)
+ this_y = Fl::event_y_root();
+ else
+ this_y = Fl::event_x_root();
+ switch (e) {
+ case FL_FOCUS:
+ ret = 1;
+ break;
+ case FL_ENTER:
+ ret = 1;
+ fl_cursor(m_type == VERTICAL ? FL_CURSOR_NS : FL_CURSOR_WE);
+ m_hover = true;
+ redraw();
+ break;
+ case FL_LEAVE:
+ ret = 1;
+ fl_cursor(FL_CURSOR_DEFAULT);
+ m_hover = false;
+ redraw();
+ break;
+ case FL_PUSH:
+ ret = 1;
+ m_lastPos = this_y;
+ break;
+ case FL_DRAG:
+ handleDrag(this_y-m_lastPos);
+ m_lastPos = this_y;
+ ret = 1;
+ break;
+ default: break;
+ }
+ return(Fl_Box::handle(e) | ret);
}
private:
std::string m_currentDir;
- bool m_showHiddenFiles;
+ bool m_showHiddenFiles;
/* normalize
Makes sure the std::string never ends with a trailing slash. */
- std::string normalize(const std::string &s);
+ std::string normalize(const std::string& s);
public:
/* init
Initializes browser and show 'dir' as initial directory. */
- void loadDir(const std::string &dir);
+ void loadDir(const std::string& dir);
/* getSelectedItem
Returns the full path or just the displayed name of the i-th selected item.
using namespace giada;
-geChannel::geChannel(int X, int Y, int W, int H, Channel* ch)
+geChannel::geChannel(int X, int Y, int W, int H, giada::m::Channel* ch)
: Fl_Group(X, Y, W, H, nullptr),
ch (ch)
{
#include <FL/Fl_Group.H>
#include "../../../../core/types.h"
+#include "../../../../core/channel.h"
-class Channel;
class geIdButton;
class geChannelStatus;
class geButton;
public:
- geChannel(int x, int y, int w, int h, Channel* ch);
+ geChannel(int x, int y, int w, int h, giada::m::Channel* ch);
/* reset
* reset channel to initial status. */
int getSize();
- Channel* ch;
+ giada::m::Channel* ch;
geIdButton* button;
geChannelStatus* status;
using namespace giada;
-geChannelMode::geChannelMode(int x, int y, int w, int h, SampleChannel *ch,
+geChannelMode::geChannelMode(int x, int y, int w, int h, m::SampleChannel *ch,
const char *L)
: Fl_Menu_Button(x, y, w, h, L), ch(ch)
{
static void cb_changeMode (Fl_Widget *v, void *p);
inline void __cb_changeMode(int mode);
- class SampleChannel *ch;
+ giada::m::SampleChannel *ch;
public:
- geChannelMode(int x, int y, int w, int h, class SampleChannel *ch,
+ geChannelMode(int x, int y, int w, int h, giada::m::SampleChannel *ch,
const char *l=0);
void draw();
};
if (ch->status == ChannelStatus::PLAY)
fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
else
- fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // status empty
+ fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // status empty
if (mixer::recording && ch->armed)
fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_RED); // take in progress
else
- if (recorder::active && recorder::canRec(ch, clock::isRunning(), mixer::recording))
- fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_BLUE); // action record
+ if (recorder::isActive())
+ fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_BLUE); // action recording
- /* equation for the progress bar:
- * ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
+ /* Equation for the progress bar:
+ ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
int pos = ch->getPosition();
if (pos == -1)
class geChannelStatus : public Fl_Box
{
public:
- geChannelStatus(int X, int Y, int W, int H, class SampleChannel *ch,
+ geChannelStatus(int X, int Y, int W, int H, giada::m::SampleChannel *ch,
const char *L=0);
void draw();
- class SampleChannel *ch;
+ giada::m::SampleChannel *ch;
};
int result = 0;
for (string& path : paths) {
gu_log("[geColumn::handle] loading %s...\n", path.c_str());
- SampleChannel* c = static_cast<SampleChannel*>(c::channel::addChannel(
+ m::SampleChannel* c = static_cast<m::SampleChannel*>(c::channel::addChannel(
m_index, ChannelType::SAMPLE, G_GUI_CHANNEL_H_1));
result = c::channel::loadChannel(c, gu_stripFileUrl(path));
if (result != G_RES_OK) {
/* -------------------------------------------------------------------------- */
-geChannel* geColumn::addChannel(Channel* ch, int size)
+geChannel* geColumn::addChannel(m::Channel* ch, int size)
{
geChannel* gch = nullptr;
repositioned later on during geColumn::resize(). */
if (ch->type == ChannelType::SAMPLE)
- gch = new geSampleChannel(x(), 0, w(), size, static_cast<SampleChannel*>(ch));
+ gch = new geSampleChannel(x(), 0, w(), size, static_cast<m::SampleChannel*>(ch));
else
- gch = new geMidiChannel(x(), 0, w(), size, static_cast<MidiChannel*>(ch));
+ gch = new geMidiChannel(x(), 0, w(), size, static_cast<m::MidiChannel*>(ch));
add(gch);
/* -------------------------------------------------------------------------- */
-Channel* geColumn::getChannel(int i)
+m::Channel* geColumn::getChannel(int i)
{
return static_cast<geChannel*>(child(i + 1))->ch; // Skip "add channel"
}
#include <FL/Fl_Group.H>
-class Channel;
class geButton;
class geChannel;
class geResizerBar;
Adds a new channel in this column and set the internal pointer to channel
to 'ch'. */
- geChannel* addChannel(Channel* ch, int size);
+ geChannel* addChannel(giada::m::Channel* ch, int size);
int handle(int e) override;
void draw() override;
- void resize(int x, int y, int w, int h) override;
+ void resize(int x, int y, int w, int h) override;
/* clear
Removes all channels from the column. If full==true, delete also the "add new
void refreshChannels();
- Channel* getChannel(int i);
+ giada::m::Channel* getChannel(int i);
int getIndex();
void setIndex(int i);
bool isEmpty();
#include "keyboard.h"
+using namespace giada;
+
+
int geKeyboard::indexColumn = 0;
/* -------------------------------------------------------------------------- */
-geChannel* geKeyboard::addChannel(int colIndex, Channel* ch, int size, bool build)
+geChannel* geKeyboard::addChannel(int colIndex, m::Channel* ch, int size, bool build)
{
geColumn* col = getColumnByIndex(colIndex);
if (e == FL_KEYDOWN) {
if (Fl::event_key() == FL_BackSpace && !bckspcPressed) {
bckspcPressed = true;
- glue_rewindSeq(false); // not from GUI
+ transport::rewindSeq(false); // not from GUI
ret = 1;
break;
}
}
else if (Fl::event_key() == ' ' && !spacePressed) {
spacePressed = true;
- glue_startStopSeq(false); // unot from GUI
+ transport::startStopSeq(false); // unot from GUI
ret = 1;
break;
}
#include <vector>
#include <FL/Fl_Scroll.H>
#include "../../../../core/const.h"
+#include "../../../../core/channel.h"
-class Channel;
class geButton;
class geColumn;
class geChannel;
Requires Channel (and not geChannel). If build is set to true, also generate
the corresponding column if column (index) does not exist yet. */
- geChannel* addChannel(int column, Channel* ch, int size, bool build=false);
+ geChannel* addChannel(int column, giada::m::Channel* ch, int size, bool build=false);
/* addColumn
* add a new column to the top of the stack. */
#include "../../../../core/const.h"
#include "../../../../core/graphics.h"
#include "../../../../core/midiChannel.h"
+#include "../../../../core/recorder.h"
#include "../../../../utils/gui.h"
#include "../../../../utils/string.h"
#include "../../../../glue/channel.h"
extern gdMainWindow* G_MainWin;
+using namespace giada;
using std::string;
{
using namespace giada;
- geMidiChannel* gch = static_cast<geMidiChannel*>(w);
- MidiChannel* ch = static_cast<MidiChannel*>(gch->ch);
+ geMidiChannel* gch = static_cast<geMidiChannel*>(w);
+ m::MidiChannel* ch = static_cast<m::MidiChannel*>(gch->ch);
Menu selectedItem = (Menu) (intptr_t) v;
/* -------------------------------------------------------------------------- */
-geMidiChannel::geMidiChannel(int X, int Y, int W, int H, MidiChannel* ch)
+geMidiChannel::geMidiChannel(int X, int Y, int W, int H, m::MidiChannel* ch)
: geChannel(X, Y, W, H, ch)
{
begin();
using namespace giada;
if (button->value())
- c::io::keyPress(static_cast<MidiChannel*>(ch), Fl::event_ctrl(), Fl::event_shift(), 0);
+ c::io::keyPress(static_cast<m::MidiChannel*>(ch), Fl::event_ctrl(), Fl::event_shift(), 0);
}
{"Large", 0, menuCallback, (void*) Menu::RESIZE_H3},
{"X-Large", 0, menuCallback, (void*) Menu::RESIZE_H4},
{0},
- {"Rename channel", 0, menuCallback, (void*) Menu::RENAME_CHANNEL},
- {"Clone channel", 0, menuCallback, (void*) Menu::CLONE_CHANNEL},
- {"Delete channel", 0, menuCallback, (void*) Menu::DELETE_CHANNEL},
+ {"Rename", 0, menuCallback, (void*) Menu::RENAME_CHANNEL},
+ {"Clone", 0, menuCallback, (void*) Menu::CLONE_CHANNEL},
+ {"Delete", 0, menuCallback, (void*) Menu::DELETE_CHANNEL},
{0}
};
void geMidiChannel::refresh()
{
setColorsByStatus(ch->status, ch->recStatus);
+ if (m::recorder::isActive() && ch->armed)
+ mainButton->setActionRecordMode();
mainButton->redraw();
}
void geMidiChannel::update()
{
- const MidiChannel* mch = static_cast<const MidiChannel*>(ch);
+ const m::MidiChannel* mch = static_cast<const m::MidiChannel*>(ch);
string label;
if (mch->name.empty())
#define GE_MIDI_CHANNEL_H
+#include "../../../../core/midiChannel.h"
#include "channel.h"
#include "channelButton.h"
-class MidiChannel;
-
-
class geMidiChannel : public geChannel
{
private:
public:
- geMidiChannel(int x, int y, int w, int h, MidiChannel* ch);
+ geMidiChannel(int x, int y, int w, int h, giada::m::MidiChannel* ch);
void resize(int x, int y, int w, int h) override;
#include "../../../../core/clock.h"
#include "../../../../core/graphics.h"
#include "../../../../core/wave.h"
+#include "../../../../core/recorder.h"
#include "../../../../core/sampleChannel.h"
#include "../../../../glue/io.h"
#include "../../../../glue/channel.h"
{
using namespace giada;
- geSampleChannel* gch = static_cast<geSampleChannel*>(w);
- SampleChannel* ch = static_cast<SampleChannel*>(gch->ch);
+ geSampleChannel* gch = static_cast<geSampleChannel*>(w);
+ m::SampleChannel* ch = static_cast<m::SampleChannel*>(gch->ch);
Menu selectedItem = (Menu) (intptr_t) v;
/* -------------------------------------------------------------------------- */
-geSampleChannel::geSampleChannel(int X, int Y, int W, int H, SampleChannel* ch)
+geSampleChannel::geSampleChannel(int X, int Y, int W, int H, m::SampleChannel* ch)
: geChannel(X, Y, W, H, ch)
{
begin();
/* If you're recording (input or actions) no menu is allowed; you can't do
anything, especially deallocate the channel */
- if (m::mixer::recording || m::recorder::active)
+ if (m::mixer::recording || m::recorder::isActive())
return;
Fl_Menu_Item rclick_menu[] = {
{"Input monitor", 0, menuCallback, (void*) Menu::INPUT_MONITOR,
- FL_MENU_TOGGLE | FL_MENU_DIVIDER | (static_cast<SampleChannel*>(ch)->inputMonitor ? FL_MENU_VALUE : 0)},
+ FL_MENU_TOGGLE | FL_MENU_DIVIDER | (static_cast<m::SampleChannel*>(ch)->inputMonitor ? 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},
{"Large", 0, menuCallback, (void*) Menu::RESIZE_H3},
{"X-Large", 0, menuCallback, (void*) Menu::RESIZE_H4},
{0},
- {"Rename channel", 0, menuCallback, (void*) Menu::RENAME_CHANNEL},
- {"Clone channel", 0, menuCallback, (void*) Menu::CLONE_CHANNEL},
- {"Free channel", 0, menuCallback, (void*) Menu::FREE_CHANNEL},
- {"Delete channel", 0, menuCallback, (void*) Menu::DELETE_CHANNEL},
+ {"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}
};
/* No 'clear start/stop actions' for those channels in loop mode: they cannot
have start/stop actions. */
- if (static_cast<SampleChannel*>(ch)->isAnyLoopMode())
+ if (static_cast<m::SampleChannel*>(ch)->isAnyLoopMode())
rclick_menu[(int) Menu::CLEAR_ACTIONS_START_STOP].deactivate();
Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50);
void geSampleChannel::cb_readActions()
{
using namespace giada::c::channel;
- toggleReadingActions(static_cast<SampleChannel*>(ch));
+ toggleReadingActions(static_cast<m::SampleChannel*>(ch));
}
setColorsByStatus(ch->status, ch->recStatus);
- if (static_cast<SampleChannel*>(ch)->wave != nullptr) {
+ if (static_cast<m::SampleChannel*>(ch)->wave != nullptr) {
if (m::mixer::recording && ch->armed)
mainButton->setInputRecordMode();
- if (m::recorder::active) {
- if (m::recorder::canRec(ch, m::clock::isRunning(), m::mixer::recording))
- mainButton->setActionRecordMode();
- }
+ if (m::recorder::isActive())
+ mainButton->setActionRecordMode();
status->redraw(); // status invisible? sampleButton too (see below)
}
mainButton->redraw();
void geSampleChannel::update()
{
- const SampleChannel* sch = static_cast<const SampleChannel*>(ch);
+ const m::SampleChannel* sch = static_cast<const m::SampleChannel*>(ch);
switch (sch->status) {
case ChannelStatus::EMPTY:
void geSampleChannel::showActionButton()
{
- readActions->value(static_cast<SampleChannel*>(ch)->readActions);
+ readActions->value(static_cast<m::SampleChannel*>(ch)->readActions);
readActions->show();
packWidgets();
redraw();
#define GE_SAMPLE_CHANNEL_H
+#include "../../../../core/sampleChannel.h"
#include "channel.h"
-class SampleChannel;
class geChannelMode;
class geButton;
public:
- geSampleChannel(int x, int y, int w, int h, SampleChannel* ch);
+ geSampleChannel(int x, int y, int w, int h, giada::m::SampleChannel* ch);
void resize(int x, int y, int w, int h) override;
break;
}
case FL_PASTE: {
- geSampleChannel* gch = static_cast<geSampleChannel*>(parent());
- SampleChannel* ch = static_cast<SampleChannel*>(gch->ch);
+ geSampleChannel* gch = static_cast<geSampleChannel*>(parent());
+ m::SampleChannel* ch = static_cast<m::SampleChannel*>(gch->ch);
int result = c::channel::loadChannel(ch, gu_trim(gu_stripFileUrl(Fl::event_text())));
if (result != G_RES_OK)
G_MainWin->keyboard->printChannelMessage(result);
#include "mainTransport.h"
+using namespace giada;
+
+
geMainTransport::geMainTransport(int x, int y)
: Fl_Group(x, y, 131, 25)
{
/* -------------------------------------------------------------------------- */
-void geMainTransport::cb_rewind (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_rewind(); }
-void geMainTransport::cb_play (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_play(); }
-void geMainTransport::cb_recAction(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recAction(); }
-void geMainTransport::cb_recInput (Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_recInput(); }
-void geMainTransport::cb_metronome(Fl_Widget *v, void *p) { ((geMainTransport*)p)->__cb_metronome(); }
+void geMainTransport::cb_rewind (Fl_Widget *v, void *p) { ((geMainTransport*)p)->cb_rewind(); }
+void geMainTransport::cb_play (Fl_Widget *v, void *p) { ((geMainTransport*)p)->cb_play(); }
+void geMainTransport::cb_recAction(Fl_Widget *v, void *p) { ((geMainTransport*)p)->cb_recAction(); }
+void geMainTransport::cb_recInput (Fl_Widget *v, void *p) { ((geMainTransport*)p)->cb_recInput(); }
+void geMainTransport::cb_metronome(Fl_Widget *v, void *p) { ((geMainTransport*)p)->cb_metronome(); }
/* -------------------------------------------------------------------------- */
-void geMainTransport::__cb_rewind()
+void geMainTransport::cb_rewind()
{
- glue_rewindSeq(true);
+ c::transport::rewindSeq(true);
}
/* -------------------------------------------------------------------------- */
-void geMainTransport::__cb_play()
+void geMainTransport::cb_play()
{
- glue_startStopSeq(true);
+ c::transport::startStopSeq(true);
}
/* -------------------------------------------------------------------------- */
-void geMainTransport::__cb_recAction()
+void geMainTransport::cb_recAction()
{
using namespace giada::c::io;
startStopActionRec(true);
/* -------------------------------------------------------------------------- */
-void geMainTransport::__cb_recInput()
+void geMainTransport::cb_recInput()
{
using namespace giada::c::io;
startStopInputRec(true);
/* -------------------------------------------------------------------------- */
-void geMainTransport::__cb_metronome()
+void geMainTransport::cb_metronome()
{
- glue_startStopMetronome(true);
+ c::transport::startStopMetronome(true);
}
static void cb_recAction(Fl_Widget *v, void *p);
static void cb_recInput (Fl_Widget *v, void *p);
static void cb_metronome(Fl_Widget *v, void *p);
-
- inline void __cb_rewind ();
- inline void __cb_play ();
- inline void __cb_recAction();
- inline void __cb_recInput ();
- inline void __cb_metronome();
+ void cb_rewind ();
+ void cb_play ();
+ void cb_recAction();
+ void cb_recInput ();
+ void cb_metronome();
public:
#include <FL/Fl_Group.H>
#include "../../core/midiDispatcher.h"
+#include "../../core/channel.h"
-class Channel;
class gdMidiInputBase;
class geMidiLearner;
class geBox;
/* Channel it belongs to. Might be nullptr if the learner comes from the MIDI
input master window. */
- Channel* ch;
+ giada::m::Channel* ch;
geBox* text;
geButton* value;
struct cbData_t
{
- gdMidiInputBase* window;
- geMidiLearner* learner;
- Channel* channel;
+ gdMidiInputBase* window;
+ geMidiLearner* learner;
+ giada::m::Channel* channel;
} cbData;
/* param
uint32_t* param;
geMidiLearner(int x, int y, int w, const char* l,
- giada::m::midiDispatcher::cb_midiLearn* cb, uint32_t* param, Channel* ch);
+ giada::m::midiDispatcher::cb_midiLearn* cb, uint32_t* param, giada::m::Channel* ch);
void updateValue();
};
using namespace giada;
-gePluginElement::gePluginElement(gdPluginList* gdp, Plugin* p, int X, int Y, int W)
+gePluginElement::gePluginElement(gdPluginList* gdp, m::Plugin* p, int X, int Y, int W)
: Fl_Group (X, Y, W, 20),
m_parentWin(gdp),
m_plugin (p)
class gdPluginList;
-class Plugin;
class geIdButton;
class geChoice;
{
private:
- gdPluginList* m_parentWin;
- Plugin* m_plugin;
+ gdPluginList* m_parentWin;
+ giada::m::Plugin* m_plugin;
static void cb_removePlugin(Fl_Widget* v, void* p);
static void cb_openPluginWindow(Fl_Widget* v, void* p);
geIdButton* shiftDown;
geIdButton* remove;
- gePluginElement(gdPluginList* gdp, Plugin* p, int x, int y, int w);
+ gePluginElement(gdPluginList* gdp, giada::m::Plugin* p, int x, int y, int w);
};
#endif
using std::string;
+using namespace giada;
using namespace giada::c;
-gePluginParameter::gePluginParameter(int paramIndex, Plugin* p, int X, int Y,
+gePluginParameter::gePluginParameter(int paramIndex, m::Plugin* p, int X, int Y,
int W, int labelWidth)
: Fl_Group (X, Y, W, G_GUI_UNIT),
m_paramIndex(paramIndex),
#include <FL/Fl_Group.H>
-class Plugin;
class geBox;
class geSlider;
static const int VALUE_WIDTH = 100;
int m_paramIndex;
- Plugin* m_plugin;
+ giada::m::Plugin* m_plugin;
geBox* m_label;
geSlider* m_slider;
public:
- gePluginParameter(int paramIndex, Plugin* p, int x, int y, int w, int labelWidth);
+ gePluginParameter(int paramIndex, giada::m::Plugin* p, int x, int y, int w, int labelWidth);
void update(bool changeSlider);
};
#include "boostTool.h"
-geBoostTool::geBoostTool(int X, int Y, SampleChannel* ch)
+using namespace giada;
+
+
+geBoostTool::geBoostTool(int X, int Y, m::SampleChannel* ch)
: Fl_Group(X, Y, 220, 20),
ch (ch)
{
#include <FL/Fl_Group.H>
-class SampleChannel;
class geDial;
class geInput;
class geButton;
{
private:
- SampleChannel* ch;
+ giada::m::SampleChannel* ch;
- geBox* label;
- geDial* dial;
- geInput* input;
- geButton* normalize;
+ geBox* label;
+ geDial* dial;
+ geInput* input;
+ geButton* normalize;
- static void cb_setBoost(Fl_Widget* w, void* p);
- static void cb_setBoostNum(Fl_Widget* w, void* p);
- static void cb_normalize(Fl_Widget* w, void* p);
- inline void cb_setBoost();
- inline void cb_setBoostNum();
- inline void cb_normalize();
+ static void cb_setBoost(Fl_Widget* w, void* p);
+ static void cb_setBoostNum(Fl_Widget* w, void* p);
+ static void cb_normalize(Fl_Widget* w, void* p);
+ void cb_setBoost();
+ void cb_setBoostNum();
+ void cb_normalize();
public:
- geBoostTool(int x, int y, SampleChannel* ch);
+ geBoostTool(int x, int y, giada::m::SampleChannel* ch);
- void refresh();
+ void refresh();
};
using namespace giada;
-gePanTool::gePanTool(int x, int y, SampleChannel *ch)
+gePanTool::gePanTool(int x, int y, m::SampleChannel* ch)
: Fl_Group(x, y, 200, 20),
ch (ch)
{
/* -------------------------------------------------------------------------- */
-void gePanTool::cb_panning (Fl_Widget *w, void *p) { ((gePanTool*)p)->__cb_panning(); }
-void gePanTool::cb_panReset(Fl_Widget *w, void *p) { ((gePanTool*)p)->__cb_panReset(); }
+void gePanTool::cb_panning (Fl_Widget *w, void *p) { ((gePanTool*)p)->cb_panning(); }
+void gePanTool::cb_panReset(Fl_Widget *w, void *p) { ((gePanTool*)p)->cb_panReset(); }
/* -------------------------------------------------------------------------- */
-void gePanTool::__cb_panning()
+void gePanTool::cb_panning()
{
c::channel::setPanning(ch, dial->value());
}
/* -------------------------------------------------------------------------- */
-void gePanTool::__cb_panReset()
+void gePanTool::cb_panReset()
{
c::channel::setPanning(ch, 0.5f);
}
\ No newline at end of file
#include <FL/Fl_Group.H>
-class SampleChannel;
class geDial;
class geInput;
class geButton;
{
private:
- SampleChannel *ch;
+ giada::m::SampleChannel* ch;
- geBox *label;
- geDial *dial;
- geInput *input;
- geButton *reset;
+ geBox* label;
+ geDial* dial;
+ geInput* input;
+ geButton* reset;
- static void cb_panning (Fl_Widget *w, void *p);
- static void cb_panReset(Fl_Widget *w, void *p);
- inline void __cb_panning();
- inline void __cb_panReset();
+ static void cb_panning (Fl_Widget* w, void* p);
+ static void cb_panReset(Fl_Widget* w, void* p);
+ void cb_panning();
+ void cb_panReset();
public:
- gePanTool(int x, int y, SampleChannel *ch);
+ gePanTool(int x, int y, giada::m::SampleChannel* ch);
void refresh();
};
using namespace giada;
-gePitchTool::gePitchTool(int x, int y, SampleChannel* ch)
+gePitchTool::gePitchTool(int x, int y, m::SampleChannel* ch)
: Fl_Group(x, y, 600, 20),
ch (ch)
{
/* -------------------------------------------------------------------------- */
-void gePitchTool::cb_setPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitch(); }
-void gePitchTool::cb_setPitchToBar (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchToBar(); }
-void gePitchTool::cb_setPitchToSong(Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchToSong(); }
-void gePitchTool::cb_setPitchHalf (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchHalf(); }
-void gePitchTool::cb_setPitchDouble(Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchDouble(); }
-void gePitchTool::cb_resetPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_resetPitch(); }
-void gePitchTool::cb_setPitchNum (Fl_Widget* w, void* p) { ((gePitchTool*)p)->__cb_setPitchNum(); }
+void gePitchTool::cb_setPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitch(); }
+void gePitchTool::cb_setPitchToBar (Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitchToBar(); }
+void gePitchTool::cb_setPitchToSong(Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitchToSong(); }
+void gePitchTool::cb_setPitchHalf (Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitchHalf(); }
+void gePitchTool::cb_setPitchDouble(Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitchDouble(); }
+void gePitchTool::cb_resetPitch (Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_resetPitch(); }
+void gePitchTool::cb_setPitchNum (Fl_Widget* w, void* p) { ((gePitchTool*)p)->cb_setPitchNum(); }
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitch()
+void gePitchTool::cb_setPitch()
{
c::channel::setPitch(ch, dial->value());
}
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitchNum()
+void gePitchTool::cb_setPitchNum()
{
c::channel::setPitch(ch, atof(input->value()));
}
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitchHalf()
+void gePitchTool::cb_setPitchHalf()
{
c::channel::setPitch(ch, dial->value()/2);
}
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitchDouble()
+void gePitchTool::cb_setPitchDouble()
{
c::channel::setPitch(ch, dial->value()*2);
}
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitchToBar()
+void gePitchTool::cb_setPitchToBar()
{
// TODO - opaque channel's count
c::channel::setPitch(ch, (ch->getEnd()) / (float) m::clock::getFramesInBar());
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_setPitchToSong()
+void gePitchTool::cb_setPitchToSong()
{
// TODO - opaque channel's count
c::channel::setPitch(ch, ch->getEnd() / (float) m::clock::getFramesInLoop());
/* -------------------------------------------------------------------------- */
-void gePitchTool::__cb_resetPitch()
+void gePitchTool::cb_resetPitch()
{
c::channel::setPitch(ch, G_DEFAULT_PITCH);
}
#include <FL/Fl_Group.H>
-class SampleChannel;
class geDial;
class geInput;
class geButton;
{
private:
- SampleChannel *ch;
-
- geBox *label;
- geDial *dial;
- geInput *input;
- geButton *pitchToBar;
- geButton *pitchToSong;
- geButton *pitchHalf;
- geButton *pitchDouble;
- geButton *pitchReset;
-
- static void cb_setPitch (Fl_Widget *w, void *p);
- static void cb_setPitchToBar (Fl_Widget *w, void *p);
- static void cb_setPitchToSong(Fl_Widget *w, void *p);
- static void cb_setPitchHalf (Fl_Widget *w, void *p);
- static void cb_setPitchDouble(Fl_Widget *w, void *p);
- static void cb_resetPitch (Fl_Widget *w, void *p);
- static void cb_setPitchNum (Fl_Widget *w, void *p);
- inline void __cb_setPitch();
- inline void __cb_setPitchToBar();
- inline void __cb_setPitchToSong();
- inline void __cb_setPitchHalf();
- inline void __cb_setPitchDouble();
- inline void __cb_resetPitch();
- inline void __cb_setPitchNum();
+ giada::m::SampleChannel* ch;
+
+ geBox* label;
+ geDial* dial;
+ geInput* input;
+ geButton* pitchToBar;
+ geButton* pitchToSong;
+ geButton* pitchHalf;
+ geButton* pitchDouble;
+ geButton* pitchReset;
+
+ static void cb_setPitch (Fl_Widget* w, void* p);
+ static void cb_setPitchToBar (Fl_Widget* w, void* p);
+ static void cb_setPitchToSong(Fl_Widget* w, void* p);
+ static void cb_setPitchHalf (Fl_Widget* w, void* p);
+ static void cb_setPitchDouble(Fl_Widget* w, void* p);
+ static void cb_resetPitch (Fl_Widget* w, void* p);
+ static void cb_setPitchNum (Fl_Widget* w, void* p);
+ void cb_setPitch();
+ void cb_setPitchToBar();
+ void cb_setPitchToSong();
+ void cb_setPitchHalf();
+ void cb_setPitchDouble();
+ void cb_resetPitch();
+ void cb_setPitchNum();
public:
- gePitchTool(int x, int y, SampleChannel *ch);
+ gePitchTool(int x, int y, giada::m::SampleChannel* ch);
void refresh();
};
using namespace giada::c;
-geRangeTool::geRangeTool(int x, int y, SampleChannel* ch)
+geRangeTool::geRangeTool(int x, int y, giada::m::SampleChannel* ch)
: Fl_Group(x, y, 280, G_GUI_UNIT),
m_ch (ch)
{
/* -------------------------------------------------------------------------- */
-void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_setChanPos(); }
-void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->__cb_resetStartEnd(); }
+void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_setChanPos(); }
+void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_resetStartEnd(); }
/* -------------------------------------------------------------------------- */
-void geRangeTool::__cb_setChanPos()
+void geRangeTool::cb_setChanPos()
{
sampleEditor::setBeginEnd(m_ch, atoi(m_begin->value()), atoi(m_end->value()));
static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform(); // TODO - glue's business!
/* -------------------------------------------------------------------------- */
-void geRangeTool::__cb_resetStartEnd()
+void geRangeTool::cb_resetStartEnd()
{
sampleEditor::setBeginEnd(m_ch, 0, m_ch->wave->getSize() - 1);
static_cast<gdSampleEditor*>(window())->waveTools->updateWaveform(); // TODO - glue's business!
#include <FL/Fl_Group.H>
-class SampleChannel;
class geInput;
class geButton;
class geBox;
{
private:
- SampleChannel* m_ch;
+ giada::m::SampleChannel* m_ch;
geBox* m_label;
geInput* m_begin;
static void cb_setChanPos (Fl_Widget* w, void* p);
static void cb_resetStartEnd(Fl_Widget* w, void* p);
- inline void __cb_setChanPos();
- inline void __cb_resetStartEnd();
+ void cb_setChanPos();
+ void cb_resetStartEnd();
public:
- geRangeTool(int x, int y, SampleChannel* ch);
+ geRangeTool(int x, int y, giada::m::SampleChannel* ch);
void refresh();
};
using namespace giada::c;
-geShiftTool::geShiftTool(int x, int y, SampleChannel* ch)
+geShiftTool::geShiftTool(int x, int y, giada::m::SampleChannel* ch)
: Fl_Group(x, y, 300, G_GUI_UNIT),
m_ch (ch)
{
#include <FL/Fl_Group.H>
-class SampleChannel;
class geInput;
class geButton;
class geBox;
{
private:
- SampleChannel* m_ch;
+ giada::m::SampleChannel* m_ch;
geBox* m_label;
geInput* m_shift;
public:
- geShiftTool(int x, int y, SampleChannel* ch);
+ geShiftTool(int x, int y, giada::m::SampleChannel* ch);
void refresh();
};
using std::string;
+using namespace giada;
-geVolumeTool::geVolumeTool(int X, int Y, SampleChannel* ch)
+geVolumeTool::geVolumeTool(int X, int Y, m::SampleChannel* ch)
: Fl_Group(X, Y, 150, 20),
ch (ch)
{
/* -------------------------------------------------------------------------- */
-void geVolumeTool::cb_setVolume (Fl_Widget* w, void* p) { ((geVolumeTool*)p)->__cb_setVolume(); }
-void geVolumeTool::cb_setVolumeNum(Fl_Widget* w, void* p) { ((geVolumeTool*)p)->__cb_setVolumeNum(); }
+void geVolumeTool::cb_setVolume (Fl_Widget* w, void* p) { ((geVolumeTool*)p)->cb_setVolume(); }
+void geVolumeTool::cb_setVolumeNum(Fl_Widget* w, void* p) { ((geVolumeTool*)p)->cb_setVolumeNum(); }
/* -------------------------------------------------------------------------- */
-void geVolumeTool::__cb_setVolume()
+void geVolumeTool::cb_setVolume()
{
using namespace giada;
/* -------------------------------------------------------------------------- */
-void geVolumeTool::__cb_setVolumeNum()
+void geVolumeTool::cb_setVolumeNum()
{
using namespace giada;
#include <FL/Fl_Group.H>
-class SampleChannel;
class geDial;
class geInput;
class geBox;
{
private:
- SampleChannel *ch;
+ giada::m::SampleChannel* ch;
- geBox *label;
- geDial *dial;
- geInput *input;
+ geBox* label;
+ geDial* dial;
+ geInput* input;
- static void cb_setVolume (Fl_Widget *w, void *p);
- static void cb_setVolumeNum(Fl_Widget *w, void *p);
- inline void __cb_setVolume ();
- inline void __cb_setVolumeNum();
+ static void cb_setVolume (Fl_Widget* w, void* p);
+ static void cb_setVolumeNum(Fl_Widget* w, void* p);
+ void cb_setVolume ();
+ void cb_setVolumeNum();
public:
- geVolumeTool(int x, int y, SampleChannel *ch);
+ geVolumeTool(int x, int y, giada::m::SampleChannel* ch);
void refresh();
};
/* -------------------------------------------------------------------------- */
-geWaveTools::geWaveTools(int x, int y, int w, int h, SampleChannel *ch, const char *l)
+geWaveTools::geWaveTools(int x, int y, int w, int h, m::SampleChannel* ch, const char* l)
: Fl_Scroll(x, y, w, h, l),
ch (ch)
{
menu[(int)Menu::TO_NEW_CHANNEL].deactivate();
}
- Fl_Menu_Button *b = new Fl_Menu_Button(0, 0, 100, 50);
+ Fl_Menu_Button* b = new Fl_Menu_Button(0, 0, 100, 50);
b->box(G_CUSTOM_BORDER_BOX);
b->textsize(G_GUI_FONT_SIZE_BASE);
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 = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, b);
if (m)
m->do_callback(this, m->user_data());
return;
#include <FL/Fl_Scroll.H>
-class SampleChannel;
class geWaveform;
public:
- SampleChannel *ch;
- geWaveform *waveform;
+ giada::m::SampleChannel* ch;
+ geWaveform* waveform;
- geWaveTools(int X,int Y,int W, int H, SampleChannel *ch, const char *L=0);
+ geWaveTools(int x, int y, int w, int h, giada::m::SampleChannel* ch, const char* l=0);
void resize(int x, int y, int w, int h);
int handle(int e);
using namespace giada::c;
-geWaveform::geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l)
+geWaveform::geWaveform(int x, int y, int w, int h, giada::m::SampleChannel* ch, const char* l)
: Fl_Widget (x, y, w, h, l),
m_selection {},
m_ch (ch),
#include <FL/Fl_Widget.H>
-class SampleChannel;
-
-
class geWaveform : public Fl_Widget
{
private:
std::vector<int> points;
} m_grid;
- SampleChannel* m_ch;
+ giada::m::SampleChannel* m_ch;
int m_chanStart;
bool m_chanStartLit;
int m_chanEnd;
static const int ZOOM_IN = -1;
static const int ZOOM_OUT = 0;
- geWaveform(int x, int y, int w, int h, SampleChannel* ch, const char* l=0);
+ geWaveform(int x, int y, int w, int h, giada::m::SampleChannel* ch, const char* l=0);
~geWaveform();
void draw() override;
using namespace giada::m;
-geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char *L)
- : Fl_Box (x, y, w, h, L),
- clip (false),
- mixerPeak (0.0f),
- peak (0.0f),
- dbLevel (0.0f),
- dbLevelOld(0.0f)
+geSoundMeter::geSoundMeter(int x, int y, int w, int h, const char* l)
+: Fl_Box (x, y, w, h, l),
+ clip (false),
+ mixerPeak (0.0f),
+ peak (0.0f),
+ dbLevel (0.0f),
+ dbLevelOld(0.0f)
{
}
void geSoundMeter::draw()
{
- fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);
+ fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4);
- /* peak = the highest value inside the frame */
+ /* peak = the highest value inside the frame */
- peak = 0.0f;
- float tmp_peak = 0.0f;
+ peak = 0.0f;
+ float tmp_peak = 0.0f;
- tmp_peak = fabs(mixerPeak);
- if (tmp_peak > peak)
- peak = tmp_peak;
+ tmp_peak = fabs(mixerPeak);
+ if (tmp_peak > peak)
+ peak = tmp_peak;
- clip = peak >= 1.0f ? true : false; // 1.0f is considered clip
+ clip = peak >= 1.0f ? true : false; // 1.0f is considered clip
- /* dBFS (full scale) calculation, plus decay of -2dB per frame */
+ /* dBFS (full scale) calculation, plus decay of -2dB per frame */
- dbLevel = 20 * log10(peak);
- if (dbLevel < dbLevelOld)
- if (dbLevelOld > -G_MIN_DB_SCALE)
- dbLevel = dbLevelOld - 2.0f;
+ dbLevel = 20 * log10(peak);
+ if (dbLevel < dbLevelOld)
+ if (dbLevelOld > -G_MIN_DB_SCALE)
+ dbLevel = dbLevelOld - 2.0f;
- dbLevelOld = dbLevel;
+ dbLevelOld = dbLevel;
- /* graphical part */
+ /* graphical part */
- float px_level = 0.0f;
- if (dbLevel < 0.0f)
- px_level = ((w()/G_MIN_DB_SCALE) * dbLevel) + w();
- else
- px_level = w();
+ float px_level = 0.0f;
+ if (dbLevel < 0.0f)
+ px_level = ((w()/G_MIN_DB_SCALE) * dbLevel) + w();
+ else
+ px_level = w();
- fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2);
- fl_rectf(x()+1, y()+1, (int) px_level, h()-2, clip || !kernelAudio::getStatus() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4);
+ fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2);
+ fl_rectf(x()+1, y()+1, (int) px_level, h()-2, clip || !kernelAudio::getStatus() ? G_COLOR_RED_ALERT : G_COLOR_GREY_4);
}
{
public:
- geSoundMeter(int X, int Y, int W, int H, const char *L=0);
+ geSoundMeter(int x, int y, int w, int h, const char* l=0);
- void draw() override;
+ void draw() override;
- bool clip;
+ bool clip;
float mixerPeak; // peak from mixer
private:
* -------------------------------------------------------------------------- */
-#include <pthread.h>
-#if defined(__linux__) || defined(__APPLE__)
- #include <unistd.h>
-#endif
+#include <atomic>
#include <FL/Fl.H>
#include "core/init.h"
-#include "core/const.h"
-#include "core/patch.h"
-#include "core/conf.h"
-#include "core/midiMapConf.h"
-#include "core/mixer.h"
-#include "core/clock.h"
-#include "core/mixerHandler.h"
-#include "core/kernelAudio.h"
-#include "core/kernelMidi.h"
-#include "core/recorder.h"
-#include "utils/gui.h"
-#include "utils/time.h"
-#include "gui/dialogs/gd_mainWindow.h"
-#include "core/pluginHost.h"
-pthread_t G_videoThread;
-bool G_quit;
-gdMainWindow* G_MainWin;
-
-
-void* videoThreadCb(void* arg);
+std::atomic<bool> G_quit(false);
+class gdMainWindow* G_MainWin = nullptr;
int main(int argc, char** argv)
{
- G_quit = false;
-
- init_prepareParser();
- init_prepareMidiMap();
- init_prepareKernelAudio();
- init_prepareKernelMIDI();
- init_startGUI(argc, argv);
-
- Fl::lock();
- pthread_create(&G_videoThread, nullptr, videoThreadCb, nullptr);
- init_startKernelAudio();
+ using namespace giada;
-#ifdef WITH_VST
- juce::initialiseJuce_GUI();
-#endif
+ m::init::startup(argc, argv);
int ret = Fl::run();
-#ifdef WITH_VST
- juce::shutdownJuce_GUI();
-#endif
+ m::init::shutdown();
- pthread_join(G_videoThread, nullptr);
return ret;
}
-void* videoThreadCb(void* arg)
-{
- using namespace giada;
- if (m::kernelAudio::getStatus())
- while (!G_quit) {
- gu_refreshUI();
- u::time::sleep(G_GUI_REFRESH_RATE);
- }
- pthread_exit(nullptr);
- return 0;
-}
Maps 'x' in range [a, b] to a new range [w, z]. Source:
https://en.wikipedia.org/wiki/Linear_equation#Two-point_form*/
-template <typename Tin, typename Tout>
-Tout map(Tin x, Tin a, Tin b, Tout w, Tout z)
+template <typename TI, typename TO>
+TO map(TI x, TI a, TI b, TO w, TO z)
{
return (((x - a) / (float) (b - a)) * (z - w)) + w;
}
+
+template <typename T>
+T bound(T x, T min, T max, T def)
+{
+ return x < min || x > max ? def : x;
+}
}}} // giada::u::math::
buffer.free();
- REQUIRE(buffer[0] == nullptr);
REQUIRE(buffer.countFrames() == 0);
REQUIRE(buffer.countSamples() == 0);
REQUIRE(buffer.countChannels() == 0);
conf::recsStopOnChanHalt = true;
conf::chansStopOnSeqHalt = false;
conf::treatRecsAsLoops = true;
- conf::resizeRecordings = false;
conf::pluginPath = "path/to/plugins";
conf::patchPath = "path/to/patches";
conf::samplePath = "path/to/samples";
REQUIRE(conf::recsStopOnChanHalt == true);
REQUIRE(conf::chansStopOnSeqHalt == false);
REQUIRE(conf::treatRecsAsLoops == true);
- REQUIRE(conf::resizeRecordings == false);
REQUIRE(conf::pluginPath == "path/to/plugins");
REQUIRE(conf::patchPath == "path/to/patches");
REQUIRE(conf::samplePath == "path/to/samples");
patch::plugin_t plugin3;
#endif
- action0.type = 0;
- action0.frame = 50000;
- action0.fValue = 0.3f;
- action0.iValue = 1000;
- action1.type = 2;
- action1.frame = 589;
- action1.fValue = 1.0f;
- action1.iValue = 130;
+ action0.id = 0;
+ action0.channel = 6;
+ action0.frame = 4000;
+ action0.event = 0xFF00FF00;
+ action0.prev = -1;
+ action0.next = -1;
+ action1.id = 1;
+ action1.channel = 2;
+ action1.frame = 8000;
+ action1.event = 0x00000000;
+ action1.prev = -1;
+ action1.next = -1;
channel1.actions.push_back(action0);
channel1.actions.push_back(action1);
REQUIRE(channel0.midiOutChan == 5);
patch::action_t action0 = channel0.actions.at(0);
- REQUIRE(action0.type == 0);
- REQUIRE(action0.frame == 50000);
- REQUIRE(action0.fValue == Approx(0.3f));
- REQUIRE(action0.iValue == 1000);
+ REQUIRE(action0.id == 0);
+ REQUIRE(action0.channel == 6);
+ REQUIRE(action0.frame == 4000);
+ REQUIRE(action0.event == 0xFF00FF00);
+ REQUIRE(action0.prev == -1);
+ REQUIRE(action0.next == -1);
patch::action_t action1 = channel0.actions.at(1);
- REQUIRE(action1.type == 2);
- REQUIRE(action1.frame == 589);
- REQUIRE(action1.fValue == Approx(1.0f));
- REQUIRE(action1.iValue == 130);
+ REQUIRE(action1.id == 1);
+ REQUIRE(action1.channel == 2);
+ REQUIRE(action1.frame == 8000);
+ REQUIRE(action1.event == 0x00000000);
+ REQUIRE(action1.prev == -1);
+ REQUIRE(action1.next == -1);
#ifdef WITH_VST
patch::plugin_t plugin0 = channel0.plugins.at(0);
#include "../src/core/recorder.h"
#include "../src/core/const.h"
+#include "../src/core/types.h"
+#include "../src/core/action.h"
#include <catch.hpp>
-using std::string;
-using namespace giada::m;
-
-
TEST_CASE("recorder")
{
- /* Each SECTION the TEST_CASE is executed from the start. The following
- code is exectuted before each SECTION. */
+ using namespace giada;
+ using namespace giada::m;
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);
- recorder::init();
- REQUIRE(recorder::frames.size() == 0);
- REQUIRE(recorder::global.size() == 0);
+ recorder::init(&mutex);
+ recorder::enable();
- SECTION("Test record single action")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 50, 1, 0.5f);
-
- REQUIRE(recorder::frames.size() == 1);
- REQUIRE(recorder::frames.at(0) == 50);
- REQUIRE(recorder::global.at(0).size() == 1); // 1 action on frame #0
- REQUIRE(recorder::global.at(0).at(0)->chan == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(0).at(0)->frame == 50);
- REQUIRE(recorder::global.at(0).at(0)->iValue == 1);
- REQUIRE(recorder::global.at(0).at(0)->fValue == 0.5f);
- }
+ REQUIRE(recorder::hasActions(/*ch=*/0) == false);
- SECTION("Test record, two actions on same frame")
+ SECTION("Test record")
{
- recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f);
- recorder::rec(0, G_ACTION_KEYREL, 50, 1, 0.5f);
+ 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);
- REQUIRE(recorder::frames.size() == 1); // same frame, frames.size must stay 1
- REQUIRE(recorder::frames.at(0) == 50);
- REQUIRE(recorder::global.at(0).size() == 2); // 2 actions on frame #0
+ const Action* a1 = recorder::rec(ch, f1, e1);
+ const Action* a2 = recorder::rec(ch, f2, e2);
- REQUIRE(recorder::global.at(0).at(0)->chan == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(0).at(0)->frame == 50);
- REQUIRE(recorder::global.at(0).at(0)->iValue == 6);
- REQUIRE(recorder::global.at(0).at(0)->fValue == 0.3f);
+ REQUIRE(recorder::hasActions(ch) == true);
+ REQUIRE(a1->frame == f1);
+ REQUIRE(a2->frame == f2);
+ REQUIRE(a1->prev == nullptr);
+ REQUIRE(a1->next == nullptr);
+ REQUIRE(a2->prev == nullptr);
+ REQUIRE(a2->next == nullptr);
- REQUIRE(recorder::global.at(0).at(1)->chan == 0);
- REQUIRE(recorder::global.at(0).at(1)->type == G_ACTION_KEYREL);
- REQUIRE(recorder::global.at(0).at(1)->frame == 50);
- REQUIRE(recorder::global.at(0).at(1)->iValue == 1);
- REQUIRE(recorder::global.at(0).at(1)->fValue == 0.5f);
-
- SECTION("Test record, another action on a different frame")
+ SECTION("Test clear actions by channel")
{
- recorder::rec(0, G_ACTION_KEYPRESS, 70, 1, 0.5f);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::frames.at(1) == 70);
- REQUIRE(recorder::global.at(0).size() == 2); // 2 actions on frame #0
- REQUIRE(recorder::global.at(1).size() == 1); // 1 actions on frame #1
- REQUIRE(recorder::global.at(1).at(0)->chan == 0);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 70);
- REQUIRE(recorder::global.at(1).at(0)->iValue == 1);
- REQUIRE(recorder::global.at(1).at(0)->fValue == 0.5f);
+ 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 retrieval")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f);
- recorder::rec(0, G_ACTION_KEYREL, 70, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 50, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 70, 1, 0.5f);
- recorder::rec(2, G_ACTION_KEYPRESS, 100, 6, 0.3f);
- recorder::rec(2, G_ACTION_KEYREL, 120, 1, 0.5f);
-
- /* Give me action on chan 1, type G_ACTION_KEYREL, frame 70. */
- recorder::action *action = nullptr;
- REQUIRE(recorder::getAction(1, G_ACTION_KEYREL, 70, &action) == 1);
-
- REQUIRE(action != nullptr);
- REQUIRE(action->chan == 1);
- REQUIRE(action->type == G_ACTION_KEYREL);
- REQUIRE(action->frame == 70);
- REQUIRE(action->iValue == 1);
- REQUIRE(action->fValue == 0.5f);
-
- /* Give me *next* action on chan 0, type G_ACTION_KEYREL, starting from frame 20.
- Must be action #2 */
-
- REQUIRE(recorder::getNextAction(0, G_ACTION_KEYREL, 20, &action) == 1);
- REQUIRE(action != nullptr);
- REQUIRE(action->chan == 0);
- REQUIRE(action->type == G_ACTION_KEYREL);
- REQUIRE(action->frame == 70);
-
- /* Give me *next* action on chan 2, type G_ACTION_KEYPRESS, starting from
- frame 200. You are requesting frame outside boundaries. */
-
- REQUIRE(recorder::getNextAction(2, G_ACTION_KEYPRESS, 200, &action) == -1);
-
- /* Give me *next* action on chan 2, type G_ACTION_KEYPRESS, starting from
- frame 100. That action does not exist. */
- REQUIRE(recorder::getNextAction(2, G_ACTION_KEYPRESS, 100, &action) == -2);
- }
-
- SECTION("Test retrieval MIDI")
- {
- recorder::rec(0, G_ACTION_MIDI, 0, 0x903C3F00, 0.0f);
- recorder::rec(1, G_ACTION_MIDI, 0, 0x903D3F00, 0.0f);
- recorder::rec(0, G_ACTION_MIDI, 1000, 0x803C2000, 0.0f);
- recorder::rec(0, G_ACTION_MIDI, 1050, 0x903C3F00, 0.0f);
- recorder::rec(0, G_ACTION_MIDI, 2000, 0x803C3F00, 0.0f);
- recorder::rec(1, G_ACTION_MIDI, 90, 0x803D3F00, 0.0f);
- recorder::rec(1, G_ACTION_MIDI, 1050, 0x903D3F00, 0.0f);
- recorder::rec(1, G_ACTION_MIDI, 2000, 0x803D3F00, 0.0f);
-
- recorder::action* result = nullptr;
- recorder::getNextAction(0, G_ACTION_MIDI, 100, &result, 0x803CFF00, 0x0000FF00);
-
- REQUIRE(result != nullptr);
- REQUIRE(result->frame == 1000);
- }
-
- SECTION("Test deletion, single action")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f);
- recorder::rec(0, G_ACTION_KEYREL, 60, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 70, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f);
-
- /* Delete action #0, don't check values. */
- recorder::deleteAction(0, 50, G_ACTION_KEYPRESS, false, &mutex);
-
- REQUIRE(recorder::frames.size() == 3);
- REQUIRE(recorder::global.size() == 3);
-
- SECTION("Test deletion checked")
+ SECTION("Test clear actions by type")
{
- /* Delete action #1, check values. */
- recorder::deleteAction(1, 70, G_ACTION_KEYPRESS, true, &mutex, 6, 0.3f);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
+ recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_ON);
+ recorder::clearActions(/*channel=*/0, MidiEvent::NOTE_OFF);
+
+ REQUIRE(recorder::hasActions(/*channel=*/0) == false);
}
- }
-
- SECTION("Test deletion, range of actions")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 50, 6, 0.3f);
- recorder::rec(0, G_ACTION_KEYREL, 60, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 70, 6, 0.3f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f);
-
- /* Delete any action on channel 0 of types KEYPRESS | KEYREL between
- frames 0 and 200. */
- recorder::deleteActions(0, 0, 200, G_ACTION_KEYPRESS | G_ACTION_KEYREL, &mutex);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
- REQUIRE(recorder::global.at(0).size() == 1);
- REQUIRE(recorder::global.at(1).size() == 1);
-
- REQUIRE(recorder::global.at(0).at(0)->chan == 1);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(0).at(0)->frame == 100);
- REQUIRE(recorder::global.at(0).at(0)->iValue == 6);
- REQUIRE(recorder::global.at(0).at(0)->fValue == 0.3f);
-
- REQUIRE(recorder::global.at(1).at(0)->chan == 1);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
- REQUIRE(recorder::global.at(1).at(0)->frame == 120);
- REQUIRE(recorder::global.at(1).at(0)->iValue == 1);
- REQUIRE(recorder::global.at(1).at(0)->fValue == 0.5f);
- }
-
- SECTION("Test action presence")
- {
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f);
-
- recorder::deleteAction(0, 80, G_ACTION_KEYREL, false, &mutex);
-
- REQUIRE(recorder::hasActions(0) == false);
- REQUIRE(recorder::hasActions(1) == true);
- }
-
- SECTION("Test clear actions by channel")
- {
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 100, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f);
-
- recorder::clearChan(1);
-
- REQUIRE(recorder::hasActions(0) == true);
- REQUIRE(recorder::hasActions(1) == false);
- REQUIRE(recorder::frames.size() == 1);
- REQUIRE(recorder::global.size() == 1);
- REQUIRE(recorder::global.at(0).size() == 1);
- }
-
- SECTION("Test clear actions by type")
- {
- recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 100, 6, 0.3f);
- recorder::rec(1, G_ACTION_KEYREL, 120, 1, 0.5f);
-
- /* Clear all actions of type KEYREL from channel 1. */
-
- recorder::clearAction(1, G_ACTION_KEYREL);
-
- REQUIRE(recorder::hasActions(0) == true);
- REQUIRE(recorder::hasActions(1) == false);
- REQUIRE(recorder::frames.size() == 1);
- REQUIRE(recorder::global.size() == 1);
- REQUIRE(recorder::global.at(0).size() == 1);
- }
-
- SECTION("Test clear all")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYREL, 100, 6, 0.3f);
- recorder::rec(2, G_ACTION_KILL, 120, 1, 0.5f);
-
- recorder::clearAll();
- REQUIRE(recorder::frames.size() == 0);
- REQUIRE(recorder::global.size() == 0);
- }
-
- SECTION("Test optimization")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 20, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYPRESS, 20, 1, 0.5f);
- recorder::rec(1, G_ACTION_KEYREL, 80, 1, 0.5f);
-
- /* Fake frame 80 without actions.*/
- recorder::global.at(1).clear();
-
- recorder::optimize();
-
- REQUIRE(recorder::frames.size() == 1);
- REQUIRE(recorder::global.size() == 1);
- REQUIRE(recorder::global.at(0).size() == 2);
- }
-
- SECTION("Test BPM update")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::updateBpm(60.0f, 120.0f, 44100); // scaling up
-
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 40);
-
- recorder::updateBpm(120.0f, 60.0f, 44100); // scaling down
-
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 80);
- }
-
- SECTION("Test samplerate update")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 120, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 150, 1, 0.5f);
-
- recorder::updateSamplerate(44100, 22050); // scaling down
-
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 160);
- REQUIRE(recorder::frames.at(2) == 240);
- REQUIRE(recorder::frames.at(3) == 300);
-
- recorder::updateSamplerate(22050, 44100); // scaling up
-
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 80);
- REQUIRE(recorder::frames.at(2) == 120);
- REQUIRE(recorder::frames.at(3) == 150);
- }
-
- SECTION("Test expand")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(0, G_ACTION_KILL, 200, 1, 0.5f);
-
- recorder::expand(300, 600);
-
- REQUIRE(recorder::frames.size() == 6);
- REQUIRE(recorder::global.size() == 6);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 80);
- REQUIRE(recorder::frames.at(2) == 200);
- REQUIRE(recorder::frames.at(3) == 300);
- REQUIRE(recorder::frames.at(4) == 380);
- REQUIRE(recorder::frames.at(5) == 500);
- }
-
- SECTION("Test shrink")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(0, G_ACTION_KILL, 200, 1, 0.5f);
-
- recorder::shrink(100);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 80);
- }
-
- SECTION("Test overdub, full overwrite")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 80, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 200, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 400, 1, 0.5f);
-
- /* Should delete all actions in between and keep the first one, plus a
- new last action on frame 500. */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 0, 1024);
- recorder::stopOverdub(500, 500, &mutex);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 500);
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 500);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, left overlap")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 100, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 400, 1, 0.5f);
-
- /* Overdub part of the leftmost part of a composite action. Expected result:
- a new composite action.
- Original: ----|########|
- Overdub: |#######|-----
- Result: |#######|----- */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 0, 16);
- recorder::stopOverdub(300, 500, &mutex);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 300);
-
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 300);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, right overlap")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 000, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 400, 1, 0.5f);
-
- /* Overdub part of the rightmost part of a composite action. Expected result:
- a new composite action.
- Original: |########|------
- Overdub: -----|#######|--
- Result: |###||#######|-- */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 100, 16);
- recorder::stopOverdub(500, 500, &mutex);
-
- REQUIRE(recorder::frames.size() == 4);
- REQUIRE(recorder::global.size() == 4);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 84); // 100 - bufferSize (16)
- REQUIRE(recorder::frames.at(2) == 100);
- REQUIRE(recorder::frames.at(3) == 500);
-
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 84);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
-
- REQUIRE(recorder::global.at(2).at(0)->frame == 100);
- REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(3).at(0)->frame == 500);
- REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, hole diggin'")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 400, 1, 0.5f);
-
- /* Overdub in the middle of a long, composite action. Expected result:
- original action trimmed down plus anther action next to it. Total frames
- should be 4.
- Original: |#############|
- Overdub: ---|#######|---
- Result: |#||#######|--- */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 100, 16);
- recorder::stopOverdub(300, 500, &mutex);
-
- REQUIRE(recorder::frames.size() == 4);
- REQUIRE(recorder::global.size() == 4);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 84); // 100 - bufferSize (16)
- REQUIRE(recorder::frames.at(2) == 100);
- REQUIRE(recorder::frames.at(3) == 300);
-
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 84);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
-
- REQUIRE(recorder::global.at(2).at(0)->frame == 100);
- REQUIRE(recorder::global.at(2).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(3).at(0)->frame == 300);
- REQUIRE(recorder::global.at(3).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, cover all")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 100, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 120, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 200, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYPRESS, 220, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 300, 1, 0.5f);
-
- /* Overdub all existing actions. Expected result: a single composite one. */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 0, 16);
- recorder::stopOverdub(500, 500, &mutex);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::global.size() == 2);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 500);
-
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 500);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, null loop")
- {
- recorder::rec(0, G_ACTION_KEYPRESS, 0, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 500, 1, 0.5f);
-
- /* A null loop is a loop that begins and ends on the very same frame. */
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 300, 16);
- recorder::stopOverdub(300, 700, &mutex);
-
- REQUIRE(recorder::frames.size() == 2);
- REQUIRE(recorder::frames.at(0) == 0);
- REQUIRE(recorder::frames.at(1) == 284); // 300 - bufferSize (16)
-
- REQUIRE(recorder::global.at(0).at(0)->frame == 0);
- REQUIRE(recorder::global.at(0).at(0)->type == G_ACTION_KEYPRESS);
- REQUIRE(recorder::global.at(1).at(0)->frame == 284);
- REQUIRE(recorder::global.at(1).at(0)->type == G_ACTION_KEYREL);
- }
-
- SECTION("Test overdub, ring loop")
- {
- /* A ring loop occurs when you record the last action beyond the end of
- the sequencer.
- Original: ---|#######|---
- Overdub: #####|------|##
- Result: ---|#######||#| */
-
- recorder::rec(0, G_ACTION_KEYPRESS, 200, 1, 0.5f);
- recorder::rec(0, G_ACTION_KEYREL, 300, 1, 0.5f);
-
- recorder::startOverdub(0, G_ACTION_KEYPRESS | G_ACTION_KEYREL, 400, 16);
- recorder::stopOverdub(250, 700, &mutex);
-
- REQUIRE(recorder::frames.size() == 4);
- REQUIRE(recorder::frames.at(0) == 200);
- REQUIRE(recorder::frames.at(1) == 300);
- REQUIRE(recorder::frames.at(2) == 400);
- REQUIRE(recorder::frames.at(3) == 700);
+ SECTION("Test clear all")
+ {
+ recorder::clearAll();
+ REQUIRE(recorder::hasActions(/*channel=*/0) == false);
+ }
}
-}
+}
\ No newline at end of file
REQUIRE(ch.wave == w);
REQUIRE(ch.begin == 0);
REQUIRE(ch.end == w->getSize() - 1);
- REQUIRE(ch.name == w->getBasename());
+ REQUIRE(ch.name == "");
}
SECTION("begin/end")