--------------------------------------------------------------------------------
+0.16.3 --- 2020 . 06. 15
+- Non-virtual Channels architecture
+- Added G_DEBUG macro
+- Optimized CPU usage when playing with many channels
+- Increased UI refresh rate to 30 frames per second
+- Improved quantizer precision
+- Simplified behavior when halting channels containing recorded actions
+- Fix wrong audio sample looping with pitch != 1.0
+- Fix MIDI input master values not stored on quit
+- Fix One-shot press channel mode not working via mouse
+- Fix Action recording overlap (both live and via Action Editor)
+- Fix crash when loading a project with missing audio files
+- Fix BPM not changing via Jack
+
+
0.16.2 --- 2020 . 02 . 18
- Switch to Json for modern C++ library for reading and writing Json data
- Resizable channels, improved version
cppFlags = -I$(top_srcdir)/src
-cxxFlags = -std=c++14 -Wall
+cxxFlags = -std=c++17 -Wall
ldAdd =
ldFlags =
sourcesExtra =
sourcesCore = \
src/core/const.h \
src/core/queue.h \
+ src/core/ringBuffer.h \
src/core/types.h \
src/core/range.h \
src/core/action.h \
src/core/midiEvent.cpp \
src/core/audioBuffer.h \
src/core/audioBuffer.cpp \
+ src/core/quantizer.h \
+ src/core/quantizer.cpp \
src/core/conf.h \
src/core/conf.cpp \
src/core/kernelAudio.h \
src/core/pluginManager.cpp \
src/core/mixerHandler.h \
src/core/mixerHandler.cpp \
+ src/core/sequencer.h \
+ src/core/sequencer.cpp \
src/core/init.h \
src/core/init.cpp \
src/core/plugin.h \
src/core/waveManager.cpp \
src/core/recManager.h \
src/core/recManager.cpp \
+ src/core/channels/state.h \
+ src/core/channels/state.cpp \
+ src/core/channels/sampleActionRecorder.h \
+ src/core/channels/sampleActionRecorder.cpp \
+ src/core/channels/midiActionRecorder.h \
+ src/core/channels/midiActionRecorder.cpp \
+ src/core/channels/waveReader.h \
+ src/core/channels/waveReader.cpp \
+ src/core/channels/midiController.h \
+ src/core/channels/midiController.cpp \
+ src/core/channels/sampleController.h \
+ src/core/channels/sampleController.cpp \
+ src/core/channels/samplePlayer.h \
+ src/core/channels/samplePlayer.cpp \
+ src/core/channels/audioReceiver.h \
+ src/core/channels/audioReceiver.cpp \
+ src/core/channels/midiLighter.h \
+ src/core/channels/midiLighter.cpp \
+ src/core/channels/midiLearner.h \
+ src/core/channels/midiLearner.cpp \
+ src/core/channels/midiSender.h \
+ src/core/channels/midiSender.cpp \
+ src/core/channels/midiReceiver.h \
+ src/core/channels/midiReceiver.cpp \
src/core/channels/channel.h \
src/core/channels/channel.cpp \
- src/core/channels/midiChannel.h \
- src/core/channels/midiChannel.cpp \
- src/core/channels/masterChannel.h \
- src/core/channels/masterChannel.cpp \
- src/core/channels/sampleChannel.h \
- src/core/channels/sampleChannel.cpp \
src/core/channels/channelManager.h \
src/core/channels/channelManager.cpp \
- src/core/channels/sampleChannelProc.h \
- src/core/channels/sampleChannelProc.cpp \
- src/core/channels/sampleChannelRec.h \
- src/core/channels/sampleChannelRec.cpp \
- src/core/channels/midiChannelProc.h \
- src/core/channels/midiChannelProc.cpp \
src/core/model/model.h \
src/core/model/model.cpp \
src/core/model/storage.h \
src/core/model/storage.cpp \
src/core/idManager.h \
src/core/idManager.cpp \
+ src/glue/events.h \
+ src/glue/events.cpp \
src/glue/main.h \
src/glue/main.cpp \
src/glue/io.h \
src/gui/dialogs/midiIO/midiInputChannel.cpp \
src/gui/dialogs/midiIO/midiInputMaster.h \
src/gui/dialogs/midiIO/midiInputMaster.cpp \
+ src/gui/elems/midiIO/midiLearner.h \
+ src/gui/elems/midiIO/midiLearner.cpp \
+ src/gui/elems/midiIO/midiLearnerPack.h \
+ src/gui/elems/midiIO/midiLearnerPack.cpp \
src/gui/elems/browser.h \
src/gui/elems/browser.cpp \
src/gui/elems/soundMeter.h \
src/gui/elems/actionEditor/envelopePoint.cpp \
src/gui/elems/actionEditor/pianoRoll.h \
src/gui/elems/actionEditor/pianoRoll.cpp \
- src/gui/elems/actionEditor/noteEditor.h \
+ src/gui/elems/actionEditor/noteEditor.h \
src/gui/elems/actionEditor/noteEditor.cpp \
src/gui/elems/actionEditor/pianoItem.h \
src/gui/elems/actionEditor/pianoItem.cpp \
src/gui/elems/config/tabBehaviors.cpp \
src/gui/elems/config/tabPlugins.h \
src/gui/elems/config/tabPlugins.cpp \
- src/gui/elems/midiIO/midiLearnerBase.h \
- src/gui/elems/midiIO/midiLearnerBase.cpp \
- src/gui/elems/midiIO/midiLearnerMaster.h \
- src/gui/elems/midiIO/midiLearnerMaster.cpp \
- src/gui/elems/midiIO/midiLearnerChannel.h \
- src/gui/elems/midiIO/midiLearnerChannel.cpp \
- src/gui/elems/midiIO/midiLearnerPlugin.h \
- src/gui/elems/midiIO/midiLearnerPlugin.cpp \
src/gui/elems/basics/scroll.h \
src/gui/elems/basics/scroll.cpp \
+ src/gui/elems/basics/pack.h \
+ src/gui/elems/basics/pack.cpp \
+ src/gui/elems/basics/group.h \
+ src/gui/elems/basics/group.cpp \
+ src/gui/elems/basics/scrollPack.h \
+ src/gui/elems/basics/scrollPack.cpp \
src/gui/elems/basics/boxtypes.h \
src/gui/elems/basics/boxtypes.cpp \
src/gui/elems/basics/baseButton.h \
src/utils/ver.cpp \
src/utils/string.h \
src/utils/string.cpp \
- src/deps/rtaudio/RtAudio.h \
+ src/deps/rtaudio/RtAudio.h \
src/deps/rtaudio/RtAudio.cpp
sourcesTests = \
tests/main.cpp \
tests/utils.cpp \
tests/recorder.cpp \
tests/waveFx.cpp \
- tests/audioBuffer.cpp \
- tests/sampleChannel.cpp
-
+ tests/audioBuffer.cpp
if WITH_VST
sourcesExtra += \
bin_PROGRAMS = giada
-giada_SOURCES = $(sourcesCore) $(sourcesMain) $(sourcesExtra)
+giada_SOURCES = $(sourcesCore) $(sourcesExtra) $(sourcesMain)
giada_CPPFLAGS = $(cppFlags)
giada_CXXFLAGS = $(cxxFlags)
giada_LDADD = $(ldAdd)
## What is Giada?
-Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets.
+Giada is an open source, minimalistic and hardcore music production tool. Designed for DJs, live performers and electronic musicians.
<p align="center">
✦✦✦ <a href="http://www.youtube.com/user/GiadaLoopMachine">See Giada in action!</a> ✦✦✦
</p>
-
+
## Main features
+* Your sample player! Load samples from your crates and play them with a computer keyboard or a MIDI controller;
+* Your loop machine! Build your performance in real time by layering audio tracks or MIDI events, driven by the main sequencer;
+* Your song editor! Write songs from scratch or edit existing live recordings with the powerful Action Editor, for a fine-tuned control;
+* Your live recorder! Record sounds from the real world and MIDI events coming from external devices or other apps;
+* Your FX processor! Process samples or audio/MIDI input signals with VST instruments from your plug-ins collection;
+* Your MIDI controller! Control other software or synchronize physical MIDI devices by using Giada as a MIDI master sequencer.
+
+### And more:
+
* Ultra-lightweight internal design;
* multi-thread/multi-core support;
* 32-bit floating point audio engine;
* ALSA, JACK + Transport, CoreAudio, ASIO and DirectSound full support;
-* high quality internal resampler;
-* unlimited number of channels (controllable via computer keyboard);
-* several playback modes and combinations;
+* unlimited number of channels (optionally controllable via computer keyboard);
* BPM and beat sync with sample-accurate loop engine;
-* VST and VSTi (instrument) plug-in support;
-* MIDI input and output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps);
-* super-sleek, built-in wave editor;
-* live sampler from external inputs;
-* live action recorder with automatic quantizer;
-* piano Roll editor;
-* portable patch storage system, based on super-hackable JSON files;
+* MIDI output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps);
+* super-sleek, built-in Wave Editor for audio samples and Piano Roll editor for MIDI messages;
+* automatic quantizer;
+* portable project storage system, based on super-hackable JSON files;
* support for all major uncompressed file formats;
* test-driven development style supported by [Travis CI](https://travis-ci.org/monocasual/giada) and [Catch](https://github.com/philsquared/Catch)
* under a constant stage of development;
AC_ARG_ENABLE(
[vst],
AS_HELP_STRING([--enable-vst], [enable vst support]),
- [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)],
+ [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)],
[AM_CONDITIONAL(WITH_VST, false)]
)
AC_ARG_ENABLE(
[system-catch],
AS_HELP_STRING([--enable-system-catch], [use system-provided Catch library]),
- [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)],
+ [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)],
[AM_CONDITIONAL(WITH_SYSTEM_CATCH, false)]
)
AC_ARG_ENABLE(
[debug],
AS_HELP_STRING([--enable-debug], [enable debug mode (asserts, ...)]),
- [],
+ [],
[AC_DEFINE(NDEBUG)]
)
--- /dev/null
+#!/usr/bin/env bash
+#
+# Creates source tarballs for giada in the form of
+# 'giada-x.x.x-src.tar.gz' and optionally detached PGP signatures
+# for the created file of the form 'giada-x.x.x-src.tar.gz.asc'.
+# If the environment variable BUILD_DIR is provided, the files will be moved to
+# $BUILD_DIR/, else to the location of this script (the repository folder).
+#
+# Requirements:
+# - git
+# - tar
+# - a writable (user) /tmp folder for mktemp
+# - gnupg >= 2.0.0 (if source tarball signing is requested)
+# - a valid PGP signing key in the keyring (if source tarball signing is
+# requested)
+
+set -euo pipefail
+
+get_absolute_path() {
+ cd "$(dirname "$1")" && pwd -P
+}
+
+validate_project_tag() {
+ if ! git ls-remote -t "${upstream}"| grep -e "${version}$" > /dev/null; then
+ echo "The tag '$version' could not be found in upstream repository (${upstream})."
+ exit 1
+ fi
+}
+
+checkout_project() {
+ echo "Cloning project below working directory ${working_dir}"
+ cd "$working_dir"
+ git clone "$upstream" --branch "$version" \
+ --single-branch \
+ --depth=1 \
+ --recurse-submodules \
+ "${output_name}"
+}
+
+clean_sources() {
+ cd "${working_dir}/${output_name}"
+ echo "Removing unneeded files and folders..."
+ rm -rfv .git* \
+ .travis* \
+ create_source_tarball.sh
+}
+
+compress_sources() {
+ cd "${working_dir}"
+ tar cvfz "${output_name}.tar.gz" "${output_name}"
+}
+
+move_sources() {
+ cd "${working_dir}"
+ mv -v "${output_name}.tar.gz" "${output_dir}/"
+}
+
+sign_sources() {
+ cd "${output_dir}"
+ gpg --detach-sign \
+ -u "${signer}" \
+ -o "${output_name}.tar.gz.asc" \
+ "${output_name}.tar.gz"
+}
+
+cleanup_working_dir() {
+ echo "Removing working directory: ${working_dir}"
+ rm -rf "${working_dir}"
+}
+
+print_help() {
+ echo "Usage: $0 -v <version tag> -s <signature email or key ID>"
+ exit 1
+}
+
+if [ -n "${BUILD_DIR:-}" ]; then
+ echo "Build dir provided: ${BUILD_DIR}"
+ output_dir="${BUILD_DIR}/"
+ mkdir -p "${output_dir}"
+else
+ output_dir="$(get_absolute_path "$0")"
+fi
+
+upstream="https://github.com/monocasual/giada"
+package_name="giada"
+working_dir="$(mktemp -d)"
+version="$(date '+%Y-%m-%d')"
+output_version=""
+output_name=""
+signer=""
+signature=0
+
+# remove the working directory, no matter what
+trap cleanup_working_dir EXIT
+
+if [ ${#@} -gt 0 ]; then
+ while getopts 'hv:s:' flag; do
+ case "${flag}" in
+ h) print_help
+ ;;
+ s) signer=$OPTARG
+ signature=1
+ ;;
+ v) version=$OPTARG
+ output_version="${version//v}"
+ ;;
+ *)
+ echo "Error! Try '${0} -h'."
+ exit 1
+ ;;
+ esac
+ done
+else
+ print_help
+fi
+
+output_name="${package_name}-${output_version}-src"
+validate_project_tag
+checkout_project
+clean_sources
+compress_sources
+move_sources
+if [ $signature -eq 1 ]; then
+ sign_sources
+fi
+
+exit 0
+
+# vim:set ts=4 sw=4 et:
-#include <new>
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
#include <cassert>
-#include <cstring>
+#include <algorithm>
#include "audioBuffer.h"
namespace m
{
AudioBuffer::AudioBuffer()
- : m_data (nullptr),
- m_size (0),
- m_channels(0)
+: m_data (nullptr)
+, m_size (0)
+, m_channels(0)
{
}
+AudioBuffer::AudioBuffer(Frame size, int channels)
+: AudioBuffer()
+{
+ alloc(size, channels);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioBuffer::AudioBuffer(const AudioBuffer& o)
+: m_data (new float[o.m_size * o.m_channels])
+, m_size (o.m_size)
+, m_channels(o.m_channels)
+{
+ std::copy(o.m_data, o.m_data + (o.m_size * o.m_channels), m_data);
+}
+
+
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-float* AudioBuffer::operator [](int offset) const
+float* AudioBuffer::operator [](Frame offset) const
{
assert(m_data != nullptr);
assert(offset < m_size);
/* -------------------------------------------------------------------------- */
-void AudioBuffer::clear(int a, int b)
+void AudioBuffer::clear(Frame a, Frame b)
{
if (m_data == nullptr)
return;
if (b == -1) b = m_size;
- memset(m_data + (a * m_channels), 0, (b - a) * m_channels * sizeof(float));
+ std::fill_n(m_data + (a * m_channels), (b - a) * m_channels, 0.0);
}
/* -------------------------------------------------------------------------- */
-int AudioBuffer::countFrames() const { return m_size; }
-int AudioBuffer::countSamples() const { return m_size * m_channels; }
-int AudioBuffer::countChannels() const { return m_channels; }
-bool AudioBuffer::isAllocd() const { return m_data != nullptr; }
+Frame AudioBuffer::countFrames() const { return m_size; }
+int AudioBuffer::countSamples() const { return m_size * m_channels; }
+int AudioBuffer::countChannels() const { return m_channels; }
+bool AudioBuffer::isAllocd() const { return m_data != nullptr; }
+
+
+
+/* -------------------------------------------------------------------------- */
+
+float AudioBuffer::getPeak() const
+{
+ float peak = 0.0f;
+ for (int i = 0; i < countSamples(); i++)
+ peak = std::max(peak, m_data[i]);
+ return peak;
+}
/* -------------------------------------------------------------------------- */
-void AudioBuffer::alloc(int size, int channels)
+void AudioBuffer::alloc(Frame size, int channels)
{
free();
m_size = size;
m_channels = channels;
m_data = new float[m_size * m_channels];
- clear(); // does nothing if m_data == nullptr
+ clear();
}
void AudioBuffer::free()
{
- delete[] m_data; // No check required, delete nullptr does nothing
+ delete[] m_data;
setData(nullptr, 0, 0);
}
/* -------------------------------------------------------------------------- */
-void AudioBuffer::setData(float* data, int size, int channels)
+void AudioBuffer::setData(float* data, Frame size, int channels)
{
m_data = data;
m_size = size;
/* -------------------------------------------------------------------------- */
-void AudioBuffer::copyFrame(int frame, float* values)
+void AudioBuffer::copyData(const float* data, Frame frames, int offset)
{
assert(m_data != nullptr);
- memcpy(m_data + (frame * m_channels), values, m_channels * sizeof(float));
+ assert(frames <= m_size - offset);
+ std::copy_n(data, frames * m_channels, m_data + (offset * m_channels));
+}
+
+
+void AudioBuffer::copyData(const AudioBuffer& b, float gain)
+{
+ copyData(b[0], b.countFrames());
+ if (gain != 1.0f)
+ applyGain(gain);
}
/* -------------------------------------------------------------------------- */
-void AudioBuffer::copyData(const float* data, int frames, int offset)
+
+void AudioBuffer::addData(const AudioBuffer& b, float gain, Pan pan)
{
assert(m_data != nullptr);
- assert(frames <= m_size - offset);
- memcpy(m_data + (offset * m_channels), data, frames * m_channels * sizeof(float));
+ assert(countFrames() <= b.countFrames());
+ assert(countChannels() == pan.size());
+
+ for (int i = 0; i < countFrames(); i++)
+ for (int j = 0; j < countChannels(); j++)
+ (*this)[i][j] += b[i][j] * gain * pan[j];
}
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioBuffer::applyGain(float g)
+{
+ for (int i = 0; i < countSamples(); i++)
+ m_data[i] *= g;
+}
}} // giada::m::
\ No newline at end of file
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
#ifndef G_AUDIO_BUFFER_H
#define G_AUDIO_BUFFER_H
+#include <array>
+#include "core/types.h"
+#include "core/const.h"
+
+
namespace giada {
namespace m
{
-/* TODO - this class needs a serious modern C++ lifting */
class AudioBuffer
{
public:
+ using Pan = std::array<float, G_MAX_IO_CHANS>;
+
+ /* AudioBuffer (1)
+ Creates an empty (and invalid) audio buffer. */
+
AudioBuffer();
+
+ /* AudioBuffer (2)
+ Creates an audio buffer and allocates memory for size * channels frames. */
+
+ AudioBuffer(Frame size, int channels);
+
+ AudioBuffer(const AudioBuffer& o);
~AudioBuffer();
/* operator []
float* operator [](int offset) const;
- int countFrames() const;
+ Frame countFrames() const;
int countSamples() const;
int countChannels() const;
bool isAllocd() const;
- void alloc(int size, int channels);
+ /* getPeak
+ Returns the highest value from any channel. */
+
+ float getPeak() const;
+
+ void alloc(Frame size, int channels);
void free();
/* copyData
Copies 'frames' frames from the new 'data' into m_data, and fills m_data
- starting from frame 'offset'. It takes for granted that the new data contains
- the same number of channels than m_channels. */
-
- void copyData(const float* data, int frames, int offset=0);
+ starting from frame 'offset'. The new data MUST contain the same number of
+ channels than m_channels. */
- /* copyFrame
- Copies data pointed by 'values' into m_data[frame]. It takes for granted that
- 'values' contains the same number of channels than m_channels. */
+ void copyData(const float* data, Frame frames, int offset=0);
- void copyFrame(int frame, float* values);
+ void copyData(const AudioBuffer& b, float gain=1.0f);
+ void addData(const AudioBuffer& b, float gain=1.0f, Pan pan={1.0f, 1.0f});
/* setData
Borrow 'data' as new m_data. Makes sure not to delete the data 'data' points
to while using it. Set it back to nullptr when done. */
- void setData(float* data, int size, int channels);
+ void setData(float* data, Frame size, int channels);
/* moveData
Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer. */
Clears the internal data by setting all bytes to 0.0f. Optional parameters
'a' and 'b' set the range. */
- void clear(int a=0, int b=-1);
+ void clear(Frame a=0, Frame b=-1);
+
+ void applyGain(float g);
private:
float* m_data;
- int m_size; // in frames
+ Frame m_size;
int m_channels;
};
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "audioReceiver.h"
+
+
+namespace giada {
+namespace m
+{
+AudioReceiver::AudioReceiver(ChannelState* c)
+: state (std::make_unique<AudioReceiverState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiver::AudioReceiver(const patch::Channel& p, ChannelState* c)
+: state (std::make_unique<AudioReceiverState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiver::AudioReceiver(const AudioReceiver& o, ChannelState* c)
+: state (std::make_unique<AudioReceiverState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void AudioReceiver::render(const AudioBuffer& in) const
+{
+ /* If armed and input monitor is on, copy input buffer to channel buffer:
+ this enables the input monitoring. The channel buffer will be overwritten
+ later on by pluginHost::processStack, so that you would record "clean" audio
+ (i.e. not plugin-processed). */
+
+ bool armed = m_channelState->armed.load();
+ bool inputMonitor = state->inputMonitor.load();
+
+ if (armed && inputMonitor)
+ m_channelState->buffer.addData(in); // add, don't overwrite
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_AUDIO_RECEIVER_H
+#define G_CHANNEL_AUDIO_RECEIVER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace patch
+{
+struct Channel;
+}
+class AudioBuffer;
+struct ChannelState;
+struct AudioReceiverState;
+
+/* AudioReceiver
+Operates on input audio streams for audio recording and input monitor. */
+
+class AudioReceiver
+{
+public:
+
+ AudioReceiver(ChannelState*);
+ AudioReceiver(const patch::Channel&, ChannelState*);
+ AudioReceiver(const AudioReceiver&, ChannelState* c=nullptr);
+
+ void render(const AudioBuffer& in) const;
+
+ /* state
+ Pointer to mutable AudioReceiverState state. */
+
+ std::unique_ptr<AudioReceiverState> state;
+
+private:
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
#include <cassert>
-#include "utils/log.h"
-#include "core/channels/channelManager.h"
-#include "core/const.h"
-#include "core/pluginManager.h"
-#include "core/plugin.h"
-#include "core/kernelMidi.h"
-#include "core/patch.h"
-#include "core/clock.h"
-#include "core/wave.h"
-#include "core/mixer.h"
+#include "core/channels/state.h"
#include "core/mixerHandler.h"
-#include "core/recorderHandler.h"
-#include "core/conf.h"
-#include "core/patch.h"
-#include "core/waveFx.h"
-#include "core/midiMapConf.h"
+#include "core/pluginHost.h"
#include "channel.h"
namespace giada {
namespace m
{
-Channel::Channel(ChannelType type, ChannelStatus playStatus, int bufferSize,
- ID columnId, ID id)
-: type (type),
- playStatus (playStatus),
- recStatus (ChannelStatus::OFF),
- columnId (columnId),
- id (id),
- height (G_GUI_UNIT),
- previewMode (PreviewMode::NONE),
- pan (0.5f),
- volume (G_DEFAULT_VOL),
- armed (false),
- key (0),
- mute (false),
- solo (false),
- volume_i (1.0f),
- volume_d (0.0f),
- hasActions (false),
- readActions (false),
- midiIn (true),
- midiInKeyPress (0x0),
- midiInKeyRel (0x0),
- midiInKill (0x0),
- midiInArm (0x0),
- midiInVolume (0x0),
- midiInMute (0x0),
- midiInSolo (0x0),
- midiInFilter (-1),
- midiOutL (false),
- midiOutLplaying(0x0),
- midiOutLmute (0x0),
- midiOutLsolo (0x0)
+Channel::Channel(ChannelType type, ID id, ID columnId, Frame bufferSize)
+: id (id)
+, state (std::make_unique<ChannelState>(id, bufferSize))
+, midiLighter (state.get())
+, m_type (type)
+, m_columnId (columnId)
{
- buffer.alloc(bufferSize, G_MAX_IO_CHANS);
+ switch (m_type) {
+ case ChannelType::SAMPLE:
+ samplePlayer.emplace(state.get());
+ audioReceiver.emplace(state.get());
+ sampleActionRecorder.emplace(state.get(), samplePlayer->state.get());
+ break;
+
+ case ChannelType::PREVIEW:
+ samplePlayer.emplace(state.get());
+ break;
+
+ case ChannelType::MIDI:
+ midiController.emplace(state.get());
#ifdef WITH_VST
-
- midiBuffer.ensureSize(bufferSize);
-
+ midiReceiver.emplace(state.get());
#endif
+ midiSender.emplace(state.get());
+ midiActionRecorder.emplace(state.get());
+ break;
+
+ default: break;
+ }
}
Channel::Channel(const Channel& o)
-: type (o.type),
- playStatus (o.playStatus),
- recStatus (o.recStatus),
- columnId (o.columnId),
- id (o.id),
- height (o.height),
- previewMode (o.previewMode),
- pan (o.pan),
- volume (o.volume),
- armed (o.armed),
- name (o.name),
- key (o.key),
- mute (o.mute),
- solo (o.solo),
- volume_i (o.volume_i),
- volume_d (o.volume_d),
- hasActions (o.hasActions),
- readActions (o.readActions),
- midiIn (o.midiIn),
- midiInKeyPress (o.midiInKeyPress),
- midiInKeyRel (o.midiInKeyRel),
- midiInKill (o.midiInKill),
- midiInArm (o.midiInArm),
- midiInVolume (o.midiInVolume),
- midiInMute (o.midiInMute),
- midiInSolo (o.midiInSolo),
- midiInFilter (o.midiInFilter),
- midiOutL (o.midiOutL),
- midiOutLplaying(o.midiOutLplaying),
- midiOutLmute (o.midiOutLmute),
- midiOutLsolo (o.midiOutLsolo)
+: id (o.id)
#ifdef WITH_VST
- ,pluginIds (o.pluginIds)
+, pluginIds (o.pluginIds)
#endif
+, state (std::make_unique<ChannelState>(*o.state))
+, midiLearner (o.midiLearner)
+, midiLighter (o.midiLighter, state.get())
+, m_type (o.m_type)
+, m_columnId (o.m_columnId)
{
- buffer.alloc(o.buffer.countFrames(), G_MAX_IO_CHANS);
+ switch (m_type) {
+
+ case ChannelType::SAMPLE:
+ samplePlayer.emplace(o.samplePlayer.value(), state.get());
+ audioReceiver.emplace(o.audioReceiver.value(), state.get());
+ sampleActionRecorder.emplace(o.sampleActionRecorder.value(), state.get(), samplePlayer->state.get());
+ break;
+
+ case ChannelType::PREVIEW:
+ samplePlayer.emplace(o.samplePlayer.value(), state.get());
+ break;
+
+ case ChannelType::MIDI:
+ midiController.emplace(o.midiController.value(), state.get());
+#ifdef WITH_VST
+ midiReceiver.emplace(o.midiReceiver.value(), state.get());
+#endif
+ midiSender.emplace(o.midiSender.value(), state.get());
+ midiActionRecorder.emplace(o.midiActionRecorder.value(), state.get());
+ break;
+
+ default: break;
+ }
}
/* -------------------------------------------------------------------------- */
-Channel::Channel(const patch::Channel& p, int bufferSize)
-: type (p.type),
- playStatus (p.waveId == 0 && type == ChannelType::SAMPLE ? ChannelStatus::EMPTY : ChannelStatus::OFF),
- recStatus (ChannelStatus::OFF),
- columnId (p.columnId),
- id (p.id),
- height (p.height),
- previewMode (PreviewMode::NONE),
- pan (p.pan),
- volume (p.volume),
- armed (p.armed),
- name (p.name),
- key (p.key),
- mute (p.mute),
- solo (p.solo),
- volume_i (1.0),
- volume_d (0.0),
- hasActions (p.hasActions),
- readActions (p.readActions),
- midiIn (p.midiIn),
- midiInKeyPress (p.midiInKeyPress),
- midiInKeyRel (p.midiInKeyRel),
- midiInKill (p.midiInKill),
- midiInArm (p.midiInArm),
- midiInVolume (p.midiInVolume),
- midiInMute (p.midiInMute),
- midiInSolo (p.midiInSolo),
- midiInFilter (p.midiInFilter),
- midiOutL (p.midiOutL),
- midiOutLplaying(p.midiOutLplaying),
- midiOutLmute (p.midiOutLmute),
- midiOutLsolo (p.midiOutLsolo)
+Channel::Channel(const patch::Channel& p, Frame bufferSize)
+: id (p.id)
#ifdef WITH_VST
- ,pluginIds (p.pluginIds)
+, pluginIds (p.pluginIds)
#endif
+, state (std::make_unique<ChannelState>(p, bufferSize))
+, midiLearner (p)
+, midiLighter (p, state.get())
+, m_type (p.type)
+, m_columnId (p.columnId)
{
- buffer.alloc(bufferSize, G_MAX_IO_CHANS);
+ switch (m_type) {
+
+ case ChannelType::SAMPLE:
+ samplePlayer.emplace(p, state.get());
+ audioReceiver.emplace(p, state.get());
+ sampleActionRecorder.emplace(state.get(), samplePlayer->state.get());
+ break;
+
+ case ChannelType::PREVIEW:
+ samplePlayer.emplace(p, state.get());
+ break;
+
+ case ChannelType::MIDI:
+ midiController.emplace(state.get());
+#ifdef WITH_VST
+ midiReceiver.emplace(p, state.get());
+#endif
+ midiSender.emplace(state.get());
+ midiActionRecorder.emplace(state.get());
+ break;
+
+ default: break;
+ }
}
/* -------------------------------------------------------------------------- */
-bool Channel::isPlaying() const
+void Channel::parse(const mixer::EventBuffer& events, bool audible) const
{
- return playStatus == ChannelStatus::PLAY ||
- playStatus == ChannelStatus::ENDING;
+ for (const mixer::Event& e : events) {
+
+ if (e.action.channelId > 0 && e.action.channelId != id)
+ continue;
+
+ parse(e);
+ midiLighter.parse(e, audible);
+
+ if (midiController) midiController->parse(e);
+#ifdef WITH_VST
+ if (midiReceiver) midiReceiver->parse(e);
+#endif
+ if (midiSender) midiSender->parse(e);
+ if (samplePlayer) samplePlayer->parse(e);
+ if (midiActionRecorder) midiActionRecorder->parse(e);
+ if (sampleActionRecorder && samplePlayer && samplePlayer->hasWave())
+ sampleActionRecorder->parse(e);
+ }
}
/* -------------------------------------------------------------------------- */
-void Channel::sendMidiLmute()
+void Channel::advance(Frame bufferSize) const
{
- if (!midiOutL || midiOutLmute == 0x0)
- return;
- if (mute)
- kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOn);
- else
- kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOff);
+ /* TODO - this is used only to advance samplePlayer for its quantizer. Use
+ this to render actions in the future. */
+
+ if (samplePlayer) samplePlayer->advance(bufferSize);
}
/* -------------------------------------------------------------------------- */
-void Channel::sendMidiLsolo()
+void Channel::render(AudioBuffer* out, AudioBuffer* in, bool audible) const
{
- if (!midiOutL || midiOutLsolo == 0x0)
- return;
- if (solo)
- kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOn);
+ if (id == mixer::MASTER_OUT_CHANNEL_ID)
+ renderMasterOut(*out);
else
- kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOff);
+ if (id == mixer::MASTER_IN_CHANNEL_ID)
+ renderMasterIn(*in);
+ else
+ renderChannel(*out, *in, audible);
}
/* -------------------------------------------------------------------------- */
-void Channel::sendMidiLstatus()
+void Channel::parse(const mixer::Event& e) const
{
- if (!midiOutL || midiOutLplaying == 0x0)
- return;
- switch (playStatus) {
- case ChannelStatus::OFF:
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopped);
- break;
- case ChannelStatus::WAIT:
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.waiting);
- break;
- case ChannelStatus::ENDING:
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopping);
- break;
- case ChannelStatus::PLAY:
- if ((mixer::isChannelAudible(this) && !mute) ||
- !midimap::isDefined(midimap::midimap.playingInaudible))
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playing);
- else
- kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playingInaudible);
- break;
- default:
+ switch (e.type) {
+
+ case mixer::EventType::CHANNEL_VOLUME:
+ state->volume.store(e.action.event.getVelocityFloat()); break;
+
+ case mixer::EventType::CHANNEL_PAN:
+ state->pan.store(e.action.event.getVelocityFloat()); break;
+
+ case mixer::EventType::CHANNEL_MUTE:
+ state->mute.store(!state->mute.load()); break;
+
+ case mixer::EventType::CHANNEL_TOGGLE_ARM:
+ state->armed.store(!state->armed.load()); break;
+
+ case mixer::EventType::CHANNEL_SOLO:
+ state->solo.store(!state->solo.load());
+ m::mh::updateSoloCount();
break;
+
+ default: break;
}
}
/* -------------------------------------------------------------------------- */
-bool Channel::isMidiInAllowed(int c) const
+void Channel::renderMasterOut(AudioBuffer& out) const
{
- return midiInFilter == -1 || midiInFilter == c;
+ state->buffer.copyData(out);
+#ifdef WITH_VST
+ if (pluginIds.size() > 0)
+ pluginHost::processStack(state->buffer, pluginIds, nullptr);
+#endif
+ out.copyData(state->buffer, state->volume.load());
}
/* -------------------------------------------------------------------------- */
-void Channel::setPan(float v)
+void Channel::renderMasterIn(AudioBuffer& in) const
{
- if (v > 1.0f) v = 1.0f;
- else
- if (v < 0.0f) v = 0.0f;
- pan = v;
+#ifdef WITH_VST
+ if (pluginIds.size() > 0)
+ pluginHost::processStack(in, pluginIds, nullptr);
+#endif
}
-float Channel::getPan() const
+/* -------------------------------------------------------------------------- */
+
+
+void Channel::renderChannel(AudioBuffer& out, AudioBuffer& in, bool audible) const
{
- return pan;
-}
+ state->buffer.clear();
+ if (samplePlayer) samplePlayer->render(out);
+ if (audioReceiver) audioReceiver->render(in);
-/* -------------------------------------------------------------------------- */
+ /* If MidiReceiver exists, let it process the plug-in stack, as it can
+ contain plug-ins that take MIDI events (i.e. synths). Otherwise process the
+ plug-in stack internally with no MIDI events. */
+#ifdef WITH_VST
+ if (midiReceiver)
+ midiReceiver->render(pluginIds);
+ else
+ if (pluginIds.size() > 0)
+ pluginHost::processStack(state->buffer, pluginIds, nullptr);
+#endif
-float Channel::calcPanning(int ch) const
-{
- float p = pan;
- if (p == 0.5f) // center: nothing to do
- return 1.0;
- if (ch == 0)
- return 1.0 - p;
- else // channel 1
- return p;
+ if (audible)
+ out.addData(state->buffer, state->volume.load() * state->volume_i, calcPanning());
}
/* -------------------------------------------------------------------------- */
-void Channel::calcVolumeEnvelope()
+AudioBuffer::Pan Channel::calcPanning() const
{
- volume_i = volume_i + volume_d;
- if (volume_i < 0.0f)
- volume_i = 0.0f;
- else
- if (volume_i > 1.0f)
- volume_i = 1.0f;
-}
+ /* TODO - precompute the AudioBuffer::Pan when pan value changes instead of
+ building it on the fly. */
+
+ float pan = state->pan.load();
+ /* Center pan (0.5f)? Pass-through. */
-bool Channel::isPreview() const
-{
- return previewMode != PreviewMode::NONE;
+ if (pan == 0.5f) return { 1.0f, 1.0f };
+ return { 1.0f - pan, pan };
}
/* -------------------------------------------------------------------------- */
+ID Channel::getColumnId() const { return m_columnId; };
+ChannelType Channel::getType() const { return m_type; };
+
+
+/* -------------------------------------------------------------------------- */
+
+
bool Channel::isInternal() const
{
- return id == mixer::MASTER_OUT_CHANNEL_ID ||
- id == mixer::MASTER_IN_CHANNEL_ID ||
- id == mixer::PREVIEW_CHANNEL_ID;
+ return m_type == ChannelType::MASTER || m_type == ChannelType::PREVIEW;
}
-/* -------------------------------------------------------------------------- */
+bool Channel::isMuted() const
+{
+ /* Internals can't be muted. */
+ return !isInternal() && state->mute.load() == true;
+}
-bool Channel::isReadingActions() const
+bool Channel::canInputRec() const
{
- return hasActions && readActions;
+ return samplePlayer && !samplePlayer->hasWave() && state->armed.load() == true;
}
-
}} // giada::m::
#define G_CHANNEL_H
-#include <vector>
-#include <string>
-#include "core/types.h"
-#include "core/patch.h"
+#include <optional>
+#include "core/const.h"
#include "core/mixer.h"
-#include "core/midiMapConf.h"
-#include "core/midiEvent.h"
-#include "core/recorder.h"
-#include "core/audioBuffer.h"
+#include "core/channels/state.h"
+#include "core/channels/samplePlayer.h"
+#include "core/channels/audioReceiver.h"
#ifdef WITH_VST
-#include "deps/juce-config.h"
-#include "core/plugin.h"
-#include "core/pluginHost.h"
-#include "core/queue.h"
+#include "core/channels/midiReceiver.h"
#endif
+#include "core/channels/midiLearner.h"
+#include "core/channels/midiSender.h"
+#include "core/channels/midiController.h"
+#include "core/channels/midiLighter.h"
+#include "core/channels/sampleActionRecorder.h"
+#include "core/channels/midiActionRecorder.h"
namespace giada {
namespace m
{
-class Channel
+class Channel final
{
public:
- virtual ~Channel() {};
+ Channel(ChannelType t, ID id, ID columnId, Frame bufferSize);
+ Channel(const Channel&);
+ Channel(const patch::Channel& p, Frame bufferSize);
+ Channel(Channel&&) = default;
+ Channel& operator=(const Channel&) = default;
+ Channel& operator=(Channel&&) = default;
+ ~Channel() = default;
- /* clone
- A trick to give the caller the ability to invoke the derived class copy
- constructor given the base. TODO - This thing will go away with the Channel
- "no-virtual inheritance" refactoring. */
+ /* parse
+ Parses live events. */
- virtual Channel* clone() const = 0;
+ void parse(const mixer::EventBuffer& e, bool audible) const;
- /* load
- Loads persistence data into an existing channel. Used for built-in channels
- such as masters and preview. */
+ /* advance
+ Processes static events (e.g. actions) in the current block. */
- virtual void load(const patch::Channel& p) {}
+ void advance(Frame bufferSize) const;
- /* parseEvents
- Prepares channel for rendering. This is called on each frame. */
+ /* render
+ Renders audio data to I/O buffers. */
+
+ void render(AudioBuffer* out, AudioBuffer* in, bool audible) const;
- virtual void parseEvents(mixer::FrameEvents fe) {};
+ bool isInternal() const;
+ bool isMuted() const;
+ bool canInputRec() const;
+ ID getColumnId() const;
+ ChannelType getType() const;
+
+ ID id;
- /* render
- Audio rendering. Warning: inBuffer might be unallocated if no input devices
- are available for recording. */
-
- virtual void render(AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running) {};
-
- /* start
- Action to do when channel starts. doQuantize = false (don't quantize)
- when Mixer is reading actions from Recorder. */
-
- virtual void start(int localFrame, bool doQuantize, int velocity) {};
-
- /* stop
- What to do when channel is stopped normally (via key or MIDI). */
-
- virtual void stop() {};
-
- /* kill
- What to do when channel stops abruptly. */
-
- virtual void kill(int localFrame) {};
-
- /* set mute
- What to do when channel is un/muted. */
-
- virtual void setMute(bool value) {};
-
- /* set solo
- What to do when channel is un/soloed. */
-
- virtual void setSolo(bool value) {};
-
- /* empty
- Frees any associated resources (e.g. waveform for SAMPLE). */
-
- virtual void empty() {};
-
- /* stopBySeq
- What to do when channel is stopped by sequencer. */
-
- virtual void stopBySeq(bool chansStopOnSeqHalt) {};
-
- /* rewind
- Rewinds channel when rewind button is pressed. */
-
- virtual void rewindBySeq() {};
-
- /* canInputRec
- Tells whether a channel can accept and handle input audio. Always false for
- Midi channels, true for Sample channels only if they don't contain a
- sample yet.*/
-
- virtual bool canInputRec() const { return false; };
- virtual bool hasLogicalData() const { return false; };
- virtual bool hasEditedData() const { return false; };
- virtual bool hasData() const { return false; };
-
- virtual bool recordStart(bool canQuantize) { return true; };
- virtual bool recordKill() { return true; };
- virtual void recordStop() {};
-
- virtual void startReadingActions(bool treatRecsAsLoops,
- bool recsStopOnChanHalt) {};
- virtual void stopReadingActions(bool running, bool treatRecsAsLoops,
- bool recsStopOnChanHalt) {};
-
- virtual void stopInputRec(int globalFrame) {};
-
- /* receiveMidi
- Receives and processes midi messages from external devices. */
-
- virtual void receiveMidi(const MidiEvent& midiEvent) {};
-
- /* calcPanning
- Given an audio channel (stereo: 0 or 1) computes the current panning value. */
-
- float calcPanning(int ch) const;
-
- bool isPlaying() const;
- float getPan() const;
- bool isPreview() const;
- bool isInternal() const;
-
- /* isMidiInAllowed
- Given a MIDI channel 'c' tells whether this channel should be allowed to
- receive and process MIDI events on MIDI channel 'c'. */
-
- bool isMidiInAllowed(int c) const;
-
- /* isReadingActions
- Tells whether the channel as actions and it is currently reading them. */
-
- bool isReadingActions() const;
-
- /* sendMidiL*
- Sends MIDI lightning events to a physical device. */
-
- void sendMidiLmute();
- void sendMidiLsolo();
- void sendMidiLstatus();
-
- void setPan(float v);
-
- void calcVolumeEnvelope();
-
- /* buffer
- Working buffer for internal processing. */
-
- AudioBuffer buffer;
-
- ChannelType type;
- ChannelStatus playStatus;
- ChannelStatus recStatus;
-
- ID columnId;
- ID id;
-
- int height;
-
- /* previewMode
- Whether the channel is in audio preview mode or not. */
-
- PreviewMode previewMode;
-
- float pan;
- float volume; // global volume
- bool armed;
- std::string name;
- int key;
- bool mute;
- bool solo;
-
- /* volume_*
- Internal volume variables: volume_i for envelopes, volume_d keeps track of
- the delta during volume changes (or the line slope between two volume
- points). */
-
- double volume_i;
- double volume_d;
-
- bool hasActions; // If has some actions recorded
- bool readActions; // If should read recorded actions
-
- bool midiIn; // enable midi input
- uint32_t midiInKeyPress;
- uint32_t midiInKeyRel;
- uint32_t midiInKill;
- uint32_t midiInArm;
- uint32_t midiInVolume;
- uint32_t midiInMute;
- uint32_t midiInSolo;
-
- /* midiInFilter
- Which MIDI channel should be filtered out when receiving MIDI messages. -1
- means 'all'. */
+#ifdef WITH_VST
+ std::vector<ID> pluginIds;
+#endif
- int midiInFilter;
+ /* state
+ Pointer to mutable Channel state. */
- /* midiOutL*
- Enables MIDI lightning output, plus a set of midi lighting event to be sent
- to a device. Those events basically contains the MIDI channel, everything
- else gets stripped out. */
+ std::unique_ptr<ChannelState> state;
- bool midiOutL;
- uint32_t midiOutLplaying;
- uint32_t midiOutLmute;
- uint32_t midiOutLsolo;
+ MidiLearner midiLearner;
+ MidiLighter midiLighter;
+ std::optional<SamplePlayer> samplePlayer;
+ std::optional<AudioReceiver> audioReceiver;
+ std::optional<MidiController> midiController;
#ifdef WITH_VST
+ std::optional<MidiReceiver> midiReceiver;
+#endif
+ std::optional<MidiSender> midiSender;
+ std::optional<SampleActionRecorder> sampleActionRecorder;
+ std::optional<MidiActionRecorder> midiActionRecorder;
- std::vector<ID> pluginIds;
-
- /* MidiBuffer
- Contains MIDI events. When ready, events are sent to each plugin in the
- channel. This is available for any kind of channel, but it makes sense only
- for MIDI channels. */
-
- juce::MidiBuffer midiBuffer;
-
- /* midiQueue
- FIFO queue for collecting MIDI events from the MIDI thread and passing them
- to the audio thread. */
- /* TODO - magic number */
+private:
- Queue<MidiEvent, 32> midiQueue;
+ void parse(const mixer::Event& e) const;
-#endif
+ void renderMasterOut(AudioBuffer& out) const;
+ void renderMasterIn(AudioBuffer& in) const;
+ void renderChannel(AudioBuffer& out, AudioBuffer& in, bool audible) const;
-protected:
+ AudioBuffer::Pan calcPanning() const;
- Channel(ChannelType type, ChannelStatus status, int bufferSize,
- ID columnId, ID id);
- Channel(const Channel& o);
- Channel(const patch::Channel& p, int bufferSize);
+ ChannelType m_type;
+ ID m_columnId;
};
-
}} // giada::m::
#include <cassert>
#include "utils/fs.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
-#include "core/channels/masterChannel.h"
#include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
#include "core/const.h"
+#include "core/kernelAudio.h"
#include "core/patch.h"
#include "core/mixer.h"
#include "core/idManager.h"
/* -------------------------------------------------------------------------- */
-std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
+std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
bool inputMonitorOn, ID columnId)
{
- std::unique_ptr<Channel> ch = nullptr;
-
- if (type == ChannelType::SAMPLE)
- ch = std::make_unique<SampleChannel>(inputMonitorOn, bufferSize, columnId, channelId_.get());
- else
- if (type == ChannelType::MIDI)
- ch = std::make_unique<MidiChannel>(bufferSize, columnId, channelId_.get());
- else
- if (type == ChannelType::MASTER)
- ch = std::make_unique<MasterChannel>(bufferSize, channelId_.get());
- else
- if (type == ChannelType::PREVIEW)
- ch = std::make_unique<MasterChannel>(bufferSize, channelId_.get()); // TODO - temporary placeholder
-
- assert(ch != nullptr);
- return ch;
+ return std::make_unique<Channel>(type, channelId_.get(), columnId,
+ kernelAudio::getRealBufSize());
}
std::unique_ptr<Channel> create(const Channel& o)
{
- std::unique_ptr<Channel> ch = nullptr;
-
- if (o.type == ChannelType::SAMPLE)
- ch = std::make_unique<SampleChannel>(static_cast<const SampleChannel&>(o));
- else
- if (o.type == ChannelType::MIDI)
- ch = std::make_unique<MidiChannel>(static_cast<const MidiChannel&>(o));
- else
- if (o.type == ChannelType::MASTER)
- ch = std::make_unique<MasterChannel>(static_cast<const MasterChannel&>(o));
-
- assert(ch != nullptr);
-
- if (o.type != ChannelType::MASTER)
- ch->id = channelId_.get();
-
+ std::unique_ptr<Channel> ch = std::make_unique<Channel>(o);
+ ch->id = channelId_.get();
return ch;
}
std::unique_ptr<Channel> deserializeChannel(const patch::Channel& pch, int bufferSize)
{
- std::unique_ptr<Channel> ch = nullptr;
-
- if (pch.type == ChannelType::SAMPLE)
- ch = std::make_unique<SampleChannel>(pch, bufferSize);
- else
- if (pch.type == ChannelType::MIDI)
- ch = std::make_unique<MidiChannel>(pch, bufferSize);
-
- assert(ch != nullptr);
-
channelId_.set(pch.id);
-
- return ch;
+ return std::make_unique<Channel>(pch, bufferSize);
}
{
patch::Channel pc;
- pc.id = c.id;
- pc.type = c.type;
-
#ifdef WITH_VST
for (ID pid : c.pluginIds)
pc.pluginIds.push_back(pid);
-#endif
-
- if (c.type != ChannelType::MASTER) {
- pc.height = c.height;
- pc.name = c.name.c_str();
- pc.columnId = c.columnId;
- pc.key = c.key;
- pc.mute = c.mute;
- pc.solo = c.solo;
- pc.volume = c.volume;
- pc.pan = c.pan;
- pc.hasActions = c.hasActions;
- pc.armed = c.armed;
- pc.midiIn = c.midiIn;
- pc.midiInKeyPress = c.midiInKeyRel;
- pc.midiInKeyRel = c.midiInKeyPress;
- pc.midiInKill = c.midiInKill;
- pc.midiInArm = c.midiInArm;
- pc.midiInVolume = c.midiInVolume;
- pc.midiInMute = c.midiInMute;
- pc.midiInSolo = c.midiInSolo;
- pc.midiInFilter = c.midiInFilter;
- pc.midiOutL = c.midiOutL;
- pc.midiOutLplaying = c.midiOutLplaying;
- pc.midiOutLmute = c.midiOutLmute;
- pc.midiOutLsolo = c.midiOutLsolo;
- }
+#endif
+
+ pc.id = c.id;
+ pc.type = c.getType();
+ pc.columnId = c.getColumnId();
+ pc.height = c.state->height;
+ pc.name = c.state->name;
+ pc.key = c.state->key.load();
+ pc.mute = c.state->mute.load();
+ pc.solo = c.state->solo.load();
+ pc.volume = c.state->volume.load();
+ pc.pan = c.state->pan.load();
+ pc.hasActions = c.state->hasActions;
+ pc.readActions = c.state->readActions.load();
+ pc.armed = c.state->armed.load();
+ pc.midiIn = c.midiLearner.state->enabled.load();
+ pc.midiInFilter = c.midiLearner.state->filter.load();
+ pc.midiInKeyPress = c.midiLearner.state->keyPress.load();
+ pc.midiInKeyRel = c.midiLearner.state->keyRelease.load();
+ pc.midiInKill = c.midiLearner.state->kill.load();
+ pc.midiInArm = c.midiLearner.state->arm.load();
+ pc.midiInVolume = c.midiLearner.state->volume.load();
+ pc.midiInMute = c.midiLearner.state->mute.load();
+ pc.midiInSolo = c.midiLearner.state->solo.load();
+ pc.midiInReadActions = c.midiLearner.state->readActions.load();
+ pc.midiInPitch = c.midiLearner.state->pitch.load();
+ pc.midiOutL = c.midiLighter.state->enabled.load();
+ pc.midiOutLplaying = c.midiLighter.state->playing.load();
+ pc.midiOutLmute = c.midiLighter.state->mute.load();
+ pc.midiOutLsolo = c.midiLighter.state->solo.load();
+
+ if (c.getType() == ChannelType::SAMPLE) {
+ pc.waveId = c.samplePlayer->getWaveId();
+ pc.mode = c.samplePlayer->state->mode.load();
+ pc.begin = c.samplePlayer->state->begin.load();
+ pc.end = c.samplePlayer->state->end.load();
+ pc.pitch = c.samplePlayer->state->pitch.load();
+ pc.shift = c.samplePlayer->state->shift.load();
+ pc.midiInVeloAsVol = c.samplePlayer->state->velocityAsVol.load();
+ pc.inputMonitor = c.audioReceiver->state->inputMonitor.load();
- if (c.type == ChannelType::SAMPLE) {
- const SampleChannel& sc = static_cast<const SampleChannel&>(c);
- pc.waveId = sc.waveId;
- pc.mode = sc.mode;
- pc.begin = sc.begin;
- pc.end = sc.end;
- pc.readActions = sc.readActions;
- pc.pitch = sc.pitch;
- pc.inputMonitor = sc.inputMonitor;
- pc.midiInVeloAsVol = sc.midiInVeloAsVol;
- pc.midiInReadActions = sc.midiInReadActions;
- pc.midiInPitch = sc.midiInPitch;
}
else
- if (c.type == ChannelType::MIDI) {
- const MidiChannel& mc = static_cast<const MidiChannel&>(c);
- pc.midiOut = mc.midiOut;
- pc.midiOutChan = mc.midiOutChan;
+ if (c.getType() == ChannelType::MIDI) {
+ pc.midiOut = c.midiSender->state->enabled.load();
+ pc.midiOutChan = c.midiSender->state->filter.load();
}
return pc;
{
struct Channel;
}
-class Channel;
-class SampleChannel;
-class MidiChannel;
+class Channel;
+struct ChannelState;
+class Channel;
+class SampleChannel;
+class MidiChannel;
namespace channelManager
{
/* init
/* create (1)
Creates a new Channel from scratch. */
-std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
+std::unique_ptr<Channel> create(ChannelType type, int bufferSize,
bool inputMonitorOn, ID columnId);
/* create (2)
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include "masterChannel.h"
-
-
-namespace giada {
-namespace m
-{
-MasterChannel::MasterChannel(int bufferSize, ID id)
-: Channel(ChannelType::MASTER, ChannelStatus::OFF, bufferSize, 0, id)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MasterChannel::MasterChannel(const patch::Channel& p, int bufferSize)
-: Channel(p, bufferSize)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MasterChannel* MasterChannel::clone() const
-{
- return new MasterChannel(*this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MasterChannel::load(const patch::Channel& p)
-{
- volume = p.volume;
-#ifdef WITH_VST
- pluginIds = p.pluginIds;
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MasterChannel::render(AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running)
-{
-#ifdef WITH_VST
- if (pluginIds.size() == 0)
- return;
- if (id == mixer::MASTER_OUT_CHANNEL_ID)
- pluginHost::processStack(out, pluginIds);
- else
- if (id == mixer::MASTER_IN_CHANNEL_ID)
- pluginHost::processStack(inToOut, pluginIds);
-#endif
-}
-
-}} // giada::m::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_MASTER_CHANNEL_H
-#define G_MASTER_CHANNEL_H
-
-
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m
-{
-class MasterChannel : public Channel
-{
-public:
-
- MasterChannel(int bufferSize, ID id);
- MasterChannel(const patch::Channel& p, int bufferSize);
-
- MasterChannel* clone() const override;
- void load(const patch::Channel& p) override;
- void parseEvents(mixer::FrameEvents fe) override {};
- void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut,
- bool audible, bool running) override;
- void start(int frame, bool doQuantize, int velocity) override {};
- void kill(int localFrame) override {};
- void empty() override {};
- void stopBySeq(bool chansStopOnSeqHalt) override {};
- void stop() override {};
- void rewindBySeq() override {};
- void setMute(bool value) override {};
- void setSolo(bool value) override {};
- void receiveMidi(const MidiEvent& midiEvent) override {};
-};
-
-}} // giada::m::
-
-
-#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/action.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/mixer.h"
+#include "core/recorderHandler.h"
+#include "core/recManager.h"
+#include "core/channels/state.h"
+#include "midiActionRecorder.h"
+
+
+namespace giada {
+namespace m
+{
+MidiActionRecorder::MidiActionRecorder(ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiActionRecorder::MidiActionRecorder(const MidiActionRecorder& o, ChannelState* c)
+: MidiActionRecorder(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiActionRecorder::parse(const mixer::Event& e) const
+{
+ assert(m_channelState != nullptr);
+
+ if (e.type == mixer::EventType::MIDI && canRecord())
+ record(e.action.event);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiActionRecorder::record(const MidiEvent& e) const
+{
+ MidiEvent flat(e);
+ flat.setChannel(0);
+ recorderHandler::liveRec(m_channelState->id, flat, clock::quantize(clock::getCurrentFrame()));
+ m_channelState->hasActions = true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool MidiActionRecorder::canRecord() const
+{
+ return recManager::isRecordingAction() &&
+ clock::isRunning() &&
+ !recManager::isRecordingInput();
+}
+}} // giada::m::
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_ACTION_RECORDER_H
+#define G_CHANNEL_MIDI_ACTION_RECORDER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+class MidiActionRecorder
+{
+public:
+
+ MidiActionRecorder(ChannelState*);
+ MidiActionRecorder(const MidiActionRecorder&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e) const;
+
+private:
+
+ bool canRecord() const;
+ void record(const MidiEvent& e) const;
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/log.h"
-#include "core/channels/midiChannelProc.h"
-#include "core/channels/channelManager.h"
-#include "core/channels/channel.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/recManager.h"
-#include "core/action.h"
-#include "core/patch.h"
-#include "core/const.h"
-#include "core/conf.h"
-#include "core/mixer.h"
-#include "core/pluginHost.h"
-#include "core/kernelMidi.h"
-#include "midiChannel.h"
-
-
-namespace giada {
-namespace m
-{
-MidiChannel::MidiChannel(int bufferSize, ID columnId, ID id)
-: Channel (ChannelType::MIDI, ChannelStatus::OFF, bufferSize, columnId, id),
- midiOut (false),
- midiOutChan(G_MIDI_CHANS[0])
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel::MidiChannel(const MidiChannel& o)
-: Channel (o),
- midiOut (o.midiOut),
- midiOutChan(o.midiOutChan)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel::MidiChannel(const patch::Channel& p, int bufferSize)
-: Channel (p, bufferSize),
- midiOut (p.midiOut),
- midiOutChan(p.midiOutChan)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-MidiChannel* MidiChannel::clone() const
-{
- return new MidiChannel(*this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::parseEvents(mixer::FrameEvents fe)
-{
- midiChannelProc::parseEvents(this, fe);
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::render(AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running)
-{
- midiChannelProc::process(this, out, in, audible);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::stopBySeq(bool chansStopOnSeqHalt)
-{
- midiChannelProc::stopBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::start(int frame, bool doQuantize, int velocity)
-{
- midiChannelProc::start(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::kill(int localFrame)
-{
- midiChannelProc::kill(this, localFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::rewindBySeq()
-{
- midiChannelProc::rewindBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::setMute(bool value)
-{
- midiChannelProc::setMute(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::setSolo(bool value)
-{
- midiChannelProc::setSolo(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::empty()
-{
- hasActions = false;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::sendMidi(const MidiEvent& e, int localFrame)
-{
- if (midiOut) {
- MidiEvent e_ = e;
- e_.setChannel(midiOutChan);
- kernelMidi::send(e_.getRaw());
- }
-
-#ifdef WITH_VST
-
- /* Enqueue this MIDI event for plug-ins processing. Will be read and
- rendered later on by the audio thread. */
-
- MidiEvent e_ = e;
- e_.setDelta(localFrame);
- midiQueue.push(e_);
-
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void MidiChannel::receiveMidi(const MidiEvent& midiEvent)
-{
- namespace mrh = m::recorderHandler;
- namespace mr = m::recorder;
-
- if (!armed)
- return;
-
- /* Now all messages are turned into Channel-0 messages. Giada doesn't care
- about holding MIDI channel information. Moreover, having all internal
- messages on channel 0 is way easier. */
-
- MidiEvent midiEventFlat(midiEvent);
- midiEventFlat.setChannel(0);
-
-#ifdef WITH_VST
-
- /* Enqueue this MIDI event for plug-ins processing. Will be read and
- rendered later on by the audio thread. */
-
- midiQueue.push(midiEventFlat);
-
-#endif
-
- if (recManager::isRecordingAction()) {
- mrh::liveRec(id, midiEventFlat);
- hasActions = true;
- }
-}
-
-}} // giada::m::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_MIDI_CHANNEL_H
-#define G_MIDI_CHANNEL_H
-
-
-#ifdef WITH_VST
-#include "deps/juce-config.h"
-#endif
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m
-{
-class MidiChannel : public Channel
-{
-public:
-
- MidiChannel(int bufferSize, ID columnId, ID id);
- MidiChannel(const MidiChannel& o);
- MidiChannel(const patch::Channel& p, int bufferSize);
-
- MidiChannel* clone() const override;
- void parseEvents(mixer::FrameEvents fe) override;
- void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut,
- bool audible, bool running) override;
- void start(int frame, bool doQuantize, int velocity) override;
- void kill(int localFrame) override;
- void empty() override;
- void stopBySeq(bool chansStopOnSeqHalt) override;
- void stop() override {};
- void rewindBySeq() override;
- void setMute(bool value) override;
- void setSolo(bool value) override;
- void receiveMidi(const MidiEvent& midiEvent) override;
-
- /* sendMidi
- Sends Midi event to the outside world. */
-
- void sendMidi(const MidiEvent& e, int localFrame);
-
- bool midiOut; // enable midi output
- int midiOutChan; // midi output channel
-};
-
-}} // giada::m::
-
-
-#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "core/pluginHost.h"
-#include "core/kernelMidi.h"
-#include "core/const.h"
-#include "core/action.h"
-#include "core/mixerHandler.h"
-#include "midiChannelProc.h"
-
-
-namespace giada {
-namespace m {
-namespace midiChannelProc
-{
-namespace
-{
-void onFirstBeat_(MidiChannel* ch)
-{
- if (ch->playStatus == ChannelStatus::ENDING) {
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- }
- else
- if (ch->playStatus == ChannelStatus::WAIT) {
- ch->playStatus = ChannelStatus::PLAY;
- ch->sendMidiLstatus();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void sendAllNotesOff_(MidiChannel* ch)
-{
- MidiEvent e(MIDI_ALL_NOTES_OFF);
- ch->sendMidi(e, /*localFrame=*/0);
-
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(MidiChannel* ch, mixer::FrameEvents fe)
-{
- if (fe.onFirstBeat)
- onFirstBeat_(ch);
- if (fe.actions != nullptr)
- for (const Action& action : *fe.actions)
- if (action.channelId == ch->id && ch->isPlaying() && !ch->mute)
- ch->sendMidi(action.event, fe.frameLocal);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible)
-{
-#ifdef WITH_VST
-
- ch->midiBuffer.clear();
-
- /* Fill the MIDI buffer vector with messages coming from the MIDI queue
- filled by the MIDI thread. This is for live events, e.g. piano keyboards,
- controllers, ... */
-
- MidiEvent e;
- while (ch->midiQueue.pop(e)) {
- juce::MidiMessage message = juce::MidiMessage(
- e.getStatus(),
- e.getNote(),
- e.getVelocity());
- ch->midiBuffer.addEvent(message, e.getDelta());
- }
- pluginHost::processStack(ch->buffer, ch->pluginIds, &ch->midiBuffer);
-
- /* Process the plugin stack first, then quit if the channel is muted/soloed.
- This way there's no risk of cutting midi event pairs such as note-on and
- note-off while triggering a mute/solo. */
-
- if (!audible)
- return;
-
- for (int i=0; i<out.countFrames(); i++)
- for (int j=0; j<out.countChannels(); j++)
- out[i][j] += ch->buffer[i][j] * ch->volume;
-
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(MidiChannel* ch)
-{
- switch (ch->playStatus) {
- case ChannelStatus::PLAY:
- ch->playStatus = ChannelStatus::ENDING;
- ch->sendMidiLstatus();
- break;
-
- case ChannelStatus::ENDING:
- case ChannelStatus::WAIT:
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- break;
-
- case ChannelStatus::OFF:
- ch->playStatus = ChannelStatus::WAIT;
- ch->sendMidiLstatus();
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void kill(MidiChannel* ch, int localFrame)
-{
- if (ch->isPlaying())
- sendAllNotesOff_(ch);
-
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindBySeq(MidiChannel* ch)
-{
- sendAllNotesOff_(ch);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(MidiChannel* ch, bool v)
-{
- ch->mute = v;
- if (v)
- sendAllNotesOff_(ch);
-
- // This is for processing playing_inaudible
- ch->sendMidiLstatus();
-
- ch->sendMidiLmute();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(MidiChannel* ch, bool v)
-{
- ch->solo = v;
- mh::updateSoloCount();
-
- // This is for processing playing_inaudible
- // TODO
- //for (std::unique_ptr<Channel>& c : model::getLayout()->channels)
- // c->sendMidiLstatus();
-
- ch->sendMidiLsolo();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopBySeq(MidiChannel* ch)
-{
- sendAllNotesOff_(ch);
- kill(ch, 0);
-}
-}}};
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_MIDI_CHANNEL_PROC_H
-#define G_MIDI_CHANNEL_PROC_H
-
-
-#include "core/mixer.h"
-#include "core/audioBuffer.h"
-
-
-namespace giada {
-namespace m
-{
-class MidiChannel;
-namespace midiChannelProc
-{
-/* parseEvents
-Parses events gathered by Mixer::masterPlay(). */
-
-void parseEvents(MidiChannel* ch, mixer::FrameEvents ev);
-
-/**/
-void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible);
-
-/* kill
-Stops a channel abruptly. */
-
-void kill(MidiChannel* ch, int localFrame);
-
-/* start
-Starts a channel. */
-
-void start(MidiChannel* ch);
-
-/* stopBySeq
-Stops a channel when the stop button on main transport is pressed. */
-
-void stopBySeq(MidiChannel* ch);
-
-/* rewind
-Rewinds channel when rewind button on main transport is pressed. */
-
-void rewindBySeq(MidiChannel* ch);
-
-/* mute|unmute
-Mutes/unmutes a channel. */
-
-void setMute(MidiChannel* ch, bool v);
-void setSolo(MidiChannel* ch, bool v);
-}}};
-
-
-#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/conf.h"
+#include "core/channels/state.h"
+#include "midiController.h"
+
+
+namespace giada {
+namespace m
+{
+MidiController::MidiController(ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiController::MidiController(const MidiController& o, ChannelState* c)
+: m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::parse(const mixer::Event& e) const
+{
+ assert(m_channelState != nullptr);
+
+ switch (e.type) {
+
+ case mixer::EventType::KEY_PRESS:
+ press(); break;
+
+ case mixer::EventType::KEY_KILL:
+ case mixer::EventType::SEQUENCER_STOP:
+ kill(); break;
+
+ case mixer::EventType::SEQUENCER_FIRST_BEAT:
+ case mixer::EventType::SEQUENCER_REWIND:
+ onFirstBeat();
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::press() const
+{
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+
+ switch (playStatus) {
+ case ChannelStatus::PLAY:
+ playStatus = ChannelStatus::ENDING; break;
+
+ case ChannelStatus::ENDING:
+ case ChannelStatus::WAIT:
+ playStatus = ChannelStatus::OFF; break;
+
+ case ChannelStatus::OFF:
+ playStatus = ChannelStatus::WAIT; break;
+
+ default: break;
+ }
+
+ m_channelState->playStatus.store(playStatus);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::kill() const
+{
+ m_channelState->playStatus.store(ChannelStatus::OFF);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiController::onFirstBeat() const
+{
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+
+ if (playStatus == ChannelStatus::ENDING)
+ playStatus = ChannelStatus::OFF;
+ else
+ if (playStatus == ChannelStatus::WAIT)
+ playStatus = ChannelStatus::PLAY;
+
+ m_channelState->playStatus.store(playStatus);
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_CONTROLLER_H
+#define G_CHANNEL_MIDI_CONTROLLER_H
+
+
+#include "core/types.h"
+#include "core/mixer.h" // TODO - forward declare
+
+
+namespace giada {
+namespace m
+{
+/* MidiController
+Manages events for a MIDI Channel. */
+
+class MidiController
+{
+public:
+
+ MidiController(ChannelState*);
+ MidiController(const MidiController&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e) const;
+
+private:
+
+ void press() const;
+ void kill() const;
+ void onFirstBeat() const;
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "midiLearner.h"
+
+
+namespace giada {
+namespace m
+{
+MidiLearner::MidiLearner()
+: state(std::make_unique<MidiLearnerState>())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLearner::MidiLearner(const patch::Channel& p)
+: state(std::make_unique<MidiLearnerState>(p))
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLearner::MidiLearner(const MidiLearner& o)
+: state(std::make_unique<MidiLearnerState>(*o.state))
+{
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_LEARNER_H
+#define G_CHANNEL_MIDI_LEARNER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+struct MidiLearnerState;
+class MidiLearner
+{
+public:
+
+ MidiLearner();
+ MidiLearner(const patch::Channel&);
+ MidiLearner(const MidiLearner&);
+
+ /* state
+ Pointer to mutable MidiLearnerState state. */
+
+ std::unique_ptr<MidiLearnerState> state;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/channels/state.h"
+#include "core/mixer.h"
+#include "core/kernelMidi.h"
+#include "core/midiMapConf.h"
+#include "midiLighter.h"
+
+
+namespace giada {
+namespace m
+{
+MidiLighter::MidiLighter(ChannelState* c)
+: state (std::make_unique<MidiLighterState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighter::MidiLighter(const patch::Channel& p, ChannelState* c)
+: state (std::make_unique<MidiLighterState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighter::MidiLighter(const MidiLighter& o, ChannelState* c)
+: state (std::make_unique<MidiLighterState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::parse(const mixer::Event& e, bool audible) const
+{
+ if (state->enabled.load() == false)
+ return;
+
+ uint32_t l_playing = state->playing.load();
+ uint32_t l_mute = state->mute.load();
+ uint32_t l_solo = state->solo.load();
+
+ switch (e.type) {
+
+ case mixer::EventType::KEY_PRESS:
+ case mixer::EventType::KEY_RELEASE:
+ case mixer::EventType::KEY_KILL:
+ case mixer::EventType::SEQUENCER_STOP:
+ if (l_playing != 0x0) sendStatus(l_playing, audible);
+ break;
+
+ case mixer::EventType::CHANNEL_MUTE:
+ if (l_mute != 0x0) sendMute(l_mute);
+ break;
+
+ case mixer::EventType::CHANNEL_SOLO:
+ if (l_solo != 0x0) sendSolo(l_solo);
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendMute(uint32_t l_mute) const
+{
+ if (m_channelState->mute.load() == true)
+ kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOn);
+ else
+ kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOff);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendSolo(uint32_t l_solo) const
+{
+ if (m_channelState->solo.load() == true)
+ kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOn);
+ else
+ kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOff);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiLighter::sendStatus(uint32_t l_playing, bool audible) const
+{
+ switch (m_channelState->playStatus.load()) {
+
+ case ChannelStatus::OFF:
+ kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopped);
+ break;
+
+ case ChannelStatus::WAIT:
+ kernelMidi::sendMidiLightning(l_playing, midimap::midimap.waiting);
+ break;
+
+ case ChannelStatus::ENDING:
+ kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopping);
+ break;
+
+ case ChannelStatus::PLAY:
+ kernelMidi::sendMidiLightning(l_playing, audible ? midimap::midimap.playing : midimap::midimap.playingInaudible);
+ break;
+
+ default: break;
+ }
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_LIGHTER_H
+#define G_CHANNEL_MIDI_LIGHTER_H
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct MidiLighterState;
+
+/* MidiLighter
+Learns and emits MIDI lightning messages to physical hardware on events. */
+
+class MidiLighter
+{
+public:
+
+ MidiLighter(ChannelState*);
+ MidiLighter(const patch::Channel&, ChannelState*);
+ MidiLighter(const MidiLighter&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e, bool audible) const;
+
+ /* state
+ Pointer to mutable MidiLighterState state. */
+
+ std::unique_ptr<MidiLighterState> state;
+
+private:
+
+ void sendMute(uint32_t l_mute) const;
+ void sendSolo(uint32_t l_solo) const;
+ void sendStatus(uint32_t l_playing, bool audible) const;
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+
+#include "core/mixer.h"
+#include "core/pluginHost.h"
+#include "core/channels/state.h"
+#include "midiReceiver.h"
+
+
+namespace giada {
+namespace m
+{
+MidiReceiver::MidiReceiver(ChannelState* c)
+: state (std::make_unique<MidiReceiverState>())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiReceiver::MidiReceiver(const patch::Channel& p, ChannelState* c)
+: state (std::make_unique<MidiReceiverState>())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiReceiver::MidiReceiver(const MidiReceiver& o, ChannelState* c)
+: state (std::make_unique<MidiReceiverState>())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::parse(const mixer::Event& e) const
+{
+ switch (e.type) {
+
+ case mixer::EventType::MIDI:
+ parseMidi(e.action.event); break;
+
+ case mixer::EventType::ACTION:
+ if (m_channelState->isPlaying())
+ sendToPlugins(e.action.event, e.delta);
+ break;
+
+ case mixer::EventType::KEY_KILL:
+ case mixer::EventType::SEQUENCER_STOP:
+ case mixer::EventType::SEQUENCER_REWIND:
+ sendToPlugins(MidiEvent(G_MIDI_ALL_NOTES_OFF), 0); break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::render(const std::vector<ID>& pluginIds) const
+{
+ pluginHost::processStack(m_channelState->buffer, pluginIds, &state->midiBuffer);
+ state->midiBuffer.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::parseMidi(const MidiEvent& e) const
+{
+ /* Now all messages are turned into Channel-0 messages. Giada doesn't care
+ about holding MIDI channel information. Moreover, having all internal
+ messages on channel 0 is way easier. Then send it to plug-ins. */
+
+ MidiEvent flat(e);
+ flat.setChannel(0);
+ sendToPlugins(flat, /*delta=*/0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiReceiver::sendToPlugins(const MidiEvent& e, Frame localFrame) const
+{
+ juce::MidiMessage message = juce::MidiMessage(
+ e.getStatus(),
+ e.getNote(),
+ e.getVelocity());
+ state->midiBuffer.addEvent(message, localFrame);
+}
+}} // giada::m::
+
+
+#endif // WITH_VST
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_RECEIVER_H
+#define G_CHANNEL_MIDI_RECEIVER_H
+
+
+#ifdef WITH_VST
+
+
+#include <memory>
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct MidiReceiverState;
+
+/* MidiReceiver
+Takes live action gestures AND recorded actions and redirect them as MIDI events
+to plug-in soft synths. */
+
+class MidiReceiver
+{
+public:
+
+ MidiReceiver(ChannelState*);
+ MidiReceiver(const patch::Channel&, ChannelState*);
+ MidiReceiver(const MidiReceiver&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e) const;
+ void render(const std::vector<ID>& pluginIds) const;
+
+ /* state
+ Pointer to mutable MidiReceiverState state. */
+
+ std::unique_ptr<MidiReceiverState> state;
+
+private:
+
+ /* parseMidi
+ Takes a live message (e.g. from a MIDI keyboard), strips it and sends it
+ to plug-ins. */
+
+ void parseMidi(const MidiEvent& e) const;
+
+ /* sendToPlugins
+ Enqueues the MIDI event for plug-ins processing. This will be read later on
+ by the PluginHost. */
+
+ void sendToPlugins(const MidiEvent& e, Frame localFrame) const;
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif // WITH_VST
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/mixer.h"
+#include "core/kernelMidi.h"
+#include "core/channels/state.h"
+#include "midiSender.h"
+
+
+namespace giada {
+namespace m
+{
+MidiSender::MidiSender(ChannelState* c)
+: state(std::make_unique<MidiSenderState>())
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiSender::MidiSender(const patch::Channel& p, ChannelState* c)
+: state(std::make_unique<MidiSenderState>(p))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiSender::MidiSender(const MidiSender& o, ChannelState* c)
+: state(std::make_unique<MidiSenderState>(*o.state))
+, m_channelState(c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiSender::parse(const mixer::Event& e) const
+{
+ bool isPlaying = m_channelState->isPlaying();
+ bool isEnabled = state->enabled.load();
+
+ if (!isPlaying || !isEnabled)
+ return;
+
+ if (e.type == mixer::EventType::KEY_KILL ||
+ e.type == mixer::EventType::SEQUENCER_STOP)
+ send(MidiEvent(G_MIDI_ALL_NOTES_OFF));
+ else
+ if (e.type == mixer::EventType::ACTION)
+ send(e.action.event);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void MidiSender::send(MidiEvent e) const
+{
+ e.setChannel(state->filter.load());
+ kernelMidi::send(e.getRaw());
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_MIDI_SENDER_H
+#define G_CHANNEL_MIDI_SENDER_H
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+struct MidiSenderState;
+class MidiSender
+{
+public:
+
+ MidiSender(ChannelState*);
+ MidiSender(const patch::Channel&, ChannelState*);
+ MidiSender(const MidiSender&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e) const;
+
+ /* state
+ Pointer to mutable MidiSenderState state. */
+
+ std::unique_ptr<MidiSenderState> state;
+
+private:
+
+ void send(MidiEvent e) const;
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/action.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/mixer.h"
+#include "core/recorderHandler.h"
+#include "core/recManager.h"
+#include "core/channels/state.h"
+#include "sampleActionRecorder.h"
+
+
+namespace giada {
+namespace m
+{
+SampleActionRecorder::SampleActionRecorder(ChannelState* c, SamplePlayerState* sc)
+: m_channelState(c)
+, m_samplePlayerState(sc)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SampleActionRecorder::SampleActionRecorder(const SampleActionRecorder& o,
+ ChannelState* c, SamplePlayerState* sc)
+: SampleActionRecorder(c, sc)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::parse(const mixer::Event& e) const
+{
+ assert(m_channelState != nullptr);
+
+ switch (e.type) {
+
+ case mixer::EventType::KEY_PRESS:
+ onKeyPress(); break;
+
+ /* Record a stop event only if channel is SINGLE_PRESS. For any other
+ mode the key release event is meaningless. */
+
+ case mixer::EventType::KEY_RELEASE:
+ if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS)
+ record(MidiEvent::NOTE_OFF);
+ break;
+
+ case mixer::EventType::KEY_KILL:
+ if (canRecord())
+ record(MidiEvent::NOTE_KILL);
+ break;
+
+ case mixer::EventType::SEQUENCER_FIRST_BEAT:
+ onFirstBeat(); break;
+
+ case mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+ toggleReadActions(); break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::record(int note) const
+{
+ recorderHandler::liveRec(m_channelState->id, MidiEvent(note, 0, 0),
+ clock::quantize(clock::getCurrentFrame()));
+
+ m_channelState->hasActions = true;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::startReadActions() const
+{
+ if (conf::conf.treatRecsAsLoops)
+ m_channelState->recStatus.store(ChannelStatus::WAIT);
+ else {
+ m_channelState->recStatus.store(ChannelStatus::PLAY);
+ m_channelState->readActions.store(true);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::stopReadActions(ChannelStatus curRecStatus) const
+{
+ /* First of all, if the clock is not running or treatRecsAsLoops is off,
+ just stop and disable everything. Otherwise make sure a channel with actions
+ behave like a dynamic one. */
+
+ if (!clock::isRunning() || !conf::conf.treatRecsAsLoops) {
+ m_channelState->recStatus.store(ChannelStatus::OFF);
+ m_channelState->readActions.store(false);
+ }
+ else
+ if (curRecStatus == ChannelStatus::WAIT)
+ m_channelState->recStatus.store(ChannelStatus::OFF);
+ else
+ if (curRecStatus == ChannelStatus::ENDING)
+ m_channelState->recStatus.store(ChannelStatus::PLAY);
+ else
+ m_channelState->recStatus.store(ChannelStatus::ENDING);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::toggleReadActions() const
+{
+ /* When you start reading actions while conf::treatRecsAsLoops is true, the
+ value ch.state->readActions actually is not set to true immediately, because
+ the channel is in wait mode (REC_WAITING). readActions will become true on
+ the next first beat. So a 'stop rec' command should occur also when
+ readActions is false but the channel is in wait mode; this check will
+ handle the case of when you press 'R', the channel goes into REC_WAITING and
+ then you press 'R' again to undo the status. */
+
+ if (!m_channelState->hasActions)
+ return;
+
+ bool readActions = m_channelState->readActions.load();
+ ChannelStatus recStatus = m_channelState->recStatus.load();
+
+ if (readActions || (!readActions && recStatus == ChannelStatus::WAIT))
+ stopReadActions(recStatus);
+ else
+ startReadActions();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onKeyPress() const
+{
+ if (!canRecord())
+ return;
+ record(MidiEvent::NOTE_ON);
+
+ /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to
+ prevent existing actions to interfere with the keypress/keyrel combo. */
+
+ if (m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS)
+ m_channelState->readActions = false;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onKeyRelease() const
+{
+ if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) {
+ record(MidiEvent::NOTE_OFF);
+ m_channelState->readActions = true;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleActionRecorder::onFirstBeat() const
+{
+ ChannelStatus recStatus = m_channelState->recStatus.load();
+
+ switch (recStatus) {
+
+ case ChannelStatus::ENDING:
+ m_channelState->recStatus.store(ChannelStatus::OFF);
+ m_channelState->readActions = false;
+ break;
+
+ case ChannelStatus::WAIT:
+ m_channelState->recStatus.store(ChannelStatus::PLAY);
+ m_channelState->readActions = true;
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SampleActionRecorder::canRecord() const
+{
+ return recManager::isRecordingAction() &&
+ clock::isRunning() &&
+ !recManager::isRecordingInput() &&
+ !m_samplePlayerState->isAnyLoopMode();
+}
+}} // giada::m::
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_SAMPLE_ACTION_RECORDER_H
+#define G_CHANNEL_SAMPLE_ACTION_RECORDER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct ChannelState;
+
+/* SampleActionRecorder
+Records actions for channels and optionally manages the 'read action' state ('R'
+button on Sample Channels). */
+
+class SampleActionRecorder
+{
+public:
+
+ SampleActionRecorder(ChannelState*, SamplePlayerState*);
+ SampleActionRecorder(const SampleActionRecorder&, ChannelState* c=nullptr,
+ SamplePlayerState* sc=nullptr);
+
+ void parse(const mixer::Event& e) const;
+
+private:
+ void record(int note) const;
+ void onKeyPress() const;
+ void onKeyRelease() const;
+ void onFirstBeat() const;
+
+ void toggleReadActions() const;
+ void startReadActions() const;
+ void stopReadActions(ChannelStatus curRecStatus) const;
+
+ bool canRecord() const;
+
+ ChannelState* m_channelState;
+ SamplePlayerState* m_samplePlayerState;
+};
+}} // giada::m::
+
+
+#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/log.h"
-#include "core/const.h"
-#include "core/wave.h"
-#include "core/model/model.h"
-#include "sampleChannelProc.h"
-#include "sampleChannelRec.h"
-#include "channelManager.h"
-#include "sampleChannel.h"
-
-
-namespace giada {
-namespace m
-{
-SampleChannel::SampleChannel(bool inputMonitor, int bufferSize,
- ID columnId, ID id)
-: Channel (ChannelType::SAMPLE, ChannelStatus::EMPTY, bufferSize,
- columnId, id),
- hasWave (false),
- waveId (0),
- shift (0),
- mode (ChannelMode::SINGLE_BASIC),
- quantizing (false),
- inputMonitor (inputMonitor),
- pitch (G_DEFAULT_PITCH),
- tracker (0),
- trackerPreview (0),
- begin (0),
- end (0),
- midiInVeloAsVol (false),
- midiInReadActions(0x0),
- midiInPitch (0x0),
- bufferOffset (0),
- rewinding (false),
- rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
- if (rsmp_state == nullptr) {
- u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
- throw std::bad_alloc();
- }
- bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::SampleChannel(const SampleChannel& o)
-: Channel (o),
- hasWave (o.hasWave),
- waveId (o.waveId),
- shift (o.shift),
- mode (o.mode),
- quantizing (o.quantizing),
- inputMonitor (o.inputMonitor),
- pitch (o.pitch),
- tracker (o.tracker),
- trackerPreview (0),
- begin (o.begin),
- end (o.end),
- midiInVeloAsVol (o.midiInVeloAsVol),
- midiInReadActions(o.midiInReadActions),
- midiInPitch (o.midiInPitch),
- bufferOffset (o.bufferOffset),
- rewinding (o.rewinding),
- rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
- if (rsmp_state == nullptr) {
- u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
- throw std::bad_alloc();
- }
-
- bufferPreview.alloc(o.bufferPreview.countFrames(), G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::SampleChannel(const patch::Channel& p, int bufferSize)
-: Channel (p, bufferSize),
- hasWave (p.waveId != 0),
- waveId (p.waveId),
- shift (0), // TODO
- mode (p.mode),
- quantizing (false),
- inputMonitor (p.inputMonitor),
- pitch (p.pitch),
- tracker (0),
- trackerPreview (0),
- begin (p.begin),
- end (p.end),
- midiInVeloAsVol (p.midiInVeloAsVol),
- midiInReadActions(p.midiInReadActions),
- midiInPitch (p.midiInPitch),
- bufferOffset (0),
- rewinding (0),
- rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr))
-{
- if (rsmp_state == nullptr) {
- u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n");
- throw std::bad_alloc();
- }
-
- bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel* SampleChannel::clone() const
-{
- return new SampleChannel(*this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-SampleChannel::~SampleChannel()
-{
- if (rsmp_state != nullptr)
- src_delete(rsmp_state);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::parseEvents(mixer::FrameEvents fe)
-{
- sampleChannelProc::parseEvents(this, fe);
- sampleChannelRec::parseEvents(this, fe);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::render(AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running)
-{
- sampleChannelProc::render(this, out, in, inToOut, audible, running);
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::rewindBySeq()
-{
- sampleChannelProc::rewindBySeq(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::start(int localFrame, bool doQuantize, int velocity)
-{
- sampleChannelProc::start(this, localFrame, doQuantize, velocity);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stop()
-{
- sampleChannelProc::stop(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopBySeq(bool chansStopOnSeqHalt)
-{
- sampleChannelProc::stopBySeq(this, chansStopOnSeqHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::kill(int localFrame)
-{
- sampleChannelProc::kill(this, localFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::recordStart(bool canQuantize)
-{
- return sampleChannelRec::recordStart(this, canQuantize);
-}
-
-
-bool SampleChannel::recordKill()
-{
- return sampleChannelRec::recordKill(this);
-}
-
-
-void SampleChannel::recordStop()
-{
- sampleChannelRec::recordStop(this);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt)
-{
- sampleChannelRec::startReadingActions(this, treatRecsAsLoops, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopReadingActions(bool running, bool treatRecsAsLoops,
- bool recsStopOnChanHalt)
-{
- sampleChannelRec::stopReadingActions(this, running, treatRecsAsLoops,
- recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::stopInputRec(int globalFrame)
-{
- sampleChannelProc::stopInputRec(this, globalFrame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setMute(bool value)
-{
- sampleChannelProc::setMute(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setSolo(bool value)
-{
- sampleChannelProc::setSolo(this, value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt)
-{
- sampleChannelRec::setReadActions(this, v, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::hasLogicalData() const
-{
- if (!hasWave)
- return false;
-
- model::WavesLock wl(model::waves);
- return model::get(model::waves, waveId).isLogical();
-};
-
-
-bool SampleChannel::hasEditedData() const
-{
- if (!hasWave)
- return false;
-
- model::WavesLock wl(model::waves);
- return model::get(model::waves, waveId).isEdited();
-};
-
-
-bool SampleChannel::hasData() const
-{
- return hasWave;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setBegin(int f)
-{
- model::WavesLock lock(model::waves);
- const Wave& wave = model::get(model::waves, waveId);
-
- if (f < 0)
- f = 0;
- else
- if (f > wave.getSize())
- f = wave.getSize();
- else
- if (f >= end)
- f = end - 1;
-
- begin = f;
- tracker = f;
- trackerPreview = f;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setEnd(int f)
-{
- model::WavesLock lock(model::waves);
- const Wave& wave = model::get(model::waves, waveId);
-
- if (f >= wave.getSize())
- f = wave.getSize() - 1;
- else
- if (f <= begin)
- f = begin + 1;
-
- end = f;}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::getBegin() const { return begin; }
-int SampleChannel::getEnd() const { return end; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::setPitch(float v)
-{
- if (v > G_MAX_PITCH)
- pitch = G_MAX_PITCH;
- else
- if (v < G_MIN_PITCH)
- pitch = G_MIN_PITCH;
- else
- pitch = v;
-
-// ???? /* if status is off don't slide between frequencies */
-// ????
-// ???? if (status & (STATUS_OFF | STATUS_WAIT))
-// ???? src_set_ratio(rsmp_state, 1/pitch);
-}
-
-
-float SampleChannel::getPitch() const { return pitch; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::getPosition() const
-{
- if (playStatus != ChannelStatus::EMPTY &&
- playStatus != ChannelStatus::MISSING &&
- playStatus != ChannelStatus::OFF)
- return tracker - begin;
- else
- return -1;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::empty()
-{
- playStatus = ChannelStatus::EMPTY;
- begin = 0;
- end = 0;
- tracker = 0;
- volume = G_DEFAULT_VOL;
- hasActions = false;
- hasWave = false;
- waveId = 0;
- sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void SampleChannel::pushWave(ID wid, Frame size)
-{
- playStatus = ChannelStatus::OFF;
- waveId = wid;
- begin = 0;
- end = size;
- tracker = 0;
- hasWave = true;
- sendMidiLstatus();
-}
-
-
-void SampleChannel::popWave()
-{
- playStatus = ChannelStatus::OFF;
- waveId = 0;
- begin = 0;
- end = 0;
- tracker = 0;
- hasWave = false;
- sendMidiLstatus();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-std::string SampleChannel::getSamplePath() const
-{
- if (!hasWave)
- return "";
-
- model::WavesLock wl(model::waves);
- return model::get(model::waves, waveId).getPath();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::canInputRec() const
-{
- return !hasWave && armed == true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBuffer(AudioBuffer& dest, int start, int offset)
-{
- assert(offset < dest.countFrames());
-
- if (pitch == 1.0) return fillBufferCopy(dest, start, offset);
- else return fillBufferResampled(dest, start, offset);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBufferResampled(AudioBuffer& dest, int start, int offset)
-{
- model::WavesLock lock(model::waves);
- const Wave& wave = model::get(model::waves, waveId);
-
- rsmp_data.data_in = wave.getFrame(start); // Source data
- rsmp_data.input_frames = end - start; // How many readable frames
- rsmp_data.data_out = dest[offset]; // Destination (processed data)
- rsmp_data.output_frames = dest.countFrames() - offset; // How many frames to process
- rsmp_data.end_of_input = false;
- rsmp_data.src_ratio = 1 / pitch;
-
- src_process(rsmp_state, &rsmp_data);
-
- return rsmp_data.input_frames_used; // Returns used frames
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-int SampleChannel::fillBufferCopy(AudioBuffer& dest, int start, int offset)
-{
- model::WavesLock lock(model::waves);
- const Wave& wave = model::get(model::waves, waveId);
-
- int used = dest.countFrames() - offset;
- if (used > wave.getSize() - start)
- used = wave.getSize() - start;
-
- dest.copyData(wave.getFrame(start), used, offset);
-
- return used;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::isAnyLoopMode() const
-{
- return mode == ChannelMode::LOOP_BASIC ||
- mode == ChannelMode::LOOP_ONCE ||
- mode == ChannelMode::LOOP_REPEAT ||
- mode == ChannelMode::LOOP_ONCE_BAR;
-}
-
-
-bool SampleChannel::isAnySingleMode() const
-{
- return !isAnyLoopMode();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool SampleChannel::isOnLastFrame() const
-{
- return tracker >= end;
-}
-
-}} // giada::m::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_SAMPLE_CHANNEL_H
-#define G_SAMPLE_CHANNEL_H
-
-
-#include <memory>
-#include <functional>
-#include <samplerate.h>
-#include "core/types.h"
-#include "core/channels/channel.h"
-
-
-namespace giada {
-namespace m
-{
-class Wave;
-
-class SampleChannel : public Channel
-{
-public:
-
- SampleChannel(bool inputMonitor, int bufferSize, ID columnId, ID id);
- SampleChannel(const SampleChannel& o);
- SampleChannel(const patch::Channel& p, int bufferSize);
- ~SampleChannel();
-
- SampleChannel* clone() const override;
- void parseEvents(mixer::FrameEvents fe) override;
- void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut,
- bool audible, bool running) override;
-
- void start(int frame, bool doQuantize, int velocity) override;
- void stop() override;
- void kill(int frame) override;
- bool recordStart(bool canQuantize) override;
- bool recordKill() override;
- void recordStop() override;
- void setMute(bool value) override;
- void setSolo(bool value) override;
- void startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) override;
- void stopReadingActions(bool running, bool treatRecsAsLoops,
- bool recsStopOnChanHalt) override;
- void empty() override;
- void stopBySeq(bool chansStopOnSeqHalt) override;
- void rewindBySeq() override;
- void stopInputRec(int globalFrame) override;
- bool canInputRec() const override;
- bool hasLogicalData() const override;
- bool hasEditedData() const override;
- bool hasData() const override;
-
- int getBegin() const;
- int getEnd() const;
- float getPitch() const;
- bool isAnyLoopMode() const;
- bool isAnySingleMode() const;
- bool isOnLastFrame() const;
- std::string getSamplePath() const;
-
- /* getPosition
- Returns the position of an active sample. If EMPTY o MISSING returns -1. */
-
- int getPosition() const;
-
- /* fillBuffer
- Fills 'dest' buffer at point 'offset' with Wave data taken from 'start'.
- Returns how many frames have been used from the original Wave data. It also
- resamples data if pitch != 1.0f. */
-
- int fillBuffer(AudioBuffer& dest, int start, int offset);
-
- /* pushWave
- Adds a new wave to this channel. */
-
- void pushWave(ID waveId, Frame waveSize);
- void popWave();
-
- void setPitch(float v);
- void setBegin(int f);
- void setEnd(int f);
-
- void setReadActions(bool v, bool recsStopOnChanHalt);
-
- /* bufferPreview
- Extra buffer for audio preview. */
-
- AudioBuffer bufferPreview;
-
- /* hasWave
- Tells if a wave is linked to this channel. */
- /* TODO - useless: check if waveId != 0 */
-
- bool hasWave;
-
- /* waveId
- ID of a Wave object. Might be useless if hasWave == false. */
-
- ID waveId;
-
- int shift;
- ChannelMode mode;
- bool quantizing; // quantization in progress
- bool inputMonitor;
- float pitch;
-
- Frame tracker; // chan position
- Frame trackerPreview; // chan position for audio preview
-
- /* begin, end
- Begin/end point to read wave data from/to. */
-
- Frame begin;
- Frame end;
-
- /* midiIn*
- MIDI input parameters. */
-
- bool midiInVeloAsVol;
- uint32_t midiInReadActions;
- uint32_t midiInPitch;
-
- /* bufferOffset
- Offset used while filling the internal buffer with audio data. Value is
- greater than zero on start sample. */
-
- Frame bufferOffset;
-
- /* rewinding
- Tells whether a rewind event is taking place. Used to fill the audio
- buffer twice. */
-
- bool rewinding;
-
-private:
-
- /* rsmp_state, rsmp_data
- Structs from libsamplerate. */
-
- SRC_STATE* rsmp_state;
- SRC_DATA rsmp_data;
-
- int fillBufferResampled(AudioBuffer& dest, int start, int offset);
- int fillBufferCopy (AudioBuffer& dest, int start, int offset);
-};
-
-}} // giada::m::
-
-
-#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/math.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/const.h"
-#include "core/pluginHost.h"
-#include "core/mixerHandler.h"
-#include "sampleChannelProc.h"
-
-
-namespace giada {
-namespace m {
-namespace sampleChannelProc
-{
-namespace
-{
-void rewind_(SampleChannel* ch, Frame localFrame)
-{
- /* Quantization stops on rewind. */
-
- ch->quantizing = false;
-
- if (ch->isPlaying()) {
- ch->rewinding = true;
- ch->bufferOffset = localFrame;
- }
- else
- ch->tracker = ch->begin;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* quantize
-Starts channel according to quantizer. */
-
-void quantize_(SampleChannel* ch, int localFrame)
-{
- switch (ch->playStatus) {
- case ChannelStatus::OFF:
- ch->playStatus = ChannelStatus::PLAY;
- ch->bufferOffset = localFrame;
- ch->sendMidiLstatus();
- // ch->quantizing = false is set by sampleChannelRec::quantize()
- break;
-
- default:
- rewind_(ch, localFrame);
- break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onBar
-Things to do when the sequencer is on a bar. */
-
-void onBar_(SampleChannel* ch, int localFrame)
-{
- switch (ch->playStatus) {
- case ChannelStatus::PLAY:
- if (ch->mode == ChannelMode::LOOP_REPEAT)
- rewind_(ch, localFrame);
- break;
-
- case ChannelStatus::WAIT:
- if (ch->mode == ChannelMode::LOOP_ONCE_BAR) {
- ch->playStatus = ChannelStatus::PLAY;
- ch->bufferOffset = localFrame;
- ch->sendMidiLstatus();
- }
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onFirstBeat
-Things to do when the sequencer is on the first beat. */
-
-void onFirstBeat_(SampleChannel* ch, Frame localFrame)
-{
- switch (ch->playStatus) {
- case ChannelStatus::PLAY:
- if (ch->isAnyLoopMode())
- rewind_(ch, localFrame);
- break;
-
- case ChannelStatus::WAIT:
- ch->playStatus = ChannelStatus::PLAY;
- ch->bufferOffset = localFrame;
- ch->sendMidiLstatus();
- break;
-
- case ChannelStatus::ENDING:
- if (ch->isAnyLoopMode())
- kill(ch, localFrame);
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* onLastFrame
-Things to do when the sample has reached the end (i.e. last frame). Called by
-prepareBuffer(). */
-
-void onLastFrame_(SampleChannel* ch, bool running)
-{
- switch (ch->playStatus) {
- case ChannelStatus::PLAY:
- /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
- SINGLE_ENDLESS, which runs forever unless it's in ENDING mode.
- Other loop once modes are put in wait mode. */
- if ((ch->mode == ChannelMode::SINGLE_BASIC ||
- ch->mode == ChannelMode::SINGLE_PRESS ||
- ch->mode == ChannelMode::SINGLE_RETRIG) ||
- (ch->isAnyLoopMode() && !running))
- ch->playStatus = ChannelStatus::OFF;
- else
- if (ch->mode == ChannelMode::LOOP_ONCE ||
- ch->mode == ChannelMode::LOOP_ONCE_BAR)
- ch->playStatus = ChannelStatus::WAIT;
- ch->sendMidiLstatus();
- break;
-
- case ChannelStatus::ENDING:
- /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested
- their termination), stop 'em. Let them wait otherwise. */
- if (ch->mode == ChannelMode::LOOP_ONCE ||
- ch->mode == ChannelMode::LOOP_ONCE_BAR)
- ch->playStatus = ChannelStatus::WAIT;
- else {
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- }
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void processIO_(SampleChannel* ch, m::AudioBuffer& out, const m::AudioBuffer& in,
- bool running)
-{
- assert(out.countSamples() == ch->buffer.countSamples());
- if (in.isAllocd())
- assert(in.countSamples() == ch->buffer.countSamples());
-
- /* If armed and input buffer is not empty (i.e. input device available) and
- input monitor is on, copy input buffer to channel buffer: this enables the
- input monitoring. The channel buffer will be overwritten later on by
- pluginHost::processStack, so that you would record "clean" audio
- (i.e. not plugin-processed). */
-
- if (ch->armed && in.isAllocd() && ch->inputMonitor) {
- for (int i=0; i<ch->buffer.countFrames(); i++)
- for (int j=0; j<ch->buffer.countChannels(); j++)
- ch->buffer[i][j] += in[i][j]; // add, don't overwrite
- }
-
-#ifdef WITH_VST
- pluginHost::processStack(ch->buffer, ch->pluginIds);
-#endif
-
- for (int i=0; i<out.countFrames(); i++) {
- if (running)
- ch->calcVolumeEnvelope();
- if (!ch->mute)
- for (int j=0; j<out.countChannels(); j++)
- out[i][j] += ch->buffer[i][j] * ch->volume * ch->volume_i * ch->calcPanning(j);
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void processPreview_(SampleChannel* ch, m::AudioBuffer& out)
-{
- ch->bufferPreview.clear();
-
- /* If the tracker exceedes the end point and preview is looped, split the
- rendering as in SampleChannel::reset(). */
-
- if (ch->trackerPreview == ch->end)
- ch->trackerPreview = ch->begin;
- else
- if (ch->trackerPreview + ch->bufferPreview.countFrames() >= ch->end) {
- int offset = ch->end - ch->trackerPreview;
- ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
- ch->trackerPreview = ch->begin;
- if (ch->previewMode == PreviewMode::LOOP)
- ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->begin, offset);
- else
- if (ch->previewMode == PreviewMode::NORMAL)
- ch->previewMode = PreviewMode::NONE;
- }
- else
- ch->trackerPreview += ch->fillBuffer(ch->bufferPreview, ch->trackerPreview, 0);
-
- for (int i=0; i<out.countFrames(); i++)
- for (int j=0; j<out.countChannels(); j++)
- out[i][j] += ch->bufferPreview[i][j] * ch->volume * ch->calcPanning(j);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void fillBuffer_(SampleChannel* ch, bool running)
-{
- ch->buffer.clear();
-
- if (!ch->hasData() || !ch->isPlaying())
- return;
-
- if (ch->rewinding) {
-
- /* Fill the tail. */
-
- if (!ch->isOnLastFrame())
- ch->fillBuffer(ch->buffer, ch->tracker, 0);
-
- /* Reset tracker to begin point. */
-
- ch->tracker = ch->begin;
-
- /* Then fill the new head. */
-
- ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker, ch->bufferOffset);
- ch->bufferOffset = 0;
- ch->rewinding = false;
- }
- else {
- Frame framesUsed = ch->fillBuffer(ch->buffer, ch->tracker, ch->bufferOffset);
- ch->tracker += framesUsed;
- ch->bufferOffset = 0;
- if (ch->isOnLastFrame()) {
- onLastFrame_(ch, running);
- ch->tracker = ch->begin;
- if (ch->mode == ChannelMode::LOOP_BASIC ||
- ch->mode == ChannelMode::LOOP_REPEAT ||
- ch->mode == ChannelMode::SINGLE_ENDLESS) {
- /* framesUsed might be imprecise when working with resampled
- audio, which could cause a buffer overflow if used as offset.
- Let's clamp it to be at most buffer->countFrames(). */
- ch->tracker += ch->fillBuffer(ch->buffer, ch->tracker,
- u::math::bound(framesUsed, 0, ch->buffer.countFrames() - 1));
- }
- }
- }
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void kill(SampleChannel* ch, int localFrame)
-{
- switch (ch->playStatus) {
- case ChannelStatus::WAIT:
- case ChannelStatus::PLAY:
- case ChannelStatus::ENDING:
- /* Clear data in range [localFrame, (buffer.size)) if the kill event
- occurs in the middle of the buffer. */
- if (localFrame != 0)
- ch->buffer.clear(localFrame);
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- rewind_(ch, localFrame);
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stop(SampleChannel* ch)
-{
- switch (ch->playStatus) {
- case ChannelStatus::PLAY:
- if (ch->mode == ChannelMode::SINGLE_PRESS)
- kill(ch, 0);
- break;
-
- default:
- /* If quantizing, stop a SINGLE_PRESS immediately. */
- if (ch->mode == ChannelMode::SINGLE_PRESS && ch->quantizing)
- ch->quantizing = false;
- break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopInputRec(SampleChannel* ch, int globalFrame)
-{
- /* Start all sample channels in loop mode that were armed, i.e. that were
- recording stuff and not yet in play. They are also started in force mode, i.e.
- they must start playing right away at the current global frame, not at the
- next first beat. */
- if (ch->isAnyLoopMode() && ch->playStatus == ChannelStatus::OFF && ch->armed) {
- ch->playStatus = ChannelStatus::PLAY;
- ch->tracker = globalFrame;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt)
-{
- switch (ch->playStatus) {
- case ChannelStatus::WAIT:
- /* Loop-mode channels in wait status get stopped right away. */
- if (ch->isAnyLoopMode())
- ch->playStatus = ChannelStatus::OFF;
- break;
-
- case ChannelStatus::PLAY:
- /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end
- otherwise); b) when a channel is reading (and playing) actions. */
- if (chansStopOnSeqHalt)
- if (ch->isAnyLoopMode() || ch->isReadingActions())
- kill(ch, 0);
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindBySeq(SampleChannel* ch)
-{
- /* Rewind LOOP_ANY or SINGLE_ANY only if it's in read-record-mode. Rewind by
- sequencer is a user-generated event, it always occurs on local frame 0. */
-
- if (ch->hasData()) {
- if ((ch->isAnyLoopMode()) || (ch->recStatus == ChannelStatus::PLAY && (ch->isAnySingleMode())))
- rewind_(ch, 0);
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(SampleChannel* ch, bool value)
-{
- ch->mute = value;
-
- // This is for processing playing_inaudible
- ch->sendMidiLstatus();
-
- ch->sendMidiLmute();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(SampleChannel* ch, bool value)
-{
- ch->solo = value;
-
- // This is for processing playing_inaudible
- model::ChannelsLock l(model::channels);
- for (Channel* c : model::channels)
- c->sendMidiLstatus();
-
- ch->sendMidiLsolo();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity)
-{
- /* For one-shot modes, velocity drives the internal volume. */
- if (velocity != 0) {
- if (ch->isAnySingleMode() && ch->midiInVeloAsVol)
- ch->volume_i = u::math::map<int, float>(velocity, 0, G_MAX_VELOCITY, 0.0, 1.0);
- }
-
- switch (ch->playStatus) {
- case ChannelStatus::OFF:
- ch->bufferOffset = localFrame;
- if (ch->isAnyLoopMode()) {
- ch->playStatus = ChannelStatus::WAIT;
- ch->sendMidiLstatus();
- }
- else {
- if (doQuantize)
- ch->quantizing = true;
- else {
- ch->playStatus = ChannelStatus::PLAY;
- ch->sendMidiLstatus();
- }
- }
- break;
-
- case ChannelStatus::PLAY:
- if (ch->mode == ChannelMode::SINGLE_RETRIG) {
- if (doQuantize)
- ch->quantizing = true;
- else
- rewind_(ch, localFrame);
- }
- else
- if (ch->isAnyLoopMode() || ch->mode == ChannelMode::SINGLE_ENDLESS) {
- ch->playStatus = ChannelStatus::ENDING;
- ch->sendMidiLstatus();
- }
- else
- if (ch->mode == ChannelMode::SINGLE_BASIC) {
- rewind_(ch, localFrame);
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- }
- break;
-
- case ChannelStatus::WAIT:
- ch->playStatus = ChannelStatus::OFF;
- ch->sendMidiLstatus();
- break;
-
- case ChannelStatus::ENDING:
- ch->playStatus = ChannelStatus::PLAY;
- ch->sendMidiLstatus();
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running)
-{
- fillBuffer_(ch, running);
-
- if (audible)
- processIO_(ch, out, in, running);
-
- if (ch->isPreview())
- processPreview_(ch, out);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
-{
- if (!ch->hasData())
- return;
-
- /* Quantize only if is single mode and in quantizer-wait mode and a
- quantizer step has passed. */
-
- if (ch->isAnySingleMode() && ch->quantizing && fe.quantoPassed)
- quantize_(ch, fe.frameLocal);
- if (fe.onBar)
- onBar_(ch, fe.frameLocal);
- if (fe.onFirstBeat)
- onFirstBeat_(ch, fe.frameLocal);
-}
-}}};
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_SAMPLE_CHANNEL_PROC_H
-#define G_SAMPLE_CHANNEL_PROC_H
-
-
-#include "core/mixer.h"
-#include "core/audioBuffer.h"
-#include "core/types.h"
-
-
-namespace giada {
-namespace m
-{
-class SampleChannel;
-
-namespace sampleChannelProc
-{
-/**/
-void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in,
- AudioBuffer& inToOut, bool audible, bool running);
-
-/* parseEvents
-Parses events gathered by Mixer::masterPlay(). */
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents ev);
-
-/* kill
-Stops a channel abruptly. */
-
-void kill(SampleChannel* ch, int localFrame);
-
-/* stop
-Stops a channel normally (via key or MIDI). */
-
-void stop(SampleChannel* ch);
-
-/* stopInputRec
-Prepare a channel for playing when the input recording is done. */
-
-void stopInputRec(SampleChannel* ch, int globalFrame);
-
-/* stopBySeq
-Stops a channel when the stop button on main transport is pressed. */
-
-void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt);
-
-/* rewind
-Rewinds channel when rewind button on main transport is pressed. */
-
-void rewindBySeq(SampleChannel* ch);
-
-/* start
-Starts a channel. doQuantize = false (don't quantize) when Mixer is reading
-actions from Recorder. */
-
-void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity);
-
-void setMute(SampleChannel* ch, bool value);
-void setSolo(SampleChannel* ch, bool value);
-}}};
-
-
-#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <cassert>
-#include "utils/math.h"
-#include "core/channels/sampleChannel.h"
-#include "core/recorder.h"
-#include "core/recorderHandler.h"
-#include "core/recManager.h"
-#include "core/const.h"
-#include "core/conf.h"
-#include "core/clock.h"
-#include "core/action.h"
-#include "core/kernelAudio.h"
-#include "sampleChannelRec.h"
-
-
-namespace giada {
-namespace m {
-namespace sampleChannelRec
-{
-namespace
-{
-/* onFirstBeat
-Things to do when the sequencer is on the first beat. */
-
-void onFirstBeat_(SampleChannel* ch, bool recsStopOnChanHalt)
-{
- switch (ch->recStatus) {
- case ChannelStatus::ENDING:
- ch->recStatus = ChannelStatus::OFF;
- setReadActions(ch, false, recsStopOnChanHalt); // rec stop
- break;
-
- case ChannelStatus::WAIT:
- ch->recStatus = ChannelStatus::PLAY;
- setReadActions(ch, true, recsStopOnChanHalt); // rec start
- break;
-
- default: break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recorderCanRec_(SampleChannel* ch)
-{
- /* Can record on a channel if:
- - recorder is on
- - mixer is running
- - mixer is not recording a take somewhere
- - channel is MIDI or SAMPLE type with data in it */
-
- return recManager::isRecordingAction() &&
- clock::isRunning() &&
- !recManager::isRecordingInput() &&
- (ch->type == ChannelType::MIDI || (ch->type == ChannelType::SAMPLE && ch->hasData()));
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-/* calcVolumeEnv
-Computes any changes in volume done via envelope tool. */
-
-void calcVolumeEnv_(SampleChannel* ch, const Action& a1)
-{
- assert(a1.next != nullptr);
-
- const Action a2 = *a1.next;
-
- double vf1 = u::math::map<int, double>(a1.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
- double vf2 = u::math::map<int, double>(a2.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0);
-
- ch->volume_i = vf1;
- ch->volume_d = a2.frame == a1.frame ? 0 : (vf2 - vf1) / (a2.frame - a1.frame);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void parseAction_(SampleChannel* ch, const Action& a, int localFrame, int globalFrame)
-{
- switch (a.event.getStatus()) {
- case MidiEvent::NOTE_ON:
- if (ch->isAnySingleMode())
- ch->start(localFrame, /*quantize=*/false, /*velocity=*/0);
- break;
- case MidiEvent::NOTE_OFF:
- if (ch->isAnySingleMode())
- ch->stop();
- break;
- case MidiEvent::NOTE_KILL:
- if (ch->isAnySingleMode())
- ch->kill(localFrame);
- break;
- case MidiEvent::ENVELOPE:
- calcVolumeEnv_(ch, a);
- break;
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordKeyPressAction_(SampleChannel* ch)
-{
- if (!recorderCanRec_(ch))
- return;
-
- /* Disable reading actions while recording SINGLE_PRESS mode. Don't let
- existing actions interfere with the current one being recorded. */
-
- if (ch->mode == ChannelMode::SINGLE_PRESS)
- ch->readActions = false;
-
- recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_ON, 0, 0));
- ch->hasActions = true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void quantize_(SampleChannel* ch, bool quantoPassed)
-{
- /* Skip if in loop mode or not in a quantization stage. Otherwise the
- quantization wait has expired: record the keypress. */
-
- if (!ch->isAnyLoopMode() && ch->quantizing && quantoPassed && ch->playStatus == ChannelStatus::PLAY) {
- ch->quantizing = false;
- recordKeyPressAction_(ch);
- }
-}
-}; // {anonymous}
-
-
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
-
-
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe)
-{
- if (!ch->hasWave)
- return;
- quantize_(ch, fe.quantoPassed);
- if (fe.onFirstBeat)
- onFirstBeat_(ch, conf::conf.recsStopOnChanHalt);
- if (ch->readActions && fe.actions != nullptr)
- for (const Action& action : *fe.actions)
- if (action.channelId == ch->id)
- parseAction_(ch, action, fe.frameLocal, fe.frameGlobal);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recordStart(SampleChannel* ch, bool canQuantize)
-{
- /* Record a 'start' event if the quantizer is off, otherwise let mixer to
- handle it when a quantoWait has passed (see quantize_()). Also skip if
- channel is in any loop mode, where KEYPRESS and KEYREL are meaningless. */
-
- if (!canQuantize && !ch->isAnyLoopMode() && recorderCanRec_(ch))
- recordKeyPressAction_(ch);
- return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-bool recordKill(SampleChannel* ch)
-{
- /* Don't record NOTE_KILL actions for LOOP channels. */
- if (recorderCanRec_(ch) && !ch->isAnyLoopMode()) {
- recorder::rec(ch->id, clock::getCurrentFrame(), MidiEvent(MidiEvent::NOTE_KILL, 0, 0));
- ch->hasActions = true;
- }
- return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void recordStop(SampleChannel* ch)
-{
- /* Record a stop event only if channel is SINGLE_PRESS. For any other mode
- the stop event is meaningless. */
- if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS)
- recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_OFF, 0, 0));
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt)
-{
- ch->readActions = v;
- if (!v && recsStopOnChanHalt)
- ch->kill(0); // FIXME - wrong frame value
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, bool recsStopOnChanHalt)
-{
- if (treatRecsAsLoops)
- ch->recStatus = ChannelStatus::WAIT;
- else
- setReadActions(ch, true, recsStopOnChanHalt);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopReadingActions(SampleChannel* ch, bool isClockRunning, bool treatRecsAsLoops,
- bool recsStopOnChanHalt)
-{
- /* First of all, if the clock is not running just stop and disable everything.
- Then if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put the
- channel in REC_ENDING status. */
-
- if (!isClockRunning) {
- ch->recStatus = ChannelStatus::OFF;
- setReadActions(ch, false, false);
- }
- else
- if (ch->recStatus == ChannelStatus::WAIT)
- ch->recStatus = ChannelStatus::OFF;
- else
- if (ch->recStatus == ChannelStatus::ENDING)
- ch->recStatus = ChannelStatus::PLAY;
- else
- if (treatRecsAsLoops)
- ch->recStatus = ChannelStatus::ENDING;
- else
- setReadActions(ch, false, recsStopOnChanHalt);
-}
-}}};
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef G_SAMPLE_CHANNEL_REC_H
-#define G_SAMPLE_CHANNEL_REC_H
-
-
-namespace giada {
-namespace m
-{
-class SampleChannel;
-
-namespace sampleChannelRec
-{
-void parseEvents(SampleChannel* ch, mixer::FrameEvents fe);
-
-/* recordStart
-Records a 'start' action if capable of. Returns true if a start() call can
-be performed. */
-
-bool recordStart(SampleChannel* ch, bool doQuantize);
-
-/* recordKill
-Records a 'kill' action if capable of. Returns true if a kill() call can
-be performed. */
-
-bool recordKill(SampleChannel* ch);
-
-/* recordStop
-Ends overdub mode SINGLE_PRESS channels. */
-
-void recordStop(SampleChannel* ch);
-
-/* setReadActions
-If enabled (v == true), Recorder will read actions from channel 'ch'. If
-recsStopOnChanHalt == true and v == false, will also kill the channel. */
-
-void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt);
-
-void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops,
- bool recsStopOnChanHalt);
-void stopReadingActions(SampleChannel* ch, bool isClockRunning,
- bool treatRecsAsLoops, bool recsStopOnChanHalt);
-}}};
-
-
-#endif
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/conf.h"
+#include "core/clock.h"
+#include "core/action.h"
+#include "core/mixer.h"
+#include "core/channels/state.h"
+#include "utils/math.h"
+#include "sampleController.h"
+
+
+namespace giada {
+namespace m
+{
+namespace
+{
+constexpr int Q_ACTION_PLAY = 0;
+constexpr int Q_ACTION_REWIND = 1;
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+SampleController::SampleController(ChannelState* c, SamplePlayerState* s)
+: m_channelState (c)
+, m_samplePlayerState(s)
+{
+ m_samplePlayerState->quantizer.schedule(Q_ACTION_PLAY,
+ [&status = m_channelState->playStatus, &offset = m_samplePlayerState->offset]
+ (Frame delta)
+ {
+ offset = delta;
+ status = ChannelStatus::PLAY;
+ });
+
+ m_samplePlayerState->quantizer.schedule(Q_ACTION_REWIND, [this] (Frame delta)
+ {
+ rewind(delta);
+ });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SampleController::SampleController(const SampleController& o, ChannelState* c, SamplePlayerState* s)
+: SampleController(c, s)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::parse(const mixer::Event& e) const
+{
+ assert(m_channelState != nullptr);
+ assert(m_samplePlayerState != nullptr);
+
+ switch (e.type) {
+ case mixer::EventType::KEY_PRESS:
+ press(e.delta, e.action.event.getVelocity(), /*manual=*/true); break;
+
+ case mixer::EventType::KEY_RELEASE:
+ release(e.delta); break;
+
+ case mixer::EventType::KEY_KILL:
+ kill(e.delta); break;
+
+ case mixer::EventType::SEQUENCER_FIRST_BEAT:
+ if (clock::isRunning())
+ onFirstBeat(e.delta);
+ break;
+
+ case mixer::EventType::SEQUENCER_BAR:
+ onBar(e.delta); break;
+
+ case mixer::EventType::SEQUENCER_STOP:
+ onStopBySeq(); break;
+
+ case mixer::EventType::ACTION:
+ if (m_channelState->readActions.load() == true)
+ parseAction(e.action, e.delta);
+ break;
+
+ case mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS:
+ toggleReadActions(); break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onLastFrame() const
+{
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ SamplePlayerMode mode = m_samplePlayerState->mode.load();
+ bool running = clock::isRunning();
+
+ if (playStatus == ChannelStatus::PLAY) {
+ /* Stop LOOP_* when the sequencer is off, or SINGLE_* except for
+ SINGLE_ENDLESS, which runs forever unless it's in ENDING mode.
+ Other loop once modes are put in wait mode. */
+ if ((mode == SamplePlayerMode::SINGLE_BASIC ||
+ mode == SamplePlayerMode::SINGLE_PRESS ||
+ mode == SamplePlayerMode::SINGLE_RETRIG) ||
+ (m_samplePlayerState->isAnyLoopMode() && !running))
+ playStatus = ChannelStatus::OFF;
+ else
+ if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+ playStatus = ChannelStatus::WAIT;
+ }
+ else
+ if (playStatus == ChannelStatus::ENDING) {
+ /* LOOP_ONCE or LOOP_ONCE_BAR: if ending (i.e. the user requested
+ their termination), stop 'em. Let them wait otherwise. */
+ if (mode == SamplePlayerMode::LOOP_ONCE || mode == SamplePlayerMode::LOOP_ONCE_BAR)
+ playStatus = ChannelStatus::WAIT;
+ else
+ playStatus = ChannelStatus::OFF;
+ }
+
+ m_channelState->playStatus.store(playStatus);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::advance(Frame bufferSize) const
+{
+ Range<Frame> block(clock::getCurrentFrame(), clock::getCurrentFrame() + bufferSize);
+ m_samplePlayerState->quantizer.advance(block, clock::getQuantizerStep());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::press(Frame localFrame, int velocity, bool manual) const
+{
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ SamplePlayerMode mode = m_samplePlayerState->mode.load();
+ bool isLoop = m_samplePlayerState->isAnyLoopMode();
+
+ switch (playStatus) {
+ case ChannelStatus::OFF:
+ playStatus = pressWhileOff(localFrame, velocity, isLoop, manual); break;
+
+ case ChannelStatus::PLAY:
+ playStatus = pressWhilePlay(localFrame, mode, isLoop, manual); break;
+
+ case ChannelStatus::WAIT:
+ playStatus = ChannelStatus::OFF; break;
+
+ case ChannelStatus::ENDING:
+ playStatus = ChannelStatus::PLAY; break;
+
+ default: break;
+ }
+
+ m_channelState->playStatus.store(playStatus);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::release(Frame localFrame) const
+{
+ /* Key release is meaningful only for SINGLE_PRESS modes. */
+
+ if (m_samplePlayerState->mode.load() != SamplePlayerMode::SINGLE_PRESS)
+ return;
+
+ /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a
+ quantization step in progress that would play the channel later on:
+ disable it. */
+
+ if (m_channelState->playStatus.load() == ChannelStatus::PLAY)
+ kill(localFrame);
+ else
+ if (m_samplePlayerState->quantizer.isTriggered())
+ m_samplePlayerState->quantizer.clear();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::kill(Frame localFrame) const
+{
+ m_channelState->playStatus.store(ChannelStatus::OFF);
+ m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load());
+ m_samplePlayerState->quantizing = false;
+
+ /* Clear data in range [localFrame, (buffer.size)) if the kill event occurs
+ in the middle of the buffer. */
+
+ if (localFrame != 0)
+ m_channelState->buffer.clear(localFrame);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::rewind(Frame localFrame) const
+{
+ /* Quantization stops on rewind. */
+
+ m_samplePlayerState->quantizer.clear();
+
+ if (m_channelState->isPlaying()) {
+ m_samplePlayerState->rewinding = true;
+ m_samplePlayerState->offset = localFrame;
+ }
+ else
+ m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ChannelStatus SampleController::pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const
+{
+ m_samplePlayerState->offset = localFrame;
+
+ if (isLoop)
+ return ChannelStatus::WAIT;
+
+ if (m_samplePlayerState->velocityAsVol.load() == true)
+ m_channelState->volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME);
+
+ if (clock::canQuantize() && manual) { // manual: don't quantize recorded actions
+ m_samplePlayerState->quantizer.trigger(Q_ACTION_PLAY);
+ return ChannelStatus::OFF;
+ }
+ else
+ return ChannelStatus::PLAY;
+}
+
+
+ChannelStatus SampleController::pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const
+{
+ if (mode == SamplePlayerMode::SINGLE_RETRIG) {
+ if (clock::canQuantize() && manual) // manual: don't quantize recorded actions
+ m_samplePlayerState->quantizer.trigger(Q_ACTION_REWIND);
+ else
+ rewind(localFrame);
+ return ChannelStatus::PLAY;
+ }
+
+ if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS)
+ return ChannelStatus::ENDING;
+
+ if (mode == SamplePlayerMode::SINGLE_BASIC) {
+ rewind(localFrame);
+ return ChannelStatus::OFF;
+ }
+
+ return ChannelStatus::OFF;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onBar(Frame localFrame) const
+{
+ G_DEBUG("onBar ch=" << m_channelState->id);
+
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ SamplePlayerMode mode = m_samplePlayerState->mode.load();
+
+ if (playStatus == ChannelStatus::PLAY && mode == SamplePlayerMode::LOOP_REPEAT)
+ rewind(localFrame);
+ else
+ if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR) {
+ m_channelState->playStatus.store(ChannelStatus::PLAY);
+ m_samplePlayerState->offset = localFrame;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onFirstBeat(Frame localFrame) const
+{
+G_DEBUG("onFirstBeat ch=" << m_channelState->id << ", localFrame=" << localFrame);
+
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ bool isLoop = m_samplePlayerState->isAnyLoopMode();
+
+ switch (playStatus) {
+
+ case ChannelStatus::PLAY:
+ if (isLoop)
+ rewind(localFrame);
+ break;
+
+ case ChannelStatus::WAIT:
+ m_samplePlayerState->offset = localFrame;
+ m_channelState->playStatus.store(ChannelStatus::PLAY);
+ break;
+
+ case ChannelStatus::ENDING:
+ if (isLoop)
+ kill(localFrame);
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::onStopBySeq() const
+{
+ G_DEBUG("onStopBySeq ch=" << m_channelState->id);
+
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ bool isReadingActions = m_channelState->readActions.load() == true;
+ bool isLoop = m_samplePlayerState->isAnyLoopMode();
+
+ switch (playStatus) {
+
+ case ChannelStatus::WAIT:
+ /* Loop-mode channels in wait status get stopped right away. */
+ if (isLoop)
+ m_channelState->playStatus.store(ChannelStatus::OFF);
+ break;
+
+ case ChannelStatus::PLAY:
+ /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end
+ otherwise); b) when a channel is reading (and playing) actions. */
+ if (conf::conf.chansStopOnSeqHalt)
+ if (isLoop || isReadingActions)
+ kill(0);
+ break;
+
+ default: break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::parseAction(const Action& a, Frame localFrame) const
+{
+ bool isLoop = m_samplePlayerState->isAnyLoopMode();
+
+ switch (a.event.getStatus()) {
+ case MidiEvent::NOTE_ON:
+ if (!isLoop)
+ press(localFrame, /*velocity=*/G_MAX_VELOCITY, /*manual=*/false);
+ break;
+ case MidiEvent::NOTE_OFF:
+ if (!isLoop)
+ release(localFrame);
+ break;
+ case MidiEvent::NOTE_KILL:
+ if (!isLoop)
+ kill(localFrame);
+ break;
+ case MidiEvent::ENVELOPE:
+ //calcVolumeEnv_(ch, a); TODO
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SampleController::toggleReadActions() const
+{
+ ChannelStatus recStatus = m_channelState->recStatus.load();
+ if (clock::isRunning() && recStatus == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops)
+ kill(0);
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_SAMPLE_CONTROLLER_H
+#define G_CHANNEL_SAMPLE_CONTROLLER_H
+
+
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+namespace mixer
+{
+struct Event;
+}
+struct SamplePlayerState;
+class SampleController
+{
+public:
+
+ SampleController(ChannelState*, SamplePlayerState*);
+ SampleController(const SampleController&, ChannelState* c=nullptr, SamplePlayerState* s=nullptr);
+
+ void parse(const mixer::Event& e) const;
+ void onLastFrame() const;
+ void advance(Frame bufferSize) const;
+
+private:
+
+ void press(Frame localFrame, int velocity, bool manual) const;
+ void release(Frame localFrame) const;
+ void kill(Frame localFrame) const;
+ void rewind(Frame localFrame) const;
+
+ ChannelStatus pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const;
+ ChannelStatus pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const;
+ void toggleReadActions() const;
+
+ void onBar(Frame localFrame) const;
+ void onFirstBeat(Frame localFrame) const;
+ void onStopBySeq() const;
+ void parseAction(const Action& a, Frame localFrame) const;
+
+ ChannelState* m_channelState;
+ SamplePlayerState* m_samplePlayerState;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <algorithm>
+#include <cassert>
+#include "core/channels/channel.h"
+#include "core/channels/state.h"
+#include "core/wave.h"
+#include "core/clock.h"
+#include "samplePlayer.h"
+
+
+namespace giada {
+namespace m
+{
+SamplePlayer::SamplePlayer(ChannelState* c)
+: state (std::make_unique<SamplePlayerState>())
+, m_waveId (0)
+, m_sampleController(c, state.get())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayer::SamplePlayer(const SamplePlayer& o, ChannelState* c)
+: state (std::make_unique<SamplePlayerState>(*o.state))
+, m_waveId (o.m_waveId)
+, m_waveReader (o.m_waveReader)
+, m_sampleController(o.m_sampleController, c, state.get())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayer::SamplePlayer(const patch::Channel& p, ChannelState* c)
+: state (std::make_unique<SamplePlayerState>(p))
+, m_waveId (p.waveId)
+, m_sampleController(c, state.get())
+, m_channelState (c)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::parse(const mixer::Event& e) const
+{
+ if (e.type == mixer::EventType::CHANNEL_PITCH)
+ state->pitch.store(e.action.event.getVelocityFloat());
+
+ if (hasWave())
+ m_sampleController.parse(e);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::advance(Frame bufferSize) const
+{
+ m_sampleController.advance(bufferSize);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::render(AudioBuffer& out) const
+{
+ assert(m_channelState != nullptr);
+
+ if (m_waveReader.wave == nullptr || !m_channelState->isPlaying())
+ return;
+
+ /* Advance SampleController: this is needed for quantization. */
+
+ Frame begin = state->begin.load();
+ Frame end = state->end.load();
+ Frame tracker = state->tracker.load();
+ float pitch = state->pitch.load();
+ Frame used = 0;
+
+ /* Audio data is temporarily stored to the working audio buffer. */
+
+ AudioBuffer& buffer = m_channelState->buffer;
+
+ /* Adjust tracker in case someone has changed the begin/end points in the
+ meantime. */
+
+ if (tracker < begin || tracker >= end)
+ tracker = begin;
+
+ /* If rewinding, fill the tail first, then reset the tracker to the begin
+ point. The rest is performed as usual. */
+
+ if (state->rewinding) {
+ if (tracker < end)
+ m_waveReader.fill(buffer, tracker, 0, pitch);
+ state->rewinding = false;
+ tracker = begin;
+ }
+
+ used = m_waveReader.fill(buffer, tracker, state->offset, pitch);
+ tracker += used;
+
+//G_DEBUG ("block=[" << tracker - used << ", " << tracker << ")" <<
+// ", used=" << used << ", range=[" << begin << ", " << end << ")" <<
+// ", offset=" << state->offset << ", globalFrame=" << clock::getCurrentFrame());
+
+ if (tracker >= end) {
+//G_DEBUG ("last frame tracker=" << tracker);
+ tracker = begin;
+ m_sampleController.onLastFrame();
+ if (shouldLoop()) {
+ Frame offset = std::min(static_cast<Frame>(used / pitch), buffer.countFrames() - 1);
+ tracker += m_waveReader.fill(buffer, tracker, offset, pitch);
+ }
+ }
+
+ state->offset = 0;
+ state->tracker.store(tracker);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::loadWave(const Wave* w)
+{
+ m_waveReader.wave = w;
+
+ state->tracker.store(0);
+ state->shift.store(0);
+ state->begin.store(0);
+
+ if (w != nullptr) {
+ m_waveId = w->id;
+ m_channelState->playStatus.store(ChannelStatus::OFF);
+ m_channelState->name = w->getBasename(/*ext=*/false);
+ state->end.store(w->getSize() - 1);
+ }
+ else {
+ m_waveId = 0;
+ m_channelState->playStatus.store(ChannelStatus::EMPTY);
+ m_channelState->name = "";
+ state->end.store(0);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::setWave(const Wave& w)
+{
+ m_waveReader.wave = &w;
+ m_waveId = w.id;
+}
+
+
+void SamplePlayer::setInvalidWave()
+{
+ m_waveReader.wave = nullptr;
+ m_waveId = 0;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void SamplePlayer::kickIn(Frame f)
+{
+ assert(hasWave());
+
+ state->tracker.store(f);
+ m_channelState->playStatus.store(ChannelStatus::PLAY);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayer::shouldLoop() const
+{
+ ChannelStatus playStatus = m_channelState->playStatus.load();
+ SamplePlayerMode mode = state->mode.load();
+
+ return (mode == SamplePlayerMode::LOOP_BASIC ||
+ mode == SamplePlayerMode::LOOP_REPEAT ||
+ mode == SamplePlayerMode::SINGLE_ENDLESS) && playStatus == ChannelStatus::PLAY;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayer::hasWave() const { return m_waveReader.wave != nullptr; }
+bool SamplePlayer::hasLogicalWave() const { return hasWave() && m_waveReader.wave->isLogical(); }
+bool SamplePlayer::hasEditedWave() const { return hasWave() && m_waveReader.wave->isEdited(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ID SamplePlayer::getWaveId() const
+{
+ return m_waveId;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame SamplePlayer::getWaveSize() const
+{
+ return hasWave() ? m_waveReader.wave->getSize() : 0;
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_SAMPLE_PLAYER_H
+#define G_CHANNEL_SAMPLE_PLAYER_H
+
+
+#include "core/types.h"
+#include "core/const.h"
+#include "core/mixer.h" // TODO - forward declare
+#include "core/audioBuffer.h" // TODO - forward declare
+#include "core/channels/waveReader.h"
+#include "core/channels/sampleController.h"
+
+
+namespace giada {
+namespace m
+{
+class Wave;
+struct SamplePlayerState;
+class SamplePlayer
+{
+public:
+
+ SamplePlayer(ChannelState*);
+ SamplePlayer(const patch::Channel& p, ChannelState*);
+ SamplePlayer(const SamplePlayer&, ChannelState* c=nullptr);
+
+ void parse(const mixer::Event& e) const;
+ void advance(Frame bufferSize) const;
+ void render(AudioBuffer& out) const;
+
+ bool hasWave() const;
+ bool hasLogicalWave() const;
+ bool hasEditedWave() const;
+ ID getWaveId() const;
+ Frame getWaveSize() const;
+
+ /* loadWave
+ Loads Wave 'w' into this channel and sets it up (name, markers, ...). */
+
+ void loadWave(const Wave* w);
+
+ /* setWave
+ Just sets the pointer to a Wave object. Used during de-serialization. */
+
+ void setWave(const Wave& w);
+
+ /* setInvalidWave
+ Same as setWave(nullptr) plus the invalid ID (i.e. 0). */
+
+ void setInvalidWave();
+
+ /* kickIn
+ Starts the player right away at frame 'f'. Used when launching a loop after
+ being live recorded. */
+
+ void kickIn(Frame f);
+
+
+ /* state
+ Pointer to mutable SamplePlayerState state. */
+
+ std::unique_ptr<SamplePlayerState> state;
+
+private:
+
+ bool shouldLoop() const;
+
+ ID m_waveId;
+
+ /* m_waveReader
+ Used to read data from Wave and fill incoming buffer. */
+
+ WaveReader m_waveReader;
+
+ /* m_sampleController
+ Managers events for this Sample Player. */
+
+ SampleController m_sampleController;
+
+ /* m_channelState
+ Pointer to Channel state. Needed to alter the playStatus status when the
+ sample is over. */
+
+ ChannelState* m_channelState;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/patch.h"
+#include "state.h"
+
+
+namespace giada {
+namespace m
+{
+MidiLearnerState::MidiLearnerState()
+: enabled (true)
+, filter (0)
+, keyPress (0x0)
+, keyRelease (0x0)
+, kill (0x0)
+, arm (0x0)
+, volume (0x0)
+, mute (0x0)
+, solo (0x0)
+, readActions (0x0)
+, pitch (0x0)
+{
+}
+
+
+MidiLearnerState::MidiLearnerState(const patch::Channel& p)
+: enabled (p.midiIn)
+, filter (p.midiInFilter)
+, keyPress (p.midiInKeyPress)
+, keyRelease (p.midiInKeyRel)
+, kill (p.midiInKill)
+, arm (p.midiInArm)
+, volume (p.midiInVolume)
+, mute (p.midiInMute)
+, solo (p.midiInSolo)
+, readActions (p.midiInReadActions)
+, pitch (p.midiInPitch)
+{
+}
+
+
+MidiLearnerState::MidiLearnerState(const MidiLearnerState& o)
+: enabled (o.enabled.load())
+, filter (o.filter.load())
+, keyPress (o.keyPress.load())
+, keyRelease (o.keyRelease.load())
+, kill (o.kill.load())
+, arm (o.arm.load())
+, volume (o.volume.load())
+, mute (o.mute.load())
+, solo (o.solo.load())
+, readActions (o.readActions.load())
+, pitch (o.pitch.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool MidiLearnerState::isAllowed(int c) const
+{
+ int filter_ = filter.load();
+ bool enabled_ = enabled.load();
+
+ return enabled_ && (filter_ == -1 || filter_ == c);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+MidiLighterState::MidiLighterState()
+: enabled(false)
+, playing(0x0)
+, mute (0x0)
+, solo (0x0)
+{
+}
+
+
+MidiLighterState::MidiLighterState(const patch::Channel& p)
+: enabled(p.midiOutL)
+, playing(p.midiOutLplaying)
+, mute (p.midiOutLmute)
+, solo (p.midiOutLsolo)
+{
+}
+
+
+MidiLighterState::MidiLighterState(const MidiLighterState& o)
+: enabled(o.enabled.load())
+, playing(o.playing.load())
+, mute (o.mute.load())
+, solo (o.solo.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+MidiSenderState::MidiSenderState()
+: enabled(true)
+, filter (0)
+{
+}
+
+
+MidiSenderState::MidiSenderState(const patch::Channel& p)
+: enabled(p.midiOut)
+, filter (p.midiOutChan)
+{
+}
+
+
+MidiSenderState::MidiSenderState(const MidiSenderState& o)
+: enabled(o.enabled.load())
+, filter (o.filter.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+MidiReceiverState::MidiReceiverState()
+{
+ midiBuffer.ensureSize(G_DEFAULT_VST_MIDIBUFFER_SIZE);
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+SamplePlayerState::SamplePlayerState()
+: tracker (0)
+, pitch (G_DEFAULT_PITCH)
+, mode (SamplePlayerMode::SINGLE_BASIC)
+, velocityAsVol(false)
+, rewinding (false)
+, quantizing (false)
+, offset (0)
+{
+}
+
+
+SamplePlayerState::SamplePlayerState(const SamplePlayerState& o)
+: tracker (o.tracker.load())
+, pitch (o.pitch.load())
+, mode (o.mode.load())
+, shift (o.shift.load())
+, begin (o.begin.load())
+, end (o.end.load())
+, velocityAsVol(o.velocityAsVol.load())
+, rewinding (o.rewinding)
+, quantizing (o.quantizing)
+, offset (o.offset)
+, quantizer (o.quantizer)
+{
+}
+
+
+SamplePlayerState::SamplePlayerState(const patch::Channel& p)
+: tracker (0)
+, pitch (p.pitch)
+, mode (p.mode)
+, shift (p.shift)
+, begin (p.begin)
+, end (p.end)
+, velocityAsVol(p.midiInVeloAsVol)
+, rewinding (false)
+, quantizing (false)
+, offset (0)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool SamplePlayerState::isAnyLoopMode() const
+{
+ SamplePlayerMode m = mode.load();
+
+ return m == SamplePlayerMode::LOOP_BASIC ||
+ m == SamplePlayerMode::LOOP_ONCE ||
+ m == SamplePlayerMode::LOOP_REPEAT ||
+ m == SamplePlayerMode::LOOP_ONCE_BAR;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+AudioReceiverState::AudioReceiverState()
+: inputMonitor(false)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const patch::Channel& p)
+: inputMonitor(p.inputMonitor)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const AudioReceiverState& o)
+: inputMonitor(o.inputMonitor.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+ChannelState::ChannelState(ID id, Frame bufferSize)
+: id (id)
+, playStatus (ChannelStatus::OFF)
+, recStatus (ChannelStatus::OFF)
+, volume (G_DEFAULT_VOL)
+, pan (G_DEFAULT_PAN)
+, mute (false)
+, solo (false)
+, armed (false)
+, key (0)
+, readActions(true)
+, buffer (bufferSize, G_MAX_IO_CHANS)
+, hasActions (false)
+, height (G_GUI_UNIT)
+, volume_i (1.0f)
+{
+}
+
+
+ChannelState::ChannelState(const ChannelState& o)
+: id (o.id)
+, playStatus (o.playStatus.load())
+, recStatus (o.recStatus.load())
+, volume (o.volume.load())
+, pan (o.pan.load())
+, mute (o.mute.load())
+, solo (o.solo.load())
+, armed (o.armed.load())
+, key (o.key.load())
+, readActions(o.readActions.load())
+, buffer (o.buffer)
+, hasActions (o.hasActions)
+, name (o.name)
+, height (o.height)
+, volume_i (o.volume_i)
+{
+}
+
+
+ChannelState::ChannelState(const patch::Channel& p, Frame bufferSize)
+: id (p.id)
+, playStatus (ChannelStatus::OFF)
+, recStatus (ChannelStatus::OFF)
+, volume (p.volume)
+, pan (p.pan)
+, mute (p.mute)
+, solo (p.solo)
+, armed (p.armed)
+, key (p.key)
+, readActions(p.readActions)
+, buffer (bufferSize, G_MAX_IO_CHANS)
+, hasActions (p.hasActions)
+, name (p.name)
+, height (p.height)
+, volume_i (1.0f)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool ChannelState::isPlaying() const
+{
+ ChannelStatus s = playStatus.load();
+ return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING;
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_STATE_H
+#define G_CHANNEL_STATE_H
+
+
+#include <string>
+#include <atomic>
+#include "core/const.h"
+#include "core/types.h"
+#include "core/quantizer.h"
+#include "core/audioBuffer.h"
+#ifdef WITH_VST
+#include "deps/juce-config.h"
+#endif
+
+
+namespace giada {
+namespace m {
+namespace patch
+{
+struct Channel;
+}
+struct MidiLearnerState
+{
+ MidiLearnerState();
+ MidiLearnerState(const patch::Channel& p);
+ MidiLearnerState(const MidiLearnerState& o);
+
+ /* isAllowed
+ Tells whether the current MIDI channel 'channel' is enabled to receive MIDI
+ data. */
+
+ bool isAllowed(int channel) const;
+
+ /* enabled
+ Tells whether MIDI learning is enabled for the current channel. */
+
+ std::atomic<bool> enabled;
+
+ /* filter
+ Which MIDI channel should be filtered out when receiving MIDI messages.
+ If -1 means 'all'. */
+
+ std::atomic<int> filter;
+
+ /* MIDI learning fields. */
+
+ std::atomic<uint32_t> keyPress;
+ std::atomic<uint32_t> keyRelease;
+ std::atomic<uint32_t> kill;
+ std::atomic<uint32_t> arm;
+ std::atomic<uint32_t> volume;
+ std::atomic<uint32_t> mute;
+ std::atomic<uint32_t> solo;
+ std::atomic<uint32_t> readActions; // Sample Channels only
+ std::atomic<uint32_t> pitch; // Sample Channels only
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct MidiLighterState
+{
+ MidiLighterState();
+ MidiLighterState(const patch::Channel& p);
+ MidiLighterState(const MidiLighterState& o);
+
+ /* enabled
+ Tells whether MIDI ligthing is enabled or not. */
+
+ std::atomic<bool> enabled;
+
+ /* MIDI learning fields for MIDI ligthing. */
+
+ std::atomic<uint32_t> playing;
+ std::atomic<uint32_t> mute;
+ std::atomic<uint32_t> solo;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct MidiSenderState
+{
+ MidiSenderState();
+ MidiSenderState(const patch::Channel& p);
+ MidiSenderState(const MidiSenderState& o);
+
+ /* enabled
+ Tells whether MIDI output is enabled or not. */
+
+ std::atomic<bool> enabled;
+
+ /* filter
+ Which MIDI channel data should be sent to. */
+
+ std::atomic<int> filter;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+struct MidiReceiverState
+{
+ MidiReceiverState();
+
+ /* midiBuffer
+ Contains MIDI events to be sent to plug-ins. */
+
+ juce::MidiBuffer midiBuffer;
+};
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct SamplePlayerState
+{
+ SamplePlayerState();
+ SamplePlayerState(const patch::Channel& p);
+ SamplePlayerState(const SamplePlayerState& o);
+
+ bool isAnyLoopMode() const;
+
+ std::atomic<Frame> tracker;
+ std::atomic<float> pitch;
+ std::atomic<SamplePlayerMode> mode;
+ std::atomic<Frame> shift;
+ std::atomic<Frame> begin;
+ std::atomic<Frame> end;
+
+ /* velocityAsVol
+ Velocity drives volume. */
+
+ std::atomic<bool> velocityAsVol;
+
+ bool rewinding;
+ bool quantizing;
+ Frame offset;
+ Quantizer quantizer;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct AudioReceiverState
+{
+ AudioReceiverState();
+ AudioReceiverState(const patch::Channel& p);
+ AudioReceiverState(const AudioReceiverState& o);
+
+ std::atomic<bool> inputMonitor;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct ChannelState
+{
+ ChannelState(ID id, Frame bufferSize);
+ ChannelState(const patch::Channel& p, Frame bufferSize);
+ ChannelState(const ChannelState& o);
+
+ bool isPlaying() const;
+
+ ID id;
+
+ std::atomic<ChannelStatus> playStatus;
+ std::atomic<ChannelStatus> recStatus;
+ std::atomic<float> volume;
+ std::atomic<float> pan;
+ std::atomic<bool> mute;
+ std::atomic<bool> solo;
+ std::atomic<bool> armed;
+ std::atomic<int> key;
+ std::atomic<bool> readActions;
+
+ /* buffer (internal)
+ Working buffer for internal processing. */
+
+ AudioBuffer buffer;
+
+ bool hasActions;
+ std::string name;
+ Pixel height;
+
+ /* volume_i (internal)
+ Internal volume used for volume automation and velocity-drives-volume mode
+ on Sample Channels. */
+
+ float volume_i;
+};
+}} // giada::m::
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <memory>
+#include <cassert>
+#include <algorithm>
+#include "core/const.h"
+#include "core/model/model.h"
+#include "core/audioBuffer.h"
+#include "core/wave.h"
+#include "utils/log.h"
+#include "waveReader.h"
+
+
+namespace giada {
+namespace m
+{
+WaveReader::WaveReader()
+: wave (nullptr),
+ m_srcState(nullptr)
+{
+ allocateSrc();
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::WaveReader(const WaveReader& o)
+: wave (o.wave),
+ m_srcState(nullptr)
+{
+ allocateSrc();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::WaveReader(WaveReader&& o)
+: wave (o.wave),
+ m_srcState(nullptr)
+{
+ moveSrc(&o.m_srcState);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader& WaveReader::operator=(const WaveReader& o)
+{
+ if (this == &o) return *this;
+ wave = o.wave;
+ allocateSrc();
+ return *this;
+}
+
+
+WaveReader& WaveReader::operator=(WaveReader&& o)
+{
+ if (this == &o) return *this;
+ wave = o.wave;
+ moveSrc(&o.m_srcState);
+ return *this;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+WaveReader::~WaveReader()
+{
+ if (m_srcState != nullptr)
+ src_delete(m_srcState);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fill(AudioBuffer& out, Frame start, Frame offset, float pitch) const
+{
+ assert(wave != nullptr);
+ assert(start >= 0);
+ assert(offset < out.countFrames());
+
+ model::WavesLock l(model::waves); // TODO dependency
+
+ if (pitch == 1.0) return fillCopy(out, start, offset);
+ else return fillResampled(out, start, offset, pitch);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fillResampled(AudioBuffer& dest, Frame start, Frame offset, float pitch) const
+{
+ SRC_DATA srcData;
+
+ srcData.data_in = wave->getFrame(start); // Source data
+ srcData.input_frames = wave->getSize() - start; // How many readable frames
+ srcData.data_out = dest[offset]; // Destination (processed data)
+ srcData.output_frames = dest.countFrames() - offset; // How many frames to process
+ srcData.end_of_input = false;
+ srcData.src_ratio = 1 / pitch;
+
+ src_process(m_srcState, &srcData);
+
+ return srcData.input_frames_used;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Frame WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame offset) const
+{
+ Frame used = dest.countFrames() - offset;
+ if (used > wave->getSize() - start)
+ used = wave->getSize() - start;
+
+ dest.copyData(wave->getFrame(start), used, offset);
+
+ return used;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void WaveReader::allocateSrc()
+{
+ m_srcState = src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr);
+ if (m_srcState == nullptr) {
+ u::log::print("[WaveReader] unable to allocate memory for SRC_STATE!\n");
+ throw std::bad_alloc();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void WaveReader::moveSrc(SRC_STATE** other)
+{
+ if (m_srcState != nullptr)
+ src_delete(m_srcState);
+ m_srcState = *other;
+ *other = nullptr;
+}
+}} // giada::m::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_CHANNEL_WAVE_READER_H
+#define G_CHANNEL_WAVE_READER_H
+
+
+#include <samplerate.h>
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+class Wave;
+class WaveReader final
+{
+public:
+
+ WaveReader();
+ WaveReader(const WaveReader&);
+ WaveReader(WaveReader&&);
+ WaveReader& operator=(const WaveReader&);
+ WaveReader& operator=(WaveReader&&);
+ ~WaveReader();
+
+ Frame fill(AudioBuffer& out, Frame start, Frame offset, float pitch) const;
+
+ /* wave
+ Wave object. Might be null if the channel has no sample. */
+
+ const Wave* wave;
+
+private:
+
+ Frame fillResampled(AudioBuffer& out, Frame start, Frame offset, float pitch) const;
+ Frame fillCopy (AudioBuffer& out, Frame start, Frame offset) const;
+
+ void allocateSrc();
+ void moveSrc(SRC_STATE** o);
+
+ /* srcState
+ Struct from libsamplerate. */
+
+ SRC_STATE* m_srcState;
+};
+}} // giada::m::
+
+
+#endif
#include <atomic>
#include <cassert>
#include "glue/main.h"
-#include "utils/math.h"
+#include "glue/events.h"
#include "core/model/model.h"
#include "core/conf.h"
+#include "core/sequencer.h"
#include "core/const.h"
#include "core/kernelAudio.h"
#include "core/mixerHandler.h"
#include "core/kernelMidi.h"
+#include "utils/math.h"
#include "clock.h"
std::atomic<int> currentFrame_(0);
std::atomic<int> currentBeat_(0);
-int quanto_ = 1; // Quantizer step
+/* quantizerStep_
+Tells how many frames to wait to perform a quantized action. */
+
+int quantizerStep_ = 1;
+
+/* midiTC*
+MIDI timecode variables. */
int midiTCrate_ = 0; // Send MTC data every midiTCrate_ frames
int midiTCframes_ = 0;
int midiTCminutes_ = 0;
int midiTChours_ = 0;
-
#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
kernelAudio::JackState jackStatePrev_;
#endif
c.framesInSeq = c.framesInBeat * G_MAX_BEATS;
if (c.quantize != 0)
- quanto_ = c.framesInBeat / c.quantize;
+ quantizerStep_ = c.framesInBeat / c.quantize;
}
}; // {anonymous}
bool quantoHasPassed()
{
- return currentFrame_.load() % quanto_ == 0;
+ return clock::getQuantizerValue() != 0 && currentFrame_.load() % quantizerStep_ == 0;
}
void setBpm(float b)
{
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-
- /* Can't change bpm from within Giada when using JACK. */
-
- if (m::kernelAudio::getAPI() == G_SYS_API_JACK)
- return;
-
-#endif
-
- b = u::math::bound(b, G_MIN_BPM, G_MAX_BPM);
+ b = std::clamp(b, G_MIN_BPM, G_MAX_BPM);
model::onSwap(model::clock, [&](model::Clock& c)
{
void setBeats(int newBeats, int newBars)
{
- newBeats = u::math::bound(newBeats, 1, G_MAX_BEATS);
- newBars = u::math::bound(newBars, 1, newBeats); // Bars cannot be greater than beats
+ newBeats = std::clamp(newBeats, 1, G_MAX_BEATS);
+ newBars = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats
model::onSwap(model::clock, [&](model::Clock& c)
{
kernelMidi::send(0x00, 0x00, 0x00); // mins, secs, frames 0
kernelMidi::send(MIDI_EOX, -1, -1); // end of sysex
}
+ else
+ if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
+ kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
}
/* TODO - these things should be processed by a higher level,
above clock:: ----> clockManager */
- kernelAudio::JackState jackState = kernelAudio::jackTransportQuery();
+ kernelAudio::JackState jackStateCurr = kernelAudio::jackTransportQuery();
+
+ if (jackStateCurr != jackStatePrev_) {
- if (jackState.running != jackStatePrev_.running) {
- if (jackState.running) {
- if (!isRunning())
- mh::startSequencer();
+ if (jackStateCurr.frame != jackStatePrev_.frame && jackStateCurr.frame == 0) {
+G_DEBUG("JackState received - rewind to frame 0");
+ sequencer::rewind();
}
- else {
- if (isRunning())
- mh::stopSequencer();
+
+ if (jackStateCurr.bpm != jackStatePrev_.bpm && jackStateCurr.bpm > 1.0f) { // 0 bpm if Jack does not send that info
+G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm);
+ c::main::setBpm(jackStateCurr.bpm);
}
- }
- if (jackState.bpm != jackStatePrev_.bpm)
- if (jackState.bpm > 1.0f) // 0 bpm if Jack does not send that info
- c::main::setBpm(jackState.bpm);
- if (jackState.frame == 0 && jackState.frame != jackStatePrev_.frame)
- mh::rewindSequencer();
+ if (jackStateCurr.running != jackStatePrev_.running) {
+G_DEBUG("JackState received - running=" << jackStateCurr.running);
+ jackStateCurr.running ? sequencer::start() : sequencer::stop();
+ }
+ }
- jackStatePrev_ = jackState;
+ jackStatePrev_ = jackStateCurr;
}
#endif
model::ClockLock lock(model::clock);
const model::Clock* c = model::clock.get();
-
return c->quantize > 0 && c->status == ClockStatus::RUNNING;
}
/* -------------------------------------------------------------------------- */
-int getCurrentFrame() { return currentFrame_.load(); }
-int getCurrentBeat() { return currentBeat_.load(); }
-int getQuanto() { return quanto_; }
-ClockStatus getStatus() { model::ClockLock lock(model::clock); return model::clock.get()->status; }
-int getFramesInLoop() { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; }
-int getFramesInBar() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; }
-int getFramesInBeat() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; }
-int getFramesInSeq() { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; }
-int getQuantize() { model::ClockLock lock(model::clock); return model::clock.get()->quantize; }
-float getBpm() { model::ClockLock lock(model::clock); return model::clock.get()->bpm; }
-int getBeats() { model::ClockLock lock(model::clock); return model::clock.get()->beats; }
-int getBars() { model::ClockLock lock(model::clock); return model::clock.get()->bars; }
+Frame quantize(Frame f)
+{
+ if (!canQuantize()) return f;
+ return u::math::quantize(f, quantizerStep_) % getFramesInLoop(); // No overflow
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+int getCurrentFrame() { return currentFrame_.load(); }
+int getCurrentBeat() { return currentBeat_.load(); }
+int getQuantizerStep() { return quantizerStep_; }
+ClockStatus getStatus() { model::ClockLock lock(model::clock); return model::clock.get()->status; }
+int getFramesInLoop() { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; }
+int getFramesInBar() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; }
+int getFramesInBeat() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; }
+int getFramesInSeq() { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; }
+int getQuantizerValue() { model::ClockLock lock(model::clock); return model::clock.get()->quantize; }
+float getBpm() { model::ClockLock lock(model::clock); return model::clock.get()->bpm; }
+int getBeats() { model::ClockLock lock(model::clock); return model::clock.get()->beats; }
+int getBars() { model::ClockLock lock(model::clock); return model::clock.get()->bars; }
}}}; // giada::m::clock::
void sendMIDIrewind();
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) || defined(G_OS_MAC)
void recvJackSync();
#endif
int getFramesInBeat();
int getFramesInLoop();
int getFramesInSeq();
-int getQuantize();
-int getQuanto();
+int getQuantizerValue();
+int getQuantizerStep();
ClockStatus getStatus();
/* incrCurrentFrame
-Increases current frame of a single step (+1). */
+Increases current frame by a single step (+1). */
void incrCurrentFrame();
/* quantoHasPassed
-Tells whether a quanto unit has passed yet. */
+Tells whether a quantizer unit has passed yet. */
bool quantoHasPassed();
-/* quantoHasPassed
-Whether the quantizer value is > 0 and the clock is running. */
+/* canQuantize
+Tells whether the quantizer value is > 0 and the clock is running. */
bool canQuantize();
+/* quantize
+Quantizes the global frame 'f'. */
+
+Frame quantize(Frame f);
+
void setBpm(float b);
void setBeats(int beats, int bars);
void setQuantize(int q);
conf.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap);
conf.midiSync = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync);
conf.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps);
- conf.recsStopOnChanHalt = j.value(CONF_KEY_RECS_STOP_ON_CHAN_HALT, conf.recsStopOnChanHalt);
conf.chansStopOnSeqHalt = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, conf.chansStopOnSeqHalt);
conf.treatRecsAsLoops = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, conf.treatRecsAsLoops);
conf.inputMonitorDefaultOn = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, conf.inputMonitorDefaultOn);
j[CONF_KEY_MIDI_IN_VOLUME_OUT] = conf.midiInVolumeOut;
j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = conf.midiInBeatDouble;
j[CONF_KEY_MIDI_IN_BEAT_HALF] = conf.midiInBeatHalf;
- j[CONF_KEY_RECS_STOP_ON_CHAN_HALT] = conf.recsStopOnChanHalt;
j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT] = conf.chansStopOnSeqHalt;
j[CONF_KEY_TREAT_RECS_AS_LOOPS] = conf.treatRecsAsLoops;
j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON] = conf.inputMonitorDefaultOn;
int midiSync = MIDI_SYNC_NONE;
float midiTCfps = 25.0f;
- bool recsStopOnChanHalt = false;
bool chansStopOnSeqHalt = false;
bool treatRecsAsLoops = false;
bool inputMonitorDefaultOn = false;
#define G_CONST_H
+#include <cstdint>
+
+
+/* -- debug ----------------------------------------------------------------- */
+#ifndef NDEBUG
+ #define G_DEBUG_MODE
+ #define G_DEBUG(x) std::cerr << __FILE__ << "::" << __func__ << "() - " << x << "\n";
+#else
+ #define G_DEBUG(x) do {} while (0)
+#endif
+
+
/* -- environment ----------------------------------------------------------- */
#if defined(_WIN32)
#define G_OS_WINDOWS
/* -- version --------------------------------------------------------------- */
constexpr auto G_APP_NAME = "Giada";
-constexpr auto G_VERSION_STR = "0.16.2";
+constexpr auto G_VERSION_STR = "0.16.3";
constexpr int G_VERSION_MAJOR = 0;
constexpr int G_VERSION_MINOR = 16;
-constexpr int G_VERSION_PATCH = 2;
+constexpr int G_VERSION_PATCH = 3;
constexpr auto CONF_FILENAME = "giada.conf";
/* -- GUI ------------------------------------------------------------------- */
-constexpr float G_GUI_REFRESH_RATE = 0.05;
-constexpr float G_GUI_PLUGIN_RATE = 0.05; // refresh rate for plugin GUI
+constexpr float G_GUI_REFRESH_RATE = 1 / 30.0; // 30 fps
+constexpr float G_GUI_PLUGIN_RATE = 1 / 30.0; // 30 fps
constexpr int G_GUI_FONT_SIZE_BASE = 12;
constexpr int G_GUI_INNER_MARGIN = 4;
constexpr int G_GUI_OUTER_MARGIN = 8;
/* -- MIN/MAX values -------------------------------------------------------- */
-constexpr float G_MIN_BPM = 20.0f;
-constexpr auto G_MIN_BPM_STR = "20.0";
-constexpr float G_MAX_BPM = 999.0f;
-constexpr auto G_MAX_BPM_STR = "999.0";
-constexpr int G_MAX_BEATS = 32;
-constexpr int G_MAX_BARS = 32;
-constexpr int G_MAX_QUANTIZE = 8;
-constexpr float G_MIN_DB_SCALE = 60.0f;
-constexpr int G_MIN_COLUMN_WIDTH = 140;
-constexpr float G_MAX_BOOST_DB = 20.0f;
-constexpr float G_MIN_PITCH = 0.1f;
-constexpr float G_MAX_PITCH = 4.0f;
-constexpr float G_MAX_VOLUME = 1.0f;
-constexpr int G_MAX_GRID_VAL = 64;
-constexpr int G_MIN_BUF_SIZE = 8;
-constexpr int G_MAX_BUF_SIZE = 4096;
-constexpr int G_MIN_GUI_WIDTH = 816;
-constexpr int G_MIN_GUI_HEIGHT = 510;
-constexpr int G_MAX_IO_CHANS = 2;
-constexpr int G_MAX_VELOCITY = 0x7F;
-constexpr int G_MAX_MIDI_CHANS = 16;
-constexpr int G_MAX_POLYPHONY = 32;
+constexpr float G_MIN_BPM = 20.0f;
+constexpr auto G_MIN_BPM_STR = "20.0";
+constexpr float G_MAX_BPM = 999.0f;
+constexpr auto G_MAX_BPM_STR = "999.0";
+constexpr int G_MAX_BEATS = 32;
+constexpr int G_MAX_BARS = 32;
+constexpr int G_MAX_QUANTIZE = 8;
+constexpr float G_MIN_DB_SCALE = 60.0f;
+constexpr int G_MIN_COLUMN_WIDTH = 140;
+constexpr float G_MAX_BOOST_DB = 20.0f;
+constexpr float G_MIN_PITCH = 0.1f;
+constexpr float G_MAX_PITCH = 4.0f;
+constexpr float G_MAX_PAN = 1.0f;
+constexpr float G_MAX_VOLUME = 1.0f;
+constexpr int G_MAX_GRID_VAL = 64;
+constexpr int G_MIN_BUF_SIZE = 8;
+constexpr int G_MAX_BUF_SIZE = 4096;
+constexpr int G_MIN_GUI_WIDTH = 816;
+constexpr int G_MIN_GUI_HEIGHT = 510;
+constexpr int G_MAX_IO_CHANS = 2;
+constexpr int G_MAX_VELOCITY = 0x7F;
+constexpr int G_MAX_MIDI_CHANS = 16;
+constexpr int G_MAX_POLYPHONY = 32;
+constexpr int G_MAX_QUEUE_EVENTS = 32;
+constexpr int G_MAX_QUANTIZER_SIZE = 8;
#define G_DEFAULT_SOUNDSYS G_SYS_API_CORE
#endif
-constexpr int G_DEFAULT_SOUNDDEV_OUT = 0; // FIXME - please override with rtAudio::getDefaultDevice (or similar)
-constexpr int G_DEFAULT_SOUNDDEV_IN = -1; // no recording by default: input disabled
-constexpr int G_DEFAULT_MIDI_SYSTEM = 0;
-constexpr int G_DEFAULT_MIDI_PORT_IN = -1;
-constexpr int G_DEFAULT_MIDI_PORT_OUT = -1;
-constexpr int G_DEFAULT_SAMPLERATE = 44100;
-constexpr int G_DEFAULT_BUFSIZE = 1024;
-constexpr int G_DEFAULT_BIT_DEPTH = 32; // float
-constexpr float G_DEFAULT_VOL = 1.0f;
-constexpr float G_DEFAULT_PITCH = 1.0f;
-constexpr float G_DEFAULT_BPM = 120.0f;
-constexpr int G_DEFAULT_BEATS = 4;
-constexpr int G_DEFAULT_BARS = 1;
-constexpr int G_DEFAULT_QUANTIZE = 0; // quantizer off
-constexpr float G_DEFAULT_FADEOUT_STEP = 0.01f; // micro-fadeout speed
-constexpr int G_DEFAULT_COLUMN_WIDTH = 380;
-constexpr auto G_DEFAULT_PATCH_NAME = "(default patch)";
-constexpr int G_DEFAULT_ACTION_SIZE = 8192; // frames
-constexpr int G_DEFAULT_ZOOM_RATIO = 128;
-constexpr float G_DEFAULT_REC_TRIGGER_LEVEL = -10.0f;
-constexpr int G_DEFAULT_SUBWINDOW_W = 640;
-constexpr int G_DEFAULT_SUBWINDOW_H = 480;
+constexpr int G_DEFAULT_SOUNDDEV_OUT = 0; // FIXME - please override with rtAudio::getDefaultDevice (or similar)
+constexpr int G_DEFAULT_SOUNDDEV_IN = -1; // no recording by default: input disabled
+constexpr int G_DEFAULT_MIDI_SYSTEM = 0;
+constexpr int G_DEFAULT_MIDI_PORT_IN = -1;
+constexpr int G_DEFAULT_MIDI_PORT_OUT = -1;
+constexpr int G_DEFAULT_SAMPLERATE = 44100;
+constexpr int G_DEFAULT_BUFSIZE = 1024;
+constexpr int G_DEFAULT_BIT_DEPTH = 32; // float
+constexpr float G_DEFAULT_VOL = 1.0f;
+constexpr float G_DEFAULT_PAN = 0.5f;
+constexpr float G_DEFAULT_PITCH = 1.0f;
+constexpr float G_DEFAULT_BPM = 120.0f;
+constexpr int G_DEFAULT_BEATS = 4;
+constexpr int G_DEFAULT_BARS = 1;
+constexpr int G_DEFAULT_QUANTIZE = 0; // quantizer off
+constexpr float G_DEFAULT_FADEOUT_STEP = 0.01f; // micro-fadeout speed
+constexpr int G_DEFAULT_COLUMN_WIDTH = 380;
+constexpr auto G_DEFAULT_PATCH_NAME = "(default patch)";
+constexpr int G_DEFAULT_ACTION_SIZE = 8192; // frames
+constexpr int G_DEFAULT_ZOOM_RATIO = 128;
+constexpr float G_DEFAULT_REC_TRIGGER_LEVEL = -10.0f;
+constexpr int G_DEFAULT_SUBWINDOW_W = 640;
+constexpr int G_DEFAULT_SUBWINDOW_H = 480;
+constexpr int G_DEFAULT_VST_MIDIBUFFER_SIZE = 1024; // TODO - not 100% sure about this size
Channel voices messages - controller (0xB0) is a special subset of this family:
it drives knobs, volume, faders and such. */
-#define MIDI_CONTROLLER 0xB0 << 24
-#define MIDI_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16)
+constexpr uint32_t G_MIDI_CONTROLLER = 0xB0 << 24;
+constexpr uint32_t G_MIDI_ALL_NOTES_OFF = (G_MIDI_CONTROLLER) | (0x7B << 16);
/* system common / real-time messages. Single bytes */
#define MIDI_STOP 0xFC
#define MIDI_EOX 0xF7 // end of sysex
-/* Channels */
-
-constexpr int G_MIDI_CHANS[G_MAX_MIDI_CHANS] = {
- 0x00 << 24, 0x01 << 24, 0x02 << 24, 0x03 << 24,
- 0x04 << 24, 0x05 << 24, 0x06 << 24, 0x07 << 24,
- 0x08 << 24, 0x09 << 24, 0x0A << 24, 0x0B << 24,
- 0x0C << 24, 0x0D << 24, 0x0E << 24, 0x0F << 24
-};
-
/* midi sync constants */
#define MIDI_SYNC_NONE 0x00
constexpr auto CONF_KEY_MIDI_IN_VOLUME_OUT = "midi_in_volume_out";
constexpr auto CONF_KEY_MIDI_IN_BEAT_DOUBLE = "midi_in_beat_doble";
constexpr auto CONF_KEY_MIDI_IN_BEAT_HALF = "midi_in_beat_half";
-constexpr auto CONF_KEY_RECS_STOP_ON_CHAN_HALT = "recs_stop_on_chan_halt";
constexpr auto CONF_KEY_CHANS_STOP_ON_SEQ_HALT = "chans_stop_on_seq_halt";
constexpr auto CONF_KEY_TREAT_RECS_AS_LOOPS = "treat_recs_as_loops";
constexpr auto CONF_KEY_INPUT_MONITOR_DEFAULT_ON = "input_monitor_default_on";
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/warnings.h"
#include "glue/main.h"
-#include "core/channels/channel.h"
+#include "core/model/storage.h"
#include "core/channels/channelManager.h"
#include "core/mixer.h"
#include "core/wave.h"
#include "core/const.h"
#include "core/clock.h"
#include "core/mixerHandler.h"
+#include "core/sequencer.h"
#include "core/patch.h"
#include "core/conf.h"
#include "core/waveManager.h"
patch::init();
midimap::init();
midimap::setDefault();
+
+ model::load(conf::conf);
if (!u::log::init(conf::conf.logMode))
u::log::print("[init] log init failed! Using default stdout\n");
kernelAudio::openDevice();
clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
mh::init();
+ sequencer::init();
recorder::init();
recorderHandler::init();
waveManager::init();
clock::init(conf::conf.samplerate, conf::conf.midiTCfps);
mh::init();
+ sequencer::init();
recorder::init();
#ifdef WITH_VST
pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize());
{
shutdownGUI_();
+ model::store(conf::conf);
+
if (!conf::write())
u::log::print("[init] error while saving configuration file!\n");
else
#include "glue/main.h"
#include "core/model/model.h"
#include "conf.h"
+#include "const.h"
#include "mixer.h"
#include "const.h"
#include "kernelAudio.h"
unsigned realBufsize = 0; // Real buffer size from the soundcard
int api = 0;
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
JackState jackState;
-jack_client_t* jackGetHandle()
+jack_client_t* jackGetHandle_()
{
return static_cast<jack_client_t*>(rtSystem->HACK__getJackClient());
}
/* -------------------------------------------------------------------------- */
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+
+bool JackState::operator!=(const JackState& o) const
+{
+ return !(running == o.running && bpm == o.bpm && frame == o.frame);
+}
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
bool isReady()
{
model::KernelLock lock(model::kernel);
-
return model::kernel.get()->audioReady;
}
realBufsize = conf::conf.buffersize;
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
if (api == G_SYS_API_JACK) {
conf::conf.samplerate = getFreq(conf::conf.soundDeviceOut, 0);
nullptr, // user data (unused)
&options);
- std::unique_ptr<model::Kernel> k = model::kernel.clone();
- k->audioReady = true;
- model::kernel.swap(std::move(k));
-
+ model::onSwap(model::kernel, [](model::Kernel& k) {
+ k.audioReady = true;
+ });
return 1;
}
catch (RtAudioError &e) {
/* -------------------------------------------------------------------------- */
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-
-const JackState &jackTransportQuery()
+JackState jackTransportQuery()
{
if (api != G_SYS_API_JACK)
return jackState;
- jack_position_t position;
- jack_transport_state_t ts = jack_transport_query(jackGetHandle(), &position);
- jackState.running = ts != JackTransportStopped;
- jackState.bpm = position.beats_per_minute;
- jackState.frame = position.frame;
- return jackState;
+
+ jack_position_t position;
+ jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position);
+
+ return {
+ ts != JackTransportStopped,
+ position.beats_per_minute,
+ position.frame
+ };
}
void jackStart()
{
if (api == G_SYS_API_JACK)
- jack_transport_start(jackGetHandle());
+ jack_transport_start(jackGetHandle_());
}
if (api != G_SYS_API_JACK)
return;
jack_position_t position;
- jack_transport_query(jackGetHandle(), &position);
+ jack_transport_query(jackGetHandle_(), &position);
position.frame = frame;
- jack_transport_reposition(jackGetHandle(), &position);
+ jack_transport_reposition(jackGetHandle_(), &position);
}
if (api != G_SYS_API_JACK)
return;
jack_position_t position;
- jack_transport_query(jackGetHandle(), &position);
+ jack_transport_query(jackGetHandle_(), &position);
position.valid = jack_position_bits_t::JackPositionBBT;
position.bar = 0; // no such info from Giada
position.beat = 0; // no such info from Giada
position.tick = 0; // no such info from Giada
position.beats_per_minute = bpm;
- jack_transport_reposition(jackGetHandle(), &position);
+ jack_transport_reposition(jackGetHandle_(), &position);
}
void jackStop()
{
if (api == G_SYS_API_JACK)
- jack_transport_stop(jackGetHandle());
+ jack_transport_stop(jackGetHandle_());
}
#endif // defined(__linux__) || defined(__FreeBSD__)
struct JackState
{
- bool running;
- double bpm;
- uint32_t frame;
+ bool running;
+ double bpm;
+ uint32_t frame;
+
+ bool operator!=(const JackState& o) const;
};
#endif
void jackStop();
void jackSetPosition(uint32_t frame);
void jackSetBpm(double bpm);
-const JackState &jackTransportQuery();
+JackState jackTransportQuery();
#endif
}}}; // giada::m::kernelAudio::
+
#endif
for (const midimap::Message& m : midimap::midimap.initCommands) {
if (m.value != 0x0 && m.channel != -1) {
u::log::print("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", m.channel, m.value);
- send(m.value | G_MIDI_CHANS[m.channel]);
+ MidiEvent e(m.value);
+ e.setChannel(m.channel);
+ send(e.getRaw());
}
}
}
-
} // {anonymous}
msg.push_back(getB3(data));
midiOut_->sendMessage(&msg);
- u::log::print("[KM] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
+ u::log::print("[KM::send] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]);
}
msg.push_back(b3);
midiOut_->sendMessage(&msg);
- //u::log::print("[KM] send msg=(%X %X %X)\n", b1, b2, b3);
+ u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3);
}
/* -------------------------------------------------------------------------- */
-void sendMidiLightning(uint32_t learn, const midimap::Message& m)
+void sendMidiLightning(uint32_t learnt, const midimap::Message& m)
{
// Skip lightning message if not defined in midi map
if (!midimap::isDefined(m))
{
- u::log::print("[KM] message skipped (not defined in midimap)");
+ u::log::print("[KM::sendMidiLightning] message skipped (not defined in midimap)");
return;
}
- u::log::print("[KM] learn=%#X, chan=%d, msg=%#X, offset=%d\n", learn, m.channel,
- m.value, m.offset);
+ u::log::print("[KM::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n",
+ learnt, m.channel, m.value, m.offset);
/* Isolate 'channel' from learnt message and offset it as requested by 'nn' in
the midimap configuration file. */
- uint32_t out = ((learn & 0x00FF0000) >> 16) << m.offset;
+ uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset;
/* Merge the previously prepared channel into final message, and finally send
it. */
unsigned countOutPorts() { return numOutPorts_; }
bool getStatus() { return status_; }
+
/* -------------------------------------------------------------------------- */
{
return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00);
}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-uint32_t setChannel(uint32_t iValue, int channel)
-{
- uint32_t chanMask = 0xF << 24;
- return (iValue & (~chanMask)) | (channel << 24);
-}
-
}}}; // giada::m::kernelMidi::
uint32_t getIValue(int b1, int b2, int b3);
-/* setChannel
-Changes MIDI channel number inside iValue. Returns new message with updated
-channel. */
-
-uint32_t setChannel(uint32_t iValue, int channel);
-
/* send
Sends a MIDI message 's' as uint32_t or as separate bytes. */
/* sendMidiLightning
Sends a MIDI lightning message defined by 'msg'. */
-void sendMidiLightning(uint32_t learn, const midimap::Message& msg);
+void sendMidiLightning(uint32_t learnt, const midimap::Message& msg);
/* setApi
Sets the Api in use for both in & out messages. */
#include <cassert>
#include <vector>
#include "glue/plugin.h"
-#include "glue/io.h"
-#include "glue/channel.h"
-#include "glue/main.h"
+#include "glue/events.h"
#include "utils/log.h"
#include "utils/math.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/conf.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
bool isChannelMidiInAllowed_(ID channelId, int c)
{
model::ChannelsLock l(model::channels);
- return model::get(model::channels, channelId).isMidiInAllowed(c);
+ return model::get(model::channels, channelId).midiLearner.state->isAllowed(c);
}
m::model::PluginsLock l(m::model::plugins);
for (ID id : ids) {
- m::Plugin& p = m::model::get(m::model::plugins, id);
+ const m::Plugin& p = m::model::get(m::model::plugins, id);
for (unsigned k = 0; k < p.midiInParams.size(); k++) {
if (pure != p.midiInParams.at(k))
continue;
- c::plugin::setParameter(id, k, vf, /*gui=*/false);
+ c::events::setPluginParameter(id, k, vf, /*gui=*/false);
u::log::print(" >>> [plugin %d parameter %d] (pure=0x%X, value=%d, float=%f)\n",
p.id, k, pure, midiEvent.getVelocity(), vf);
}
{
uint32_t pure = midiEvent.getRawNoVelocity();
- /* TODO - this is definitely not the best approach but it's necessary as
- you can't call actions on m::model::channels while locking on a upper
- level. Let's wait for a better async mechanism... */
+ model::ChannelsLock lock(model::channels);
- std::vector<std::function<void()>> actions;
-
- model::channels.lock();
- for (Channel* ch : model::channels) {
+ for (const Channel* c : model::channels) {
/* Do nothing on this channel if MIDI in is disabled or filtered out for
the current MIDI channel. */
- if (!ch->midiIn || !ch->isMidiInAllowed(midiEvent.getChannel()))
+ if (!c->midiLearner.state->isAllowed(midiEvent.getChannel()))
continue;
- if (pure == ch->midiInKeyPress) {
- actions.push_back([=] {
- u::log::print(" >>> keyPress, ch=%d (pure=0x%X)\n", ch->id, pure);
- c::io::keyPress(ch->id, false, false, midiEvent.getVelocity());
- });
+ if (pure == c->midiLearner.state->keyPress.load()) {
+ u::log::print(" >>> keyPress, ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::pressChannel(c->id, midiEvent.getVelocity(), Thread::MIDI);
}
- else if (pure == ch->midiInKeyRel) {
- actions.push_back([=] {
- u::log::print(" >>> keyRel ch=%d (pure=0x%X)\n", ch->id, pure);
- c::io::keyRelease(ch->id, false, false);
- });
+ else if (pure == c->midiLearner.state->keyRelease.load()) {
+ u::log::print(" >>> keyRel ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::releaseChannel(c->id, Thread::MIDI);
}
- else if (pure == ch->midiInMute) {
- actions.push_back([=] {
- u::log::print(" >>> mute ch=%d (pure=0x%X)\n", ch->id, pure);
- c::channel::toggleMute(ch->id);
- });
+ else if (pure == c->midiLearner.state->mute.load()) {
+ u::log::print(" >>> mute ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::toggleMuteChannel(c->id, Thread::MIDI);
}
- else if (pure == ch->midiInKill) {
- actions.push_back([=] {
- u::log::print(" >>> kill ch=%d (pure=0x%X)\n", ch->id, pure);
- c::channel::kill(ch->id, /*record=*/false);
- });
+ else if (pure == c->midiLearner.state->kill.load()) {
+ u::log::print(" >>> kill ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::killChannel(c->id, Thread::MIDI);
}
- else if (pure == ch->midiInArm) {
- actions.push_back([=] {
- u::log::print(" >>> arm ch=%d (pure=0x%X)\n", ch->id, pure);
- c::channel::toggleArm(ch->id);
- });
+ else if (pure == c->midiLearner.state->arm.load()) {
+ u::log::print(" >>> arm ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::toggleArmChannel(c->id, Thread::MIDI);
}
- else if (pure == ch->midiInSolo) {
- actions.push_back([=] {
- u::log::print(" >>> solo ch=%d (pure=0x%X)\n", ch->id, pure);
- c::channel::toggleSolo(ch->id);
- });
+ else if (pure == c->midiLearner.state->solo.load()) {
+ u::log::print(" >>> solo ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::toggleSoloChannel(c->id, Thread::MIDI);
}
- else if (pure == ch->midiInVolume) {
- actions.push_back([=] {
- float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME);
- u::log::print(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n",
- ch->id, pure, midiEvent.getVelocity(), vf);
- c::channel::setVolume(ch->id, vf, /*gui=*/false);
- });
+ else if (pure == c->midiLearner.state->volume.load()) {
+ float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME);
+ u::log::print(" >>> volume ch=%d (pure=0x%X, value=%d, float=%f)\n",
+ c->id, pure, midiEvent.getVelocity(), vf);
+ c::events::setChannelVolume(c->id, vf, Thread::MIDI);
}
- else {
- const SampleChannel* sch = static_cast<const SampleChannel*>(ch);
- if (pure == sch->midiInPitch) {
- actions.push_back([=] {
- float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH);
- u::log::print(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n",
- sch->id, pure, midiEvent.getVelocity(), vf);
- c::channel::setPitch(sch->id, vf);
- });
- }
- else
- if (pure == sch->midiInReadActions) {
- actions.push_back([=] {
- u::log::print(" >>> toggle read actions ch=%d (pure=0x%X)\n", sch->id, pure);
- c::channel::toggleReadingActions(sch->id);
- });
- }
+ else if (pure == c->midiLearner.state->pitch.load()) {
+ float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH);
+ u::log::print(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n",
+ c->id, pure, midiEvent.getVelocity(), vf);
+ c::events::setChannelPitch(c->id, vf, Thread::MIDI);
+ }
+ else if (pure == c->midiLearner.state->readActions.load()) {
+ u::log::print(" >>> toggle read actions ch=%d (pure=0x%X)\n", c->id, pure);
+ c::events::toggleReadActionsChannel(c->id, Thread::MIDI);
}
-#ifdef WITH_VST
+#ifdef WITH_VST
/* Process learned plugins parameters. */
- processPlugins_(ch->pluginIds, midiEvent);
-
+ processPlugins_(c->pluginIds, midiEvent);
#endif
- /* Redirect full midi message (pure + velocity) to plugins. */
- ch->receiveMidi(midiEvent.getRaw());
- }
- model::channels.unlock();
+ /* Redirect raw MIDI message (pure + velocity) to plug-ins in armed
+ channels. */
- /* Apply all the collected actions. */
- for (auto& action : actions)
- action();
+ if (c->state->armed.load() == true)
+ c::events::sendMidiToChannel(c->id, midiEvent, Thread::MIDI);
+ }
}
const model::MidiIn* midiIn = model::midiIn.get();
if (pure == midiIn->rewind) {
- mh::rewindSequencer();
+ c::events::rewindSequencer(Thread::MIDI);
u::log::print(" >>> rewind (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->startStop) {
- mh::toggleSequencer();
+ c::events::toggleSequencer(Thread::MIDI);
u::log::print(" >>> startStop (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->actionRec) {
- recManager::toggleActionRec(conf::conf.recTriggerMode);
+ c::events::toggleActionRecording();
u::log::print(" >>> actionRec (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->inputRec) {
- c::main::toggleInputRec();
+ c::events::toggleInputRecording();
u::log::print(" >>> inputRec (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->metronome) {
- m::mixer::toggleMetronome();
+ c::events::toggleMetronome();
u::log::print(" >>> metronome (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->volumeIn) {
float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME);
- c::main::setInVol(vf, /*gui=*/false);
+ c::events::setMasterInVolume(vf, Thread::MIDI);
u::log::print(" >>> input volume (master) (pure=0x%X, value=%d, float=%f)\n",
pure, midiEvent.getVelocity(), vf);
}
else if (pure == midiIn->volumeOut) {
float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME);
- c::main::setOutVol(vf, /*gui=*/false);
+ c::events::setMasterOutVolume(vf, Thread::MIDI);
u::log::print(" >>> output volume (master) (pure=0x%X, value=%d, float=%f)\n",
pure, midiEvent.getVelocity(), vf);
}
else if (pure == midiIn->beatDouble) {
- c::main::beatsMultiply();
+ c::events::multiplyBeats();
u::log::print(" >>> sequencer x2 (master) (pure=0x%X)\n", pure);
}
else if (pure == midiIn->beatHalf) {
- c::main::beatsDivide();
+ c::events::divideBeats();
u::log::print(" >>> sequencer /2 (master) (pure=0x%X)\n", pure);
}
}
uint32_t raw = e.getRawNoVelocity();
- model::onSwap(model::channels, channelId, [&](Channel& c)
+ model::onGet(model::channels, channelId, [param, raw](Channel& c)
{
switch (param) {
- case G_MIDI_IN_KEYPRESS: c.midiInKeyPress = raw; break;
- case G_MIDI_IN_KEYREL: c.midiInKeyRel = raw; break;
- case G_MIDI_IN_KILL: c.midiInKill = raw; break;
- case G_MIDI_IN_ARM: c.midiInArm = raw; break;
- case G_MIDI_IN_MUTE: c.midiInVolume = raw; break;
- case G_MIDI_IN_SOLO: c.midiInMute = raw; break;
- case G_MIDI_IN_VOLUME: c.midiInVolume = raw; break;
- case G_MIDI_IN_PITCH: static_cast<SampleChannel&>(c).midiInPitch = raw; break;
- case G_MIDI_IN_READ_ACTIONS: static_cast<SampleChannel&>(c).midiInReadActions = raw; break;
+ case G_MIDI_IN_KEYPRESS: c.midiLearner.state->keyPress.store(raw); break;
+ case G_MIDI_IN_KEYREL: c.midiLearner.state->keyRelease.store(raw); break;
+ case G_MIDI_IN_KILL: c.midiLearner.state->kill.store(raw); break;
+ case G_MIDI_IN_ARM: c.midiLearner.state->arm.store(raw); break;
+ case G_MIDI_IN_MUTE: c.midiLearner.state->mute.store(raw); break;
+ case G_MIDI_IN_SOLO: c.midiLearner.state->solo.store(raw); break;
+ case G_MIDI_IN_VOLUME: c.midiLearner.state->volume.store(raw); break;
+ case G_MIDI_IN_PITCH: c.midiLearner.state->pitch.store(raw); break;
+ case G_MIDI_IN_READ_ACTIONS: c.midiLearner.state->readActions.store(raw); break;
+ case G_MIDI_OUT_L_PLAYING: c.midiLighter.state->playing.store(raw); break;
+ case G_MIDI_OUT_L_MUTE: c.midiLighter.state->mute.store(raw); break;
+ case G_MIDI_OUT_L_SOLO: c.midiLighter.state->solo.store(raw); break;
}
});
uint32_t raw = e.getRawNoVelocity();
- model::onSwap(model::midiIn, [&](model::MidiIn& m)
+ model::onSwap(model::midiIn, [param, raw](model::MidiIn& m)
{
switch (param) {
case G_MIDI_IN_REWIND: m.rewind = raw; break;
#include <cassert>
#include "const.h"
+#include "utils/math.h"
#include "midiEvent.h"
namespace giada {
namespace m
{
-MidiEvent::MidiEvent()
- : m_status (0),
- m_channel (0),
- m_note (0),
- m_velocity(0),
- m_delta (0)
+namespace
{
-}
+constexpr int FLOAT_FACTOR = 10000;
+} // {anonymous}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
MidiEvent::MidiEvent(uint32_t raw)
- : m_status ((raw & 0xF0000000) >> 24),
- m_channel ((raw & 0x0F000000) >> 24),
- m_note ((raw & 0x00FF0000) >> 16),
- m_velocity((raw & 0x0000FF00) >> 8),
- m_delta (0) // not used
+: m_status ((raw & 0xF0000000) >> 24)
+, m_channel ((raw & 0x0F000000) >> 24)
+, m_note ((raw & 0x00FF0000) >> 16)
+, m_velocity((raw & 0x0000FF00) >> 8)
+, m_delta (0) // not used
{
}
/* -------------------------------------------------------------------------- */
+/* static_cast to avoid ambiguity with MidiEvent(float). */
MidiEvent::MidiEvent(int byte1, int byte2, int byte3)
- : MidiEvent((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00))
+: MidiEvent(static_cast<uint32_t>((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)))
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiEvent::MidiEvent(float v)
+: MidiEvent(ENVELOPE, 0, 0)
{
+ m_velocity = v * FLOAT_FACTOR;
}
}
+float MidiEvent::getVelocityFloat() const
+{
+ return m_velocity / static_cast<float>(FLOAT_FACTOR);
+}
+
+
bool MidiEvent::isNoteOnOff() const
{
return m_status == NOTE_ON || m_status == NOTE_OFF;
static const int NOTE_KILL = 0x70;
static const int ENVELOPE = 0xB0;
- MidiEvent();
+ /* MidiEvent (1)
+ Creates and empty and invalid MIDI event. */
+
+ MidiEvent() = default;
+
MidiEvent(uint32_t raw);
MidiEvent(int byte1, int byte2, int byte3);
+ /* MidiEvent (4)
+ A constructor that takes a float parameter. Useful to build ENVELOPE events
+ for automations, volume and pitch. */
+
+ MidiEvent(float v);
+
int getStatus() const;
int getChannel() const;
int getNote() const;
- int getVelocity() const;
+ int getVelocity() const;
+ float getVelocityFloat() const;
bool isNoteOnOff() const;
int getDelta() const;
std::string input = message.valueStr;
- size_t f = input.find("0x"); // check if "0x" is there
+ std::size_t f = input.find("0x"); // check if "0x" is there
if (f != std::string::npos)
input = message.valueStr.replace(f, 2, "");
#include "utils/log.h"
#include "utils/math.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/wave.h"
#include "core/kernelAudio.h"
#include "core/recorder.h"
#include "core/const.h"
#include "core/audioBuffer.h"
#include "core/action.h"
+#include "core/sequencer.h"
#include "core/mixer.h"
{
namespace
{
-struct Metronome
-{
- static constexpr Frame CLICK_SIZE = 38;
-
- float beat[CLICK_SIZE] = {
- 0.059033, 0.117240, 0.173807, 0.227943, 0.278890, 0.325936,
- 0.368423, 0.405755, 0.437413, 0.462951, 0.482013, 0.494333,
- 0.499738, 0.498153, 0.489598, 0.474195, 0.452159, 0.423798,
- 0.389509, 0.349771, 0.289883, 0.230617, 0.173194, 0.118739,
- 0.068260, 0.022631, -0.017423, -0.051339, -0.078721, -0.099345,
- -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954,
- -0.070862, -0.048844
- };
-
- float bar[CLICK_SIZE] = {
- 0.175860, 0.341914, 0.488904, 0.608633, 0.694426, 0.741500,
- 0.747229, 0.711293, 0.635697, 0.524656, 0.384362, 0.222636,
- 0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653,
- -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160,
- -0.201360, -0.067752, 0.052194, 0.151746, 0.226280, 0.273493,
- 0.293425, 0.288307, 0.262252, 0.220811, 0.170435, 0.117887,
- 0.069639, 0.031320
- };
-
- Frame tracker = 0;
- bool running = false;
- bool playBar = false;
- bool playBeat = false;
-
- void render(AudioBuffer& outBuf, bool& process, float* data, Frame f)
- {
- process = true;
- for (int i=0; i<outBuf.countChannels(); i++)
- outBuf[f][i] += data[tracker];
- if (++tracker > Metronome::CLICK_SIZE) {
- process = false;
- tracker = 0;
- }
- }
-} metronome_;
-
-/* vChanInput_
-Virtual channel for input recording. */
+/* recBuffer_
+Working buffer for audio recording. */
-AudioBuffer vChanInput_;
+AudioBuffer recBuffer_;
-/* vChanInToOut_
-Virtual channel in->out bridge (hear what you're playin). */
+/* inBuffer_
+Working buffer for input channel. */
-AudioBuffer vChanInToOut_;
+AudioBuffer inBuffer_;
/* inputTracker_
Frame position while recording. */
std::atomic<bool> processing_(false);
std::atomic<bool> active_(false);
+/* eventBuffer_
+Buffer of events sent to channels for event parsing. This is filled with Events
+coming from the two event queues.*/
+
+EventBuffer eventBuffer_;
+
/* -------------------------------------------------------------------------- */
-void computePeak_(const AudioBuffer& buf, std::atomic<float>& peak)
+bool isChannelAudible_(const Channel& c)
{
- for (int i=0; i<buf.countFrames(); i++)
- for (int j=0; j<buf.countChannels(); j++)
- if (buf[i][j] > peak)
- peak = buf[i][j];
+ if (c.getType() == ChannelType::MASTER || c.getType() == ChannelType::PREVIEW)
+ return true;
+ if (c.state->mute.load() == true)
+ return false;
+ model::MixerLock ml(model::mixer);
+ bool hasSolos = model::mixer.get()->hasSolos;
+ return !hasSolos || (hasSolos && c.state->solo.load() == true);
}
{
if (!recManager::isRecordingInput() || !kernelAudio::isInputEnabled())
return;
+
+ float inVol = mh::getInVol();
+ int framesInLoop = clock::getFramesInLoop();
- for (int i=0; i<inBuf.countFrames(); i++, inputTracker_++)
- for (int j=0; j<inBuf.countChannels(); j++) {
- if (inputTracker_ >= clock::getFramesInLoop())
- inputTracker_ = 0;
- vChanInput_[inputTracker_][j] += inBuf[i][j] * mh::getInVol(); // adding: overdub!
- }
+ for (int i = 0; i < inBuf.countFrames(); i++, inputTracker_++)
+ for (int j = 0; j < inBuf.countChannels(); j++)
+ recBuffer_[inputTracker_ % framesInLoop][j] += inBuf[i][j] * inVol; // adding: overdub!
}
/* -------------------------------------------------------------------------- */
/* processLineIn
-Computes line in peaks, plus handles "hear what you're playin'" thing. */
+Computes line in peaks, plus handles the internal working buffer for input. */
void processLineIn_(const AudioBuffer& inBuf)
{
if (!kernelAudio::isInputEnabled())
return;
- computePeak_(inBuf, peakIn);
+ peakIn.store(inBuf.getPeak());
if (signalCb_ != nullptr && u::math::linearToDB(peakIn) > conf::conf.recTriggerLevel) {
signalCb_();
signalCb_ = nullptr;
}
- /* "hear what you're playing" - process, copy and paste the input buffer onto
- the output buffer. */
+ /* Prepare the working buffer for input stream, which will be processed
+ later on by the Master Input Channel with plug-ins. */
model::MixerLock lock(model::mixer);
-
- if (model::mixer.get()->inToOut)
- for (int i=0; i<vChanInToOut_.countFrames(); i++)
- for (int j=0; j<vChanInToOut_.countChannels(); j++)
- vChanInToOut_[i][j] = inBuf[i][j] * mh::getInVol();
+ inBuffer_.copyData(inBuf, mh::getInVol());
}
/* -------------------------------------------------------------------------- */
-/* doQuantize
-Computes quantization on 'rewind' button. */
-void doQuantize_(unsigned frame)
+void fillEventBuffer_()
{
- /* Nothing to do if quantizer disabled or a quanto has not passed yet. */
+ eventBuffer_.clear();
- if (clock::getQuantize() == 0 || !clock::quantoHasPassed())
- return;
+ Event e;
+ while (UIevents.pop(e)) eventBuffer_.push_back(e);
+ while (MidiEvents.pop(e)) eventBuffer_.push_back(e);
- if (rewindWait) {
- rewindWait = false;
- clock::rewind();
- mh::rewindChannels();
- }
+#ifdef G_DEBUG_MODE
+ for (const Event& e : eventBuffer_)
+ G_DEBUG("Event type=" << (int) e.type << ", channel=" << e.action.channelId
+ << ", delta=" << e.delta << ", globalFrame=" << clock::getCurrentFrame());
+#endif
}
/* -------------------------------------------------------------------------- */
-void renderMetronome_(AudioBuffer& outBuf, Frame f)
+void processChannels_(AudioBuffer& out, AudioBuffer& in)
{
- if (!metronome_.running)
- return;
+ model::ChannelsLock lock(model::channels);
- if (clock::isOnBar() || metronome_.playBar)
- metronome_.render(outBuf, metronome_.playBar, metronome_.bar, f);
- else
- if (clock::isOnBeat() || metronome_.playBeat)
- metronome_.render(outBuf, metronome_.playBeat, metronome_.beat, f);
+ for (const Channel* c : model::channels) {
+ bool audible = isChannelAudible_(*c);
+ c->parse(eventBuffer_, audible);
+ if (c->getType() != ChannelType::MASTER) {
+ c->advance(out.countFrames());
+ c->render(&out, &in, audible);
+ }
+ }
}
/* -------------------------------------------------------------------------- */
-void parseEvents_(Frame f)
+void processSequencer_(AudioBuffer& in)
{
- mixer::FrameEvents fe = {
- .frameLocal = f,
- .frameGlobal = clock::getCurrentFrame(),
- .doQuantize = clock::getQuantize() == 0 || !clock::quantoHasPassed(),
- .onBar = clock::isOnBar(),
- .onFirstBeat = clock::isOnFirstBeat(),
- .quantoPassed = clock::quantoHasPassed(),
- .actions = recorder::getActionsOnFrame(clock::getCurrentFrame()),
- };
-
- model::ChannelsLock lock(model::channels);
-
- /* TODO - channel->parseEvents alters things in Channel (i.e. it's mutable).
- Refactoring needed ASAP. */
-
- for (Channel* ch : model::channels)
- ch->parseEvents(fe);
+ sequencer::parse(eventBuffer_);
+ if (clock::isActive()) {
+ if (clock::isRunning())
+ sequencer::run(in.countFrames());
+ lineInRec_(in);
+ }
}
/* -------------------------------------------------------------------------- */
-void render_(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut)
+void renderMasterIn_(AudioBuffer& in)
{
- bool running = clock::isRunning();
-
model::ChannelsLock lock(model::channels);
-
- /* TODO - channel->render alters things in Channel (i.e. it's mutable).
- Refactoring needed ASAP. */
-
- for (const Channel* ch : model::channels) {
- if (ch == nullptr ||
- ch->id == mixer::MASTER_OUT_CHANNEL_ID ||
- ch->id == mixer::MASTER_IN_CHANNEL_ID)
- continue;
- const_cast<Channel*>(ch)->render(out, in, inToOut, isChannelAudible(ch), running);
- }
-
- assert(model::channels.size() >= 3); // Preview channel included
-
- /* Master channels are processed at the end, when the buffers have already
- been filled. */
-
- model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).render(out, in, inToOut, true, true);
- model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).render(out, in, inToOut, true, true);
+ model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).render(nullptr, &in, true);
}
-
-/* -------------------------------------------------------------------------- */
-
-
-void processSequencer_(AudioBuffer& out, const AudioBuffer& in)
+void renderMasterOut_(AudioBuffer& out)
{
- for (int j=0; j<out.countFrames(); j++) {
- if (clock::isRunning()) {
- parseEvents_(j);
- doQuantize_(j);
- }
- clock::sendMIDIsync();
- clock::incrCurrentFrame();
- renderMetronome_(out, j);
- }
- lineInRec_(in);
+ model::ChannelsLock lock(model::channels);
+ model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).render(&out, nullptr, true);
}
void prepareBuffers_(AudioBuffer& outBuf)
{
outBuf.clear();
- vChanInToOut_.clear();
+ inBuffer_.clear();
}
/* -------------------------------------------------------------------------- */
-/* limitOutput
+/* limit_
Applies a very dumb hard limiter. */
-void limitOutput_(AudioBuffer& outBuf)
+void limit_(AudioBuffer& outBuf)
{
- if (!conf::conf.limitOutput)
- return;
for (int i=0; i<outBuf.countFrames(); i++)
for (int j=0; j<outBuf.countChannels(); j++)
- if (outBuf[i][j] > 1.0f) outBuf[i][j] = 1.0f;
- else if (outBuf[i][j] < -1.0f) outBuf[i][j] = -1.0f;
+ outBuf[i][j] = std::max(-1.0f, std::min(outBuf[i][j], 1.0f));
}
/* finalizeOutput
Last touches after the output has been rendered: apply inToOut if any, apply
-output volume. */
+output volume, compute peak. */
void finalizeOutput_(AudioBuffer& outBuf)
{
- model::MixerLock lock(model::mixer);
-
- //printf("%f\n", mh::getOutVol());
+ bool inToOut = mh::getInToOut();
+ float outVol = mh::getOutVol();
- for (int i=0; i<outBuf.countFrames(); i++)
- for (int j=0; j<outBuf.countChannels(); j++) {
- if (model::mixer.get()->inToOut) // Merge vChanInToOut_, if enabled
- outBuf[i][j] += vChanInToOut_[i][j];
- outBuf[i][j] *= mh::getOutVol();
- }
+ if (inToOut)
+ outBuf.addData(inBuffer_, outVol);
+ else
+ outBuf.applyGain(outVol);
+
+ if (conf::conf.limitOutput)
+ limit_(outBuf);
+
+ peakOut.store(outBuf.getPeak());
}
}; // {anonymous}
/* -------------------------------------------------------------------------- */
-std::atomic<bool> rewindWait(false);
std::atomic<float> peakOut(0.0);
std::atomic<float> peakIn(0.0);
+Queue<Event, G_MAX_QUEUE_EVENTS> UIevents;
+Queue<Event, G_MAX_QUEUE_EVENTS> MidiEvents;
+
/* -------------------------------------------------------------------------- */
void init(Frame framesInSeq, Frame framesInBuffer)
{
- /* Allocate virtual inputs. vChanInput_ has variable size: it depends
+ /* Allocate virtual inputs. recBuffer_ has variable size: it depends
on how many frames there are in sequencer. */
- vChanInput_.alloc(framesInSeq, G_MAX_IO_CHANS);
- vChanInToOut_.alloc(framesInBuffer, G_MAX_IO_CHANS);
+ recBuffer_.alloc(framesInSeq, G_MAX_IO_CHANS);
+ inBuffer_.alloc(framesInBuffer, G_MAX_IO_CHANS);
u::log::print("[mixer::init] buffers ready - framesInSeq=%d, framesInBuffer=%d\n",
- framesInSeq, framesInBuffer);
-
- clock::rewind();
+ framesInSeq, framesInBuffer);
}
/* -------------------------------------------------------------------------- */
-void allocVirtualInput(Frame frames)
+void allocRecBuffer(Frame frames)
{
- vChanInput_.alloc(frames, G_MAX_IO_CHANS);
+ recBuffer_.alloc(frames, G_MAX_IO_CHANS);
}
-void clearVirtualInput()
+void clearRecBuffer()
{
- vChanInput_.clear();
+ recBuffer_.clear();
}
-const AudioBuffer& getVirtualInput()
+const AudioBuffer& getRecBuffer()
{
- return vChanInput_;
+ return recBuffer_;
}
processing_.store(true);
-#if defined(__linux__) || defined(__FreeBSD__)
-
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
if (kernelAudio::getAPI() == G_SYS_API_JACK)
clock::recvJackSync();
-
#endif
AudioBuffer out, in;
prepareBuffers_(out);
processLineIn_(in);
- /* Process model. */
-
//out[0][0] = 3.0f;
- if (clock::isActive())
- processSequencer_(out, in);
- render_(out, in, vChanInToOut_);
+ renderMasterIn_(inBuffer_);
+
+ fillEventBuffer_();
+ processSequencer_(inBuffer_);
+ processChannels_(out, inBuffer_);
+
+ renderMasterOut_(out);
+
+ /* Advance sequencer only when rendering is done. */
+
+ if (clock::isActive())
+ sequencer::advance(out);
/* Post processing. */
finalizeOutput_(out);
- limitOutput_(out);
- computePeak_(out, peakOut);
/* Unset data in buffers. If you don't do this, buffers go out of scope and
destroy memory allocated by RtAudio ---> havoc. */
+
out.setData(nullptr, 0, 0);
in.setData (nullptr, 0, 0);
/* -------------------------------------------------------------------------- */
-bool isChannelAudible(const Channel* ch)
-{
- model::MixerLock l(model::mixer);
-
- bool hasSolos = model::mixer.get()->hasSolos;
- return !hasSolos || (hasSolos && ch->solo);
-}
-
-bool isMetronomeOn() { return metronome_.running; }
-
-
-/* -------------------------------------------------------------------------- */
-
-
void startInputRec()
{
/* Start inputTracker_ from the current frame, not the beginning. */
/* -------------------------------------------------------------------------- */
-void toggleMetronome()
+void setSignalCallback(std::function<void()> f)
{
- metronome_.running = !metronome_.running;
-}
-
-
-void setMetronome(bool v)
-{
- metronome_.running = v;
+ signalCb_ = f;
}
/* -------------------------------------------------------------------------- */
-void setSignalCallback(std::function<void()> f)
+void pumpEvent(Event e)
{
- signalCb_ = f;
+ eventBuffer_.push_back(e);
}
}}}; // giada::m::mixer::
#include <functional>
#include <vector>
#include "deps/rtaudio/RtAudio.h"
+#include "core/ringBuffer.h"
#include "core/recorder.h"
#include "core/types.h"
+#include "core/queue.h"
+#include "core/midiEvent.h"
namespace giada {
{
struct Action;
class Channel;
+class Channel;
class AudioBuffer;
namespace mixer
{
-struct FrameEvents
+enum class EventType
+{
+ KEY_PRESS,
+ KEY_RELEASE,
+ KEY_KILL,
+ SEQUENCER_FIRST_BEAT, // 3
+ SEQUENCER_BAR, // 4
+ SEQUENCER_START, // 5
+ SEQUENCER_STOP, // 6
+ SEQUENCER_REWIND, // 7
+ SEQUENCER_REWIND_REQ, // 8
+ MIDI,
+ ACTION,
+ CHANNEL_TOGGLE_READ_ACTIONS,
+ CHANNEL_TOGGLE_ARM,
+ CHANNEL_MUTE,
+ CHANNEL_SOLO,
+ CHANNEL_VOLUME,
+ CHANNEL_PITCH,
+ CHANNEL_PAN
+};
+
+struct Event
{
- Frame frameLocal;
- Frame frameGlobal;
- bool doQuantize;
- bool onBar;
- bool onFirstBeat;
- bool quantoPassed;
- const std::vector<Action>* actions;
+ EventType type;
+ Frame delta;
+ Action action;
};
+/* EventBuffer
+Alias for a RingBuffer containing events to be sent to engine. The double size
+is due to the presence of two distinct Queues for collecting events coming from
+other threads. See below. */
+
+using EventBuffer = RingBuffer<Event, G_MAX_QUEUE_EVENTS * 2>;
+
constexpr int MASTER_OUT_CHANNEL_ID = 1;
constexpr int MASTER_IN_CHANNEL_ID = 2;
constexpr int PREVIEW_CHANNEL_ID = 3;
-extern std::atomic<bool> rewindWait; // rewind guard, if quantized
-extern std::atomic<float> peakOut;
-extern std::atomic<float> peakIn;
+extern std::atomic<float> peakOut; // TODO - move to model::
+extern std::atomic<float> peakIn; // TODO - move to model::
+
+/* Channel Event queues
+Collect events coming from the UI or MIDI devices to be sent to channels. Our
+poor's man Queue is a single-producer/single-consumer one, so we need two queues
+for two writers. TODO - let's add a multi-producer queue sooner or later! */
+
+extern Queue<Event, G_MAX_QUEUE_EVENTS> UIevents;
+extern Queue<Event, G_MAX_QUEUE_EVENTS> MidiEvents;
void init(Frame framesInSeq, Frame framesInBuffer);
void enable();
void disable();
-/* allocVirtualInput
+/* allocRecBuffer
Allocates new memory for the virtual input channel. Call this whenever you
shrink or resize the sequencer. */
-void allocVirtualInput(Frame frames);
+void allocRecBuffer(Frame frames);
-/* clearVirtualInput
+/* clearRecBuffer
Clears internal virtual channel. */
-void clearVirtualInput();
+void clearRecBuffer();
-/* getVirtualInput
+/* getRecBuffer
Returns a read-only reference to the internal virtual channel. Use this to
merge data into channel after an input recording session. */
-const AudioBuffer& getVirtualInput();
+const AudioBuffer& getRecBuffer();
void close();
int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize, double streamTime,
RtAudioStreamStatus status, void* userData);
-bool isChannelAudible(const Channel* ch);
-
/* startInputRec, stopInputRec
Starts/stops input recording on frame clock::getCurrentFrame(). */
void startInputRec();
void stopInputRec();
-void toggleMetronome();
-bool isMetronomeOn();
-void setMetronome(bool v);
-
void setSignalCallback(std::function<void()> f);
+
+/* pumpEvent
+Pumps a new mixer::Event into the event vector. Use this function when you want
+to inject a new event for the **current** block. Push the event in the two
+queues UIevents and MIDIevents above if the event can be processed in the next
+block instead. */
+
+void pumpEvent(Event e);
}}} // giada::m::mixer::;
#include "glue/main.h"
#include "glue/channel.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/channels/channelManager.h"
#include "core/kernelMidi.h"
#include "core/mixer.h"
ch->id = channelId;
}
- return ch;
+ return ch;
}
/* -------------------------------------------------------------------------- */
-bool channelHas_(std::function<bool(const Channel*)> f)
+bool anyChannel_(std::function<bool(const Channel*)> f)
{
model::ChannelsLock lock(model::channels);
return std::any_of(model::channels.begin(), model::channels.end(), f);
/* -------------------------------------------------------------------------- */
-bool canInputRec_(size_t chanIndex)
+template <typename F>
+std::vector<ID> getChannelsIf_(F f)
{
model::ChannelsLock l(model::channels);
- return model::channels.get(chanIndex)->canInputRec();
+
+ std::vector<ID> ids;
+ for (const Channel* c : model::channels)
+ if (f(c)) ids.push_back(c->id);
+
+ return ids;
+}
+
+
+std::vector<ID> getChannelsWithWave_()
+{
+ return getChannelsIf_([] (const Channel* c)
+ {
+ return c->samplePlayer && c->samplePlayer->hasWave();
+ });
+}
+
+
+std::vector<ID> getRecordableChannels_()
+{
+ return getChannelsIf_([] (const Channel* c)
+ {
+ return c->canInputRec();
+ });
}
/* -------------------------------------------------------------------------- */
/* pushWave_
-Pushes a new wave into Sample Channel 'ch' and into the corresponding Wave list.
+Pushes a new wave into Channel 'ch' and into the corresponding Wave list.
Use this when modifying a local model, before swapping it. */
-void pushWave_(SampleChannel& ch, std::unique_ptr<Wave>&& w, bool clone)
+void pushWave_(Channel& ch, std::unique_ptr<Wave>&& w)
{
- if (ch.hasWave && !clone) // Don't pop if cloning a channel
- model::waves.pop(model::getIndex(model::waves, ch.waveId));
-
- ID id = w->id;
- Frame size = w->getSize();
+ assert(ch.getType() == ChannelType::SAMPLE);
model::waves.push(std::move(w));
- ch.pushWave(id, size);
+
+ model::WavesLock l(model::waves);
+ ch.samplePlayer->loadWave(model::waves.back());
}
}; // {anonymous}
/* -------------------------------------------------------------------------- */
-bool uniqueSamplePath(ID channelToSkip, const std::string& path)
-{
- model::ChannelsLock cl(model::channels);
- model::WavesLock wl(model::waves);
-
- for (const Channel* c : model::channels) {
- if (c->id == channelToSkip || c->type != ChannelType::SAMPLE)
- continue;
- const SampleChannel* sc = static_cast<const SampleChannel*>(c);
- if (sc->hasWave && model::get(model::waves, sc->waveId).getPath() == path)
- return false;
- }
- return true;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-ID addChannel(ChannelType type, ID columnId)
+void addChannel(ChannelType type, ID columnId)
{
- std::unique_ptr<Channel> c = createChannel_(type, columnId);
- ID id = c->id;
- model::channels.push(std::move(c));
- return id;
+ model::channels.push(std::move(createChannel_(type, columnId)));
}
if (res.status != G_RES_OK)
return res.status;
+ ID oldWaveId;
+
model::onSwap(model::channels, channelId, [&](Channel& c)
{
- pushWave_(static_cast<SampleChannel&>(c), std::move(res.wave), /*clone=*/false);
+ oldWaveId = c.samplePlayer->getWaveId();
+ pushWave_(c, std::move(res.wave));
});
+ /* Remove old wave, if any. It is safe to do it now: the channel already
+ points to the new one. */
+
+ if (oldWaveId != 0)
+ model::waves.pop(model::getIndex(model::waves, oldWaveId));
+
return res.status;
}
void addAndLoadChannel(ID columnId, std::unique_ptr<Wave>&& w)
{
- std::unique_ptr<Channel> ch = createChannel_(ChannelType::SAMPLE,
- columnId);
+ std::unique_ptr<Channel> ch = createChannel_(ChannelType::SAMPLE, columnId);
- pushWave_(static_cast<SampleChannel&>(*ch.get()), std::move(w), /*clone=*/false);
+ pushWave_(*ch.get(), std::move(w));
/* Then add new channel to Channel list. */
/* Clone plugins, actions and wave first in their own lists. */
#ifdef WITH_VST
- pluginHost::clonePlugins(oldChannel, *newChannel.get());
+ newChannel->pluginIds = pluginHost::clonePlugins(oldChannel.pluginIds);
#endif
recorderHandler::cloneActions(channelId, newChannel->id);
- if (newChannel->hasData()) {
- SampleChannel* sch = static_cast<SampleChannel*>(newChannel.get());
- Wave& wave = model::get(model::waves, sch->waveId);
- pushWave_(*sch, waveManager::createFromWave(wave, 0, wave.getSize()), /*clone=*/true);
+ if (newChannel->samplePlayer && newChannel->samplePlayer->hasWave())
+ {
+ Wave& wave = model::get(model::waves, newChannel->samplePlayer->getWaveId());
+ pushWave_(*newChannel, waveManager::createFromWave(wave, 0, wave.getSize()));
}
/* Then push the new channel in the channels list. */
void freeChannel(ID channelId)
{
- bool hasWave;
- ID waveId;
+ ID waveId;
/* Remove Wave reference from Channel. */
model::onSwap(model::channels, channelId, [&](Channel& c)
{
- SampleChannel& sc = static_cast<SampleChannel&>(c);
- hasWave = sc.hasWave;
- waveId = sc.waveId;
- sc.empty();
+ waveId = c.samplePlayer->getWaveId();
+ c.samplePlayer->loadWave(nullptr);
});
/* Then remove the actual Wave, if any. */
- if (hasWave)
+ if (waveId != 0)
model::waves.pop(model::getIndex(model::waves, waveId));
}
void freeAllChannels()
{
- for (size_t i = 0; i < model::channels.size(); i++)
- model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.empty(); });
+ for (ID id : getChannelsWithWave_()) {
+ model::onSwap(model::channels, id, [](Channel& c)
+ {
+ c.samplePlayer->loadWave(nullptr);
+ });
+ }
+
model::waves.clear();
}
void deleteChannel(ID channelId)
{
- bool hasWave = false;
ID waveId;
#ifdef WITH_VST
std::vector<ID> pluginIds;
#endif
- model::onGet(model::channels, channelId, [&](Channel& c)
+ model::onGet(model::channels, channelId, [&](const Channel& c)
{
#ifdef WITH_VST
pluginIds = c.pluginIds;
#endif
- if (c.type != ChannelType::SAMPLE)
- return;
- SampleChannel& sc = static_cast<SampleChannel&>(c);
- hasWave = sc.hasWave;
- waveId = sc.waveId;
+ waveId = c.samplePlayer ? c.samplePlayer->getWaveId() : 0;
});
model::channels.pop(model::getIndex(model::channels, channelId));
- if (hasWave)
+ if (waveId != 0)
model::waves.pop(model::getIndex(model::waves, waveId));
#ifdef WITH_VST
void renameChannel(ID channelId, const std::string& name)
{
- model::onSwap(model::channels, channelId, [&](Channel& c) { c.name = name; });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startSequencer()
-{
- switch (clock::getStatus()) {
- case ClockStatus::STOPPED:
- clock::setStatus(ClockStatus::RUNNING);
- break;
- case ClockStatus::WAITING:
- clock::setStatus(ClockStatus::RUNNING);
- recManager::stopActionRec();
- break;
- default:
- break;
- }
-
-#ifdef __linux__
- kernelAudio::jackStart();
-#endif
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopSequencer()
-{
- clock::setStatus(ClockStatus::STOPPED);
-
- /* Stop channels with explicit locks. The RAII version would trigger a
- deadlock if recManager::stopInputRec() is called down below. */
-
- model::channels.lock();
- for (Channel* c : model::channels)
- c->stopBySeq(conf::conf.chansStopOnSeqHalt);
- model::channels.unlock();
-
-#ifdef __linux__
- kernelAudio::jackStop();
-#endif
-
- /* If recordings (both input and action) are active deactivate them, but
- store the takes. RecManager takes care of it. */
-
- if (recManager::isRecordingAction())
- recManager::stopActionRec();
- else
- if (recManager::isRecordingInput())
- recManager::stopInputRec();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleSequencer()
-{
- clock::isRunning() ? stopSequencer() : startSequencer();
+ model::onGet(model::channels, channelId, [&](Channel& c)
+ {
+ c.state->name = name;
+ }, /*rebuild=*/true);
}
void updateSoloCount()
{
- model::onSwap(model::mixer, [&](model::Mixer& m)
+ model::onSwap(model::mixer, [](model::Mixer& m)
{
- m.hasSolos = channelHas_([](const Channel* ch) { return ch->solo; });
+ m.hasSolos = anyChannel_([](const Channel* ch) {
+ return !ch->isInternal() && ch->state->solo.load() == true;
+ });
});
}
/* -------------------------------------------------------------------------- */
-void setInVol(float v)
-{
- model::onGet(model::channels, mixer::MASTER_IN_CHANNEL_ID, [&](Channel& c)
- {
- c.volume = v;
- });
-}
-
-
-void setOutVol(float v)
-{
- model::onGet(model::channels, mixer::MASTER_OUT_CHANNEL_ID, [&](Channel& c)
- {
- c.volume = v;
- });
-}
-
-
void setInToOut(bool v)
{
model::onSwap(model::mixer, [&](model::Mixer& m)
float getInVol()
{
model::ChannelsLock l(model::channels);
- return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).volume;
+ return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).state->volume.load();
}
float getOutVol()
{
model::ChannelsLock l(model::channels);
- return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).volume;
+ return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).state->volume.load();
}
/* -------------------------------------------------------------------------- */
-
-void rewindSequencer()
-{
- if (clock::getQuantize() > 0 && clock::isRunning()) // quantize rewind
- mixer::rewindWait = true;
- else {
- clock::rewind();
- rewindChannels();
- }
-
- /* FIXME - potential desync when Quantizer is enabled from this point on.
- Mixer would wait, while the following calls would be made regardless of its
- state. */
-
-#ifdef __linux__
- kernelAudio::jackSetPosition(0);
-#endif
-
- if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M)
- kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindChannels()
-{
- for (size_t i = 3; i < model::channels.size(); i++)
- model::onSwap(model::channels, model::getId(model::channels, i), [&](Channel& c) { c.rewindBySeq(); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
/* Push a new Wave into each recordable channel. Warning: this algorithm will
require some changes when we will allow overdubbing (the previous existing Wave
has to be overwritten somehow). */
void finalizeInputRec()
{
- const AudioBuffer& virtualInput = mixer::getVirtualInput();
-
- /* Can't loop with foreach, as it would require a lock on model::channels
- list which would deadlock during the model::channels::swap() call below.
- Also skip channels 0, 1 and 2: they are MASTER_IN, MASTER_OUT and PREVIEW. */
-
- for (size_t i = 3; i < model::channels.size(); i++) {
-
- if (!canInputRec_(i))
- continue;
+ for (ID id : getRecordableChannels_()) {
/* Create a new Wave with audio coming from Mixer's virtual input. */
std::unique_ptr<Wave> wave = waveManager::createEmpty(clock::getFramesInLoop(),
G_MAX_IO_CHANS, conf::conf.samplerate, filename);
- wave->copyData(virtualInput[0], virtualInput.countFrames());
+ wave->copyData(mixer::getRecBuffer());
/* Update Channel with the new Wave. The function pushWave_ will take
- take of pushing it into the stack first. Also start all channels in
+ care of pushing it into the stack first. Also start all channels in
LOOP mode. */
- model::onSwap(model::channels, model::getId(model::channels, i), [&](Channel& c)
+ model::onSwap(model::channels, id, [&](Channel& c)
{
- SampleChannel& sc = static_cast<SampleChannel&>(c);
- pushWave_(sc, std::move(wave), /*clone=*/false);
- if (sc.isAnyLoopMode())
- sc.playStatus = ChannelStatus::PLAY;
- });
+ pushWave_(c, std::move(wave));
+ if (c.samplePlayer->state->isAnyLoopMode())
+ c.samplePlayer->kickIn(clock::getCurrentFrame());
+ });
}
- mixer::clearVirtualInput();
+ mixer::clearRecBuffer();
}
bool hasRecordableSampleChannels()
{
- return channelHas_([](const Channel* ch) { return ch->canInputRec(); });
+ return anyChannel_([](const Channel* ch) { return ch->canInputRec(); });
}
bool hasLogicalSamples()
{
- return channelHas_([](const Channel* ch) { return ch->hasLogicalData(); });
+ return anyChannel_([](const Channel* ch)
+ {
+ return ch->samplePlayer && ch->samplePlayer->hasLogicalWave(); }
+ );
}
bool hasEditedSamples()
{
- return channelHas_([](const Channel* ch) { return ch->hasEditedData(); });
+ return anyChannel_([](const Channel* ch)
+ {
+ return ch->samplePlayer && ch->samplePlayer->hasEditedWave();
+ });
}
bool hasActions()
{
- return channelHas_([](const Channel* ch) { return ch->hasActions; });
+ return anyChannel_([](const Channel* ch) { return ch->state->hasActions; });
}
bool hasAudioData()
{
- return channelHas_([](const Channel* ch) { return ch->hasData(); });
+ return anyChannel_([](const Channel* ch)
+ {
+ return ch->samplePlayer && ch->samplePlayer->hasWave();
+ });
}
}}}; // giada::m::mh::
Adds a new channel of type 'type' into the channels stack. Returns the new
channel ID. */
-ID addChannel(ChannelType type, ID columnId);
+void addChannel(ChannelType type, ID columnId);
/* loadChannel
Loads a new Wave inside a Sample Channel. */
void renameChannel(ID channelId, const std::string& name);
void freeAllChannels();
-void startSequencer();
-void stopSequencer();
-void toggleSequencer();
-void rewindSequencer();
-void rewindChannels();
-
void setInToOut(bool v);
-void setInVol(float f);
-void setOutVol(float f);
/* updateSoloCount
Updates the number of solo-ed channels in mixer. */
void finalizeInputRec();
-/* uniqueSamplePath
-Returns true if path 'p' is unique. Requires SampleChannel 'skip' in order
-to skip check against itself. */
-
-bool uniqueSamplePath(ID channelToSkip, const std::string& p);
-
/* hasLogicalSamples
True if 1 or more samples are logical (memory only, such as takes) */
#include <cassert>
#include "core/model/model.h"
-#ifndef NDEBUG
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
+#ifdef G_DEBUG_MODE
#include "core/channels/channelManager.h"
#endif
RCUList<Recorder> recorder(std::make_unique<Recorder>());
RCUList<MidiIn> midiIn(std::make_unique<MidiIn>());
RCUList<Actions> actions(std::make_unique<Actions>());
-RCUList<Channel> channels;
+RCUList<Channel> channels;
RCUList<Wave> waves;
#ifdef WITH_VST
RCUList<Plugin> plugins;
}
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
void debug()
{
int i = 0;
for (const Channel* c : channels) {
- printf(" %d) %p - ID=%d name='%s' columnID=%d\n", i++, (void*)c, c->id, c->name.c_str(), c->columnId);
+ printf("\t%d) %p - ID=%d name='%s' type=%d columnID=%d\n",
+ i++, (void*) c, c->state->id, c->state->name.c_str(), (int) c->getType(), c->getColumnId());
+/*
if (c->hasData())
- printf(" wave: ID=%d\n", static_cast<const SampleChannel*>(c)->waveId);
+ printf("\t\twave: ID=%d\n", static_cast<const SampleChannel*>(c)->waveId);
+*/
#ifdef WITH_VST
if (c->pluginIds.size() > 0) {
- puts(" plugins:");
+ puts("\t\tplugins:");
for (ID id : c->pluginIds)
- printf(" ID=%d\n", id);
+ printf("\t\t\tID=%d\n", id);
}
#endif
}
i = 0;
for (const Wave* w : waves)
- printf(" %d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str());
+ printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str());
#ifdef WITH_VST
puts("model::plugins");
i = 0;
for (const Plugin* p : plugins) {
if (p->valid)
- printf(" %d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str());
+ printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str());
else
- printf(" %d) %p - ID=%d INVALID\n", i++, (void*)p, p->id);
+ printf("\t%d) %p - ID=%d INVALID\n", i++, (void*)p, p->id);
}
#endif
puts("model::clock");
- printf(" clock.status = %d\n", static_cast<int>(clock.get()->status));
- printf(" clock.bars = %d\n", clock.get()->bars);
- printf(" clock.beats = %d\n", clock.get()->beats);
- printf(" clock.bpm = %f\n", clock.get()->bpm);
- printf(" clock.quantize = %d\n", clock.get()->quantize);
+ printf("\tclock.status = %d\n", static_cast<int>(clock.get()->status));
+ printf("\tclock.bars = %d\n", clock.get()->bars);
+ printf("\tclock.beats = %d\n", clock.get()->beats);
+ printf("\tclock.bpm = %f\n", clock.get()->bpm);
+ printf("\tclock.quantize = %d\n", clock.get()->quantize);
puts("model::actions");
for (auto& kv : actions.get()->map) {
- printf(" frame: %d\n", kv.first);
+ printf("\tframe: %d\n", kv.first);
for (const Action& a : kv.second)
- printf(" (%p) - ID=%d, frame=%d, channel=%d, value=0x%X, prevId=%d, prev=%p, nextId=%d, next=%p\n",
+ printf("\t\t(%p) - ID=%d, frame=%d, channel=%d, value=0x%X, prevId=%d, prev=%p, nextId=%d, next=%p\n",
(void*) &a, a.id, a.frame, a.channelId, a.event.getRaw(), a.prevId, (void*) a.prev, a.nextId, (void*) a.next);
}
puts("===============================");
}
-#endif
+#endif // G_DEBUG_MODE
}}} // giada::m::model::
#include <algorithm>
-#include <type_traits>
+#include "core/model/traits.h"
#include "core/channels/channel.h"
+#include "core/channels/state.h"
#include "core/const.h"
#include "core/wave.h"
#include "core/plugin.h"
namespace m {
namespace model
{
+namespace
+{
+/* getIter_
+Returns an iterator of an element from list 'list' with given ID. */
+
+template<typename L>
+auto getIter_(L& list, ID id)
+{
+ static_assert(has_id<typename L::value_type>(), "This type has no ID");
+ auto it = std::find_if(list.begin(), list.end(), [&](auto* t)
+ {
+ return t->id == id;
+ });
+ assert(it != list.end());
+ return it;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* onSwapByIndex_
+Swaps i-th element from list with a new one and applies a function f to it. */
+
+template<typename L>
+void onSwapByIndex_(L& list, std::size_t i, std::function<void(typename L::value_type&)> f)
+{
+ std::unique_ptr<typename L::value_type> o = list.clone(i);
+ f(*o.get());
+ list.swap(std::move(o), i);
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
struct Clock
{
ClockStatus status = ClockStatus::STOPPED;
#endif
-/* ---------------------------------------------------------------------------*/
-
-
-template <typename T> struct has_id : std::false_type {};
-template <> struct has_id<Channel> : std::true_type {};
-template <> struct has_id<Wave> : std::true_type {};
-#ifdef WITH_VST
-template <> struct has_id<Plugin> : std::true_type {};
-#endif
-
-template <typename T> struct is_copyable : std::true_type {};
-template <> struct is_copyable<Channel> : std::false_type {};
-
-
/* -------------------------------------------------------------------------- */
template<typename L>
-auto getIter(L& list, ID id)
+bool exists(L& list, ID id)
{
- static_assert(has_id<typename L::value_type>(), "This type has no ID");
+ static_assert(has_id<typename L::value_type>(), "This type has no ID");
+ typename L::Lock l(list);
auto it = std::find_if(list.begin(), list.end(), [&](auto* t)
{
return t->id == id;
});
- assert(it != list.end());
- return it;
+ return it != list.end();
}
/* -------------------------------------------------------------------------- */
+/* getIndex (thread safe)
+Returns the index of element with ID from a list. */
+
template<typename L>
-size_t getIndex(L& list, ID id)
+std::size_t getIndex(L& list, ID id)
{
static_assert(has_id<typename L::value_type>(), "This type has no ID");
typename L::Lock l(list);
- return std::distance(list.begin(), getIter(list, id));
+ return std::distance(list.begin(), getIter_(list, id));
}
/* -------------------------------------------------------------------------- */
+/* getIndex (thread safe)
+Returns the element ID of the i-th element of a list. */
+
template<typename L>
-ID getId(L& list, size_t i)
+ID getId(L& list, std::size_t i)
{
static_assert(has_id<typename L::value_type>(), "This type has no ID");
typename L::Lock l(list);
typename L::value_type& get(L& list, ID id)
{
static_assert(has_id<typename L::value_type>(), "This type has no ID");
- return **getIter(list, id);
+ return **getIter_(list, id);
}
/* -------------------------------------------------------------------------- */
-/* onGet (1)
+/* onGet (1) (thread safe)
Utility function for reading ID-based things from a RCUList. */
template<typename L>
-void onGet(L& list, ID id, std::function<void(typename L::value_type&)> f)
+void onGet(L& list, ID id, std::function<void(typename L::value_type&)> f, bool rebuild=false)
{
static_assert(has_id<typename L::value_type>(), "This type has no ID");
typename L::Lock l(list);
- f(**getIter(list, id));
+ f(**getIter_(list, id));
+ if (rebuild)
+ list.changed.store(true);
}
-/* onGet (2)
+/* onGet (2) (thread safe)
Same as (1), for non-ID-based things. */
template<typename L>
/* ---------------------------------------------------------------------------*/
-template<typename L>
-void onSwapByIndex_(L& list, size_t i, std::function<void(typename L::value_type&)> f)
-{
- std::unique_ptr<typename L::value_type> o = list.clone(i);
- f(*o.get());
- list.swap(std::move(o), i);
-}
-
-/* onSwapById_ (1)
-Regular version for copyable types. */
-
-template<typename L>
-void onSwapById_(L& list, ID id, std::function<void(typename L::value_type&)> f,
- const std::true_type& /*is_copyable=true*/)
-{
- static_assert(has_id<typename L::value_type>(), "This type has no ID");
- onSwapByIndex_(list, getIndex(list, id), f);
-}
-
-
-/* onSwapById_ (2)
-Custom version for non-copyable types, e.g. Channel types. Let's wait for the
-no-virtual channel refactoring... */
-
-template<typename L>
-void onSwapById_(L& list, ID id, std::function<void(typename L::value_type&)> f,
- const std::false_type& /*is_copyable=false*/)
-{
- static_assert(has_id<typename L::value_type>(), "This type has no ID");
-
- size_t i = getIndex(list, id);
-
- list.lock();
- std::unique_ptr<typename L::value_type> o(list.get(i)->clone());
- list.unlock();
-
- f(*o.get());
-
- channels.swap(std::move(o), i);
-}
-
-
-/* onSwap (1)
-Utility function for swapping things in a RCUList. */
+/* onSwap (1) (thread safe)
+Utility function for swapping ID-based things in a RCUList. */
template<typename L>
void onSwap(L& list, ID id, std::function<void(typename L::value_type&)> f)
{
static_assert(has_id<typename L::value_type>(), "This type has no ID");
- onSwapById_(list, id, f, is_copyable<typename L::value_type>());
+ onSwapByIndex_(list, getIndex(list, id), f);
}
-/* onSwap (2)
+/* onSwap (2) (thread safe)
Utility function for swapping things in a RCUList when the list contains only
a single element (and so with no ID). */
}
-/* ---------------------------------------------------------------------------*/
+/* ---------------------------------------------------------------------------*/
-#ifndef NDEBUG
+#ifdef G_DEBUG_MODE
void debug();
#include <cassert>
#include "core/model/model.h"
#include "core/channels/channelManager.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/kernelAudio.h"
#include "core/patch.h"
#include "core/conf.h"
#include "core/pluginManager.h"
#include "core/recorderHandler.h"
#include "core/waveManager.h"
+#include "core/sequencer.h"
#include "core/model/storage.h"
namespace m {
namespace model
{
+namespace
+{
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
void store(patch::Patch& patch)
{
#ifdef WITH_VST
patch.beats = clock.get()->beats;
patch.bpm = clock.get()->bpm;
patch.quantize = clock.get()->quantize;
- patch.metronome = mixer::isMetronomeOn(); // TODO - not here
+ patch.metronome = sequencer::isMetronomeOn(); // TODO - not here
#ifdef WITH_VST
for (const Plugin* p : plugins)
{
a.map = std::move(recorderHandler::deserializeActions(patch.actions));
});
+
#ifdef WITH_VST
for (const patch::Plugin& pplugin : patch.plugins)
plugins.push(pluginManager::deserializePlugin(pplugin));
#endif
- for (const patch::Wave& pwave : patch.waves)
- waves.push(std::move(waveManager::deserializeWave(pwave)));
-
- for (const patch::Channel& pchannel : patch.channels) {
- if (pchannel.type == ChannelType::MASTER || pchannel.type == ChannelType::PREVIEW)
- onSwap(channels, pchannel.id, [&](Channel& ch) { ch.load(pchannel); });
+ for (const patch::Wave& pwave : patch.waves) {
+ std::unique_ptr<Wave> w = waveManager::deserializeWave(pwave);
+ if (w != nullptr)
+ waves.push(std::move(w));
+ }
+
+ channels.clear();
+ for (const patch::Channel& pchannel : patch.channels)
+ channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize()));
+
+ /* Load Waves into Channels. */
+
+ ChannelsLock cl(channels);
+ WavesLock wl(waves);
+
+ for (Channel* c : channels) {
+ if (!c->samplePlayer)
+ continue;
+ if (exists(waves, c->samplePlayer->getWaveId()))
+ c->samplePlayer->setWave(get(waves, c->samplePlayer->getWaveId()));
else
- channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize()));
- }
+ c->samplePlayer->setInvalidWave();
+ }
}
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MODEL_TRAITS_H
+#define G_MODEL_TRAITS_H
+
+
+#include <type_traits>
+#include "core/wave.h"
+#include "core/plugin.h"
+#include "core/channels/channel.h"
+
+
+namespace giada {
+namespace m {
+namespace model
+{
+template <typename T> struct has_id : std::false_type {};
+template <> struct has_id<Channel> : std::true_type {};
+template <> struct has_id<Wave> : std::true_type {};
+#ifdef WITH_VST
+template <> struct has_id<Plugin> : std::true_type {};
+#endif
+}}} // giada::m::model::
+
+
+#endif
if (!j.contains(PATCH_KEY_CHANNELS))
return;
- ID id = mixer::PREVIEW_CHANNEL_ID;
+ ID defaultId = mixer::PREVIEW_CHANNEL_ID;
for (const auto& jchannel : j[PATCH_KEY_CHANNELS]) {
Channel c;
- c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++id);
+ c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++defaultId);
c.type = static_cast<ChannelType>(jchannel.value(PATCH_KEY_CHANNEL_TYPE, 1));
c.volume = jchannel.value(PATCH_KEY_CHANNEL_VOLUME, G_DEFAULT_VOL);
c.height = jchannel.value(PATCH_KEY_CHANNEL_SIZE, G_GUI_UNIT);
c.midiOutLmute = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, 0);
c.midiOutLsolo = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, 0);
c.armed = jchannel.value(PATCH_KEY_CHANNEL_ARMED, false);
- c.mode = static_cast<ChannelMode>(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1));
+ c.mode = static_cast<SamplePlayerMode>(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1));
c.waveId = jchannel.value(PATCH_KEY_CHANNEL_WAVE_ID, 0);
c.begin = jchannel.value(PATCH_KEY_CHANNEL_BEGIN, 0);
c.end = jchannel.value(PATCH_KEY_CHANNEL_END, 0);
j[PATCH_KEY_CHANNELS].push_back(jchannel);
}
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void modernize_()
+{
+ /* 0.16.3
+ - Make sure that ChannelType is correct: ID 1, 2 are MASTER channels,
+ ID 3 is PREVIEW channel;
+ - set panning to default (0.5) and waveId to 0 for non-Sample Channels. */
+
+ for (Channel& c : patch.channels) {
+
+ if (c.id == mixer::MASTER_OUT_CHANNEL_ID || c.id == mixer::MASTER_IN_CHANNEL_ID)
+ c.type = ChannelType::MASTER;
+ else
+ if (c.id == mixer::PREVIEW_CHANNEL_ID)
+ c.type = ChannelType::PREVIEW;
+
+ if (c.type != ChannelType::SAMPLE) {
+ c.pan = G_DEFAULT_PAN;
+ c.waveId = 0;
+ }
+ }
+}
}; // {anonymous}
readWaves_(j, basePath);
readActions_(j);
readChannels_(j);
+ modernize_();
}
catch (nl::json::exception& e) {
u::log::print("[patch::read] Exception thrown: %s\n", e.what());
bool mute;
bool solo;
float volume = G_DEFAULT_VOL;
- float pan = 0.5f;
+ float pan = G_DEFAULT_PAN;
bool hasActions;
bool armed;
bool midiIn;
uint32_t midiOutLmute;
uint32_t midiOutLsolo;
// sample channel
- ID waveId;
- ChannelMode mode;
- Frame begin;
- Frame end;
- Frame shift;
- bool readActions;
- float pitch = G_DEFAULT_PITCH;
- bool inputMonitor;
- bool midiInVeloAsVol;
- uint32_t midiInReadActions;
- uint32_t midiInPitch;
+ ID waveId = 0;
+ SamplePlayerMode mode;
+ Frame begin;
+ Frame end;
+ Frame shift;
+ bool readActions;
+ float pitch = G_DEFAULT_PITCH;
+ bool inputMonitor;
+ bool midiInVeloAsVol;
+ uint32_t midiInReadActions;
+ uint32_t midiInPitch;
// midi channel
bool midiOut;
int midiOutChan;
else {
audioBuffer_.clear();
processPlugins_(pluginIds, *events);
-
}
juceToGiadaOutBuf_(outBuf);
}
/* -------------------------------------------------------------------------- */
-void clonePlugins(const Channel& oldChannel, Channel& newChannel)
+std::vector<ID> clonePlugins(std::vector<ID> pluginIds)
{
- newChannel.pluginIds.clear();
- for (ID id : oldChannel.pluginIds)
- newChannel.pluginIds.push_back(clonePlugin_(id));
+ std::vector<ID> out;
+ for (ID id : pluginIds)
+ out.push_back(clonePlugin_(id));
+ return out;
}
namespace m
{
class Plugin;
-class Channel;
class AudioBuffer;
-
namespace pluginHost
{
using Stack = std::vector<std::shared_ptr<Plugin>>;
void freePlugins(const std::vector<ID>& pluginIds);
/* clonePlugins
-Clones all the plug-ins from the current channel to the new one. */
+Clones all the plug-ins from 'pluginIds' vector coming from the old channel
+and returns new IDs. */
-void clonePlugins(const Channel& oldChannel, Channel& newChannel);
+std::vector<ID> clonePlugins(std::vector<ID> pluginIds);
void setPluginParameter(ID pluginId, int paramIndex, float value);
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/clock.h"
+#include "quantizer.h"
+
+
+namespace giada {
+namespace m
+{
+void Quantizer::trigger(int id)
+{
+ assert(id >= 0);
+ assert(id < (int) m_callbacks.size());
+
+ m_performId = id;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::schedule(int id, std::function<void(Frame delta)> f)
+{
+ assert(id >= 0);
+ assert(id < (int) m_callbacks.size());
+
+ m_callbacks[id] = f;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::advance(Range<Frame> block, Frame quantizerStep)
+{
+ /* Nothing to do if there's no action to perform. */
+
+ if (m_performId == -1)
+ return;
+
+ assert(m_callbacks[m_performId] != nullptr);
+
+ for (Frame global = block.getBegin(), local = 0; global < block.getEnd(); global++, local++) {
+
+ if (global % quantizerStep != 0) // Skip if it's not on a quantization unit.
+ continue;
+
+ m_callbacks[m_performId](local);
+ m_performId = -1;
+ return;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void Quantizer::clear()
+{
+ m_performId = -1;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool Quantizer::isTriggered() const
+{
+ return m_performId != -1;
+}
+}} // giada::m::
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_QUANTIZER_H
+#define G_QUANTIZER_H
+
+
+#include <array>
+#include <functional>
+#include "core/const.h"
+#include "core/range.h"
+#include "core/types.h"
+
+
+namespace giada {
+namespace m
+{
+class Quantizer
+{
+public:
+
+ /* schedule
+ Schedules a function in slot 'id' to be called at the right time. The
+ function has a 'delta' parameter for the buffer offset. */
+
+ void schedule(int id, std::function<void(Frame)>);
+
+ /* trigger
+ Triggers the function in slot 'id'. Might start right away, or at the end
+ of the quantization step. */
+
+ void trigger(int id);
+
+ /* advance
+ Computes the internal state. Wants a range of frames [currentFrame,
+ currentFrame + bufferSize) and a quantization step. Call this function
+ on each block. */
+
+ void advance(Range<Frame> block, Frame quantizerStep);
+
+ /* clear
+ Disables quantized operations in progress, if any. */
+
+ void clear();
+
+ /* isTriggered
+ True if a quantizer function has been triggered(). */
+
+ bool isTriggered() const;
+
+private:
+
+ std::array<std::function<void(Frame)>, G_MAX_QUANTIZER_SIZE> m_callbacks;
+ int m_performId = -1;
+};
+}} // giada::m::
+
+
+#endif
/* Queue
Single producer, single consumer lock-free queue. */
-template<typename T, size_t size>
+template<typename T, std::size_t size>
class Queue
{
public:
bool pop(T& item)
{
- size_t curr = m_head.load();
+ std::size_t curr = m_head.load();
if (curr == m_tail.load()) // Queue empty, nothing to do
return false;
bool push(const T& item)
{
- size_t curr = m_tail.load();
- size_t next = increment(curr);
+ std::size_t curr = m_tail.load();
+ std::size_t next = increment(curr);
if (next == m_head.load()) // Queue full, nothing to do
return false;
private:
- size_t increment(size_t i) const
+ std::size_t increment(std::size_t i) const
{
return (i + 1) % size;
}
std::array<T, size> m_data;
- std::atomic<size_t> m_head;
- std::atomic<size_t> m_tail;
+ std::atomic<std::size_t> m_head;
+ std::atomic<std::size_t> m_tail;
};
}} // giada::m::
template<typename T>
class Range
{
-private:
-
- T m_a;
- T m_b;
-
public:
Range() : m_a(0), m_b(0) {}
T getBegin() const { return m_a; }
T getEnd() const { return m_b; }
T getLength() const { return m_b - m_a; }
+
+private:
+
+ T m_a;
+ T m_b;
};
} // giada::
namespace giada {
namespace m
{
+/* RCUList
+Single producer, multiple consumer (i.e. one writer, many readers) RCU-based
+list. */
+
template<typename T>
class RCUList
{
public:
/* Lock
- Scoped lock structure. */
+ Scoped lock structure. Not copyable, not moveable, not copy-constructible.
+ Same as std::scoped_lock:
+ https://en.cppreference.com/w/cpp/thread/scoped_lock .*/
struct Lock
{
- Lock(RCUList<T>& r) : rcu(r)
- {
- rcu.lock();
- }
-
- ~Lock()
- {
- rcu.unlock();
- }
+ Lock(RCUList<T>& r) : rcu(r) { rcu.lock(); }
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ ~Lock() { rcu.unlock(); }
RCUList<T>& rcu;
};
void unlock()
{
m_readers[t_grace]--;
+ assert(m_readers[t_grace] >= 0 && "Negative reader");
}
/* get
Returns a reference to the data held by node 'i'. */
- // TODO - this will return a const ref with the non-virtual Channel
- // refactoring.
- T* get(size_t i=0) const
+ T* get(std::size_t i=0) const
{
assert(i < size() && "Index overflow");
assert(m_readers[t_grace].load() > 0 && "Forgot lock before reading");
/* Subscript operator []
Same as above for the [] syntax. */
- // TODO - this will return a const ref with the non-virtual Channel
- // refactoring.
- T* operator[] (size_t i) const
+ T* operator[] (std::size_t i) const
{
return get(i);
}
/* back
Return data held by the last node. */
- // TODO - this will return a const ref with the non-virtual Channel
- // refactoring.
T* back() const
{
}
/* clone
- Returns a new copy of the data held by node 'i'. The template machinery
- is required for when you declare a RCUList<Base> and later on want to clone
- a derived object. Usage:
-
- RCUList<Base> list;
- ...
- std::unique_ptr<Derived> d = list.clone<Derived>(i);
- */
+ Returns a new copy of the data held by node 'i'. */
- template<typename C=T>
- std::unique_ptr<C> clone(size_t i=0) const
+ std::unique_ptr<T> clone(std::size_t i=0) const
{
- return std::make_unique<C>(*static_cast<C*>(getNode(i)->data.get()));
+ /* Make sure no one is writing (swapping, popping, pushing). */
+ assert(m_writing.load() == false);
+ return std::make_unique<T>(*getNode(i)->data.get());
}
/* swap
multiple calls to swap() made by the same thread: the caller is blocked by
the spinlock below: no progress is made until m_readers[oldgrace] > 0. */
- void swap(std::unique_ptr<T> data, size_t i=0)
+ void swap(std::unique_ptr<T> data, std::size_t i=0)
{
/* Never start two overlapping writing sessions. */
calls to pop() made by the same thread: the caller is blocked by the
spinlock below: no progress is made while m_readers[oldgrace] > 0. */
- void pop(size_t i)
+ void pop(std::size_t i)
{
/* Never start two overlapping writing sessions. */
/* size
Returns the number of nodes in the list. */
- size_t size() const
+ std::size_t size() const
{
return m_size.load();
}
private:
- Node* getNode(size_t i) const
+ Node* getNode(std::size_t i) const
{
- size_t p = 0;
+ std::size_t p = 0;
Node* curr = m_head.load();
while (curr != nullptr && p < i) {
std::array<std::atomic<int>, 2> m_readers;
std::atomic<std::int8_t> m_grace;
- std::atomic<size_t> m_size;
+ std::atomic<std::size_t> m_size;
std::atomic<bool> m_writing;
/* m_head
#include "gui/dispatcher.h"
-#include "core/channels/channel.h"
#include "core/model/model.h"
#include "core/types.h"
#include "core/clock.h"
#include "core/kernelAudio.h"
#include "core/conf.h"
#include "core/mixer.h"
+#include "core/sequencer.h"
#include "core/mixerHandler.h"
#include "core/midiDispatcher.h"
#include "core/recorder.h"
if (!kernelAudio::isReady())
return false;
clock::setStatus(ClockStatus::RUNNING);
- m::mh::startSequencer();
+ sequencer::start();
return true;
}
if (!kernelAudio::isReady() || !mh::hasRecordableSampleChannels())
return false;
mixer::startInputRec();
- mh::startSequencer();
+ sequencer::start();
return true;
}
} // {anonymous}
if (clock::getStatus() == ClockStatus::WAITING) {
clock::setStatus(ClockStatus::STOPPED);
- m::midiDispatcher::setSignalCallback(nullptr);
+ midiDispatcher::setSignalCallback(nullptr);
v::dispatcher::setSignalCallback(nullptr);
return;
}
actions. Start reading right away, without checking whether
conf::treatRecsAsLoops is enabled or not. */
- for (ID id : channels)
- m::model::onGet(m::model::channels, id, [](Channel& c)
+ for (ID id : channels) {
+ model::onGet(model::channels, id, [](Channel& c)
{
- c.startReadingActions(/*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false);
+ c.state->readActions.store(true);
});
+ }
}
#include <cassert>
#include "utils/log.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
#include "core/action.h"
#include "core/idManager.h"
#include "core/recorder.h"
Action* findAction_(ActionMap& src, ID id)
{
- for (auto& kv : src)
- for (Action& a : kv.second)
+ for (auto& [frame, actions] : src)
+ for (Action& a : actions)
if (a.id == id)
return &a;
assert(false);
{
model::onSwap(model::actions, [&](model::Actions& a)
{
- for (auto& kv : a.map) {
- std::vector<Action>& as = kv.second;
- as.erase(std::remove_if(as.begin(), as.end(), f), as.end());
- }
+ for (auto& [frame, actions] : a.map)
+ actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end());
optimize_(a.map);
updateMapPointers(a.map);
});
}
+
+/* -------------------------------------------------------------------------- */
+
+
+bool exists_(ID channelId, Frame frame, const MidiEvent& event, const ActionMap& target)
+{
+ for (const auto& [_, actions] : target)
+ for (const Action& a : actions)
+ if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw())
+ return true;
+ return false;
+}
+
+
+bool exists_(ID channelId, Frame frame, const MidiEvent& event)
+{
+ model::ActionsLock lock(model::actions);
+ return exists_(channelId, frame, event, model::actions.get()->map);
+}
} // {anonymous}
{
std::unique_ptr<model::Actions> ma = model::actions.clone();
- /* Remove all existing actions: let's start from scratch. */
+ /* Remove all existing actions: let's start from scratch.
+ TODO - so why cloning it?! */
ma->map.clear();
{
model::ActionsLock lock(model::actions);
- for (const auto& kv : model::actions.get()->map) {
- Frame frame = f(kv.first);
- for (const Action& a : kv.second) {
+ for (const auto& [oldFrame, actions] : model::actions.get()->map) {
+ Frame newFrame = f(oldFrame);
+ for (const Action& a : actions) {
Action copy = a;
- copy.frame = frame;
- ma->map[frame].push_back(copy);
+ copy.frame = newFrame;
+ ma->map[newFrame].push_back(copy);
}
- u::log::print("[recorder::updateKeyFrames] %d -> %d\n", kv.first, frame);
+G_DEBUG(oldFrame << " -> " << newFrame);
}
}
{
model::ActionsLock lock(model::actions);
- for (const auto& kv : model::actions.get()->map)
- for (const Action& a : kv.second)
+ for (const auto& [frame, actions] : model::actions.get()->map)
+ for (const Action& a : actions)
if (a.channelId == channelId && (type == 0 || type == a.event.getStatus()))
return true;
return false;
Action rec(ID channelId, Frame frame, MidiEvent event)
{
+ /* Skip duplicates. */
+
+ if (exists_(channelId, frame, event))
+ return {};
+
Action a = makeAction(0, channelId, frame, event);
/* If key frame doesn't exist yet, the [] operator in std::map is smart
/* -------------------------------------------------------------------------- */
-void rec(std::vector<Action>& as)
+void rec(std::vector<Action>& actions)
{
- if (as.size() == 0)
+ if (actions.size() == 0)
return;
- /* Generate new action ID and fix next and prev IDs. */
-
- for (Action& a : as) {
- int id = a.id;
- a.id = actionId_.get();
- for (Action& aa : as) {
- if (aa.prevId == id) aa.prevId = a.id;
- if (aa.nextId == id) aa.nextId = a.id;
- }
- }
-
model::onSwap(model::actions, [&](model::Actions& mas)
{
- for (const Action& a : as)
- mas.map[a.frame].push_back(a);
+ for (const Action& a : actions)
+ if (!exists_(a.channelId, a.frame, a.event, mas.map))
+ mas.map[a.frame].push_back(a);
updateMapPointers(mas.map);
});
}
{
model::ActionsLock lock(model::actions);
- for (auto& kv : model::actions.get()->map)
- for (const Action& action : kv.second)
+ for (auto& [_, actions] : model::actions.get()->map)
+ for (const Action& action : actions)
f(action);
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+ID getNewActionId()
+{
+ return actionId_.get();
+}
}}}; // giada::m::recorder::
constructor. */
void updateMapPointers(ActionMap& src);
+
+/* getNewActionId
+Returns a new action ID, internally generated. */
+
+ID getNewActionId();
+
}}}; // giada::m::recorder::
const Action* getActionPtrById_(int id, const recorder::ActionMap& source)
{
- for (auto& kv : source)
- for (const Action& action : kv.second)
+ for (const auto& [_, actions] : source)
+ for (const Action& action : actions)
if (action.id == id)
return &action;
return nullptr;
Without this trick (i.e. if it loops from vector.begin() each time) the
algorithm would end up matching wrong partners. */
-void consolidate_(const Action& a1, size_t i)
+void consolidate_(const Action& a1, std::size_t i)
{
for (auto it = recs_.begin() + i; it != recs_.end(); ++it) {
/* -------------------------------------------------------------------------- */
-void liveRec(ID channelId, MidiEvent e)
+void liveRec(ID channelId, MidiEvent e, Frame globalFrame)
{
assert(e.isNoteOnOff()); // Can't record any other kind of events for now
+ /* TODO - this might allocate on the MIDI thread */
if (recs_.size() >= recs_.capacity())
recs_.reserve(recs_.size() + MAX_LIVE_RECS_CHUNK);
- recs_.push_back(recorder::makeAction(-1, channelId, clock::getCurrentFrame(), e));
+ recs_.push_back(recorder::makeAction(recorder::getNewActionId(), channelId, globalFrame, e));
}
void clearAllActions()
{
- for (size_t i = 0; i < model::channels.size(); i++)
- model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.hasActions = false; });
+ /* TODO - disgusting */
+ for (std::size_t i = 0; i < model::channels.size(); i++) {
+ model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c)
+ {
+ c.state->hasActions = false;
+ });
+ }
recorder::clearAll();
}
/* liveRec
Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */
-void liveRec(ID channelId, MidiEvent e);
+void liveRec(ID channelId, MidiEvent e, Frame global);
/* consolidate
Records all live actions. Returns a set of channels IDs that have been
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_RING_BUFFER_H
+#define G_RING_BUFFER_H
+
+
+#include <array>
+
+
+namespace giada
+{
+/* RingBuffer
+A non-thread-safe, fixed-size ring buffer implementation. It grows from 0 to S,
+then items are overwritten starting from position 0. */
+
+template <typename T, std::size_t S>
+class RingBuffer
+{
+public:
+ using iterator = typename std::array<T, S>::iterator;
+ using const_iterator = typename std::array<T, S>::const_iterator;
+
+ iterator begin() { return m_data.begin(); }
+ iterator end() { return m_data.begin() + m_end; }
+ const_iterator begin() const { return m_data.begin(); }
+ const_iterator end() const { return m_data.begin() + m_end; }
+ const_iterator cbegin() const { return m_data.begin(); }
+ const_iterator cend() const { return m_data.begin() + m_end; }
+
+ void clear()
+ {
+ m_data.fill({});
+ m_index = 0;
+ m_end = 0;
+ }
+
+ void push_back(T t)
+ {
+ m_data[m_index] = t;
+ m_index = (m_index + 1) % m_data.size(); // Wraps around at m_data.size()
+ m_end = std::max(m_index, m_end); // Points to the greater index
+ }
+
+ constexpr std::size_t size() const noexcept
+ {
+ return m_data.size();
+ }
+
+private:
+
+ std::array<T, S> m_data;
+ std::size_t m_index = 0;
+ std::size_t m_end = 0;
+};
+} // giada::
+
+
+#endif
+
+
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/model/model.h"
+#include "core/const.h"
+#include "core/mixer.h"
+#include "core/quantizer.h"
+#include "core/clock.h"
+#include "core/conf.h"
+#include "core/recManager.h"
+#include "core/kernelAudio.h"
+#include "sequencer.h"
+
+
+namespace giada {
+namespace m {
+namespace sequencer
+{
+namespace
+{
+constexpr int Q_ACTION_REWIND = 0;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+struct Metronome
+{
+ static constexpr Frame CLICK_SIZE = 38;
+
+ float beat[CLICK_SIZE] = {
+ 0.059033, 0.117240, 0.173807, 0.227943, 0.278890, 0.325936,
+ 0.368423, 0.405755, 0.437413, 0.462951, 0.482013, 0.494333,
+ 0.499738, 0.498153, 0.489598, 0.474195, 0.452159, 0.423798,
+ 0.389509, 0.349771, 0.289883, 0.230617, 0.173194, 0.118739,
+ 0.068260, 0.022631, -0.017423, -0.051339, -0.078721, -0.099345,
+ -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954,
+ -0.070862, -0.048844
+ };
+
+ float bar[CLICK_SIZE] = {
+ 0.175860, 0.341914, 0.488904, 0.608633, 0.694426, 0.741500,
+ 0.747229, 0.711293, 0.635697, 0.524656, 0.384362, 0.222636,
+ 0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653,
+ -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160,
+ -0.201360, -0.067752, 0.052194, 0.151746, 0.226280, 0.273493,
+ 0.293425, 0.288307, 0.262252, 0.220811, 0.170435, 0.117887,
+ 0.069639, 0.031320
+ };
+
+ Frame tracker = 0;
+ bool running = false;
+ bool playBar = false;
+ bool playBeat = false;
+
+ void render(AudioBuffer& outBuf, bool& process, float* data, Frame f)
+ {
+ process = true;
+ for (int i=0; i<outBuf.countChannels(); i++)
+ outBuf[f][i] += data[tracker];
+ if (++tracker > Metronome::CLICK_SIZE) {
+ process = false;
+ tracker = 0;
+ }
+ }
+} metronome_;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void renderMetronome_(AudioBuffer& outBuf, Frame f)
+{
+ if (!metronome_.running)
+ return;
+
+ if (clock::isOnBar() || metronome_.playBar)
+ metronome_.render(outBuf, metronome_.playBar, metronome_.bar, f);
+ else
+ if (clock::isOnBeat() || metronome_.playBeat)
+ metronome_.render(outBuf, metronome_.playBeat, metronome_.beat, f);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewindQ_(Frame delta)
+{
+ clock::rewind();
+ mixer::pumpEvent({ mixer::EventType::SEQUENCER_REWIND, delta });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+ if (kernelAudio::getAPI() == G_SYS_API_JACK)
+ kernelAudio::jackStart();
+ else
+#endif
+ start();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stop_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+ if (kernelAudio::getAPI() == G_SYS_API_JACK)
+ kernelAudio::jackStop();
+ else
+#endif
+ stop();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewind_()
+{
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+ if (kernelAudio::getAPI() == G_SYS_API_JACK)
+ kernelAudio::jackSetPosition(0);
+ else
+#endif
+ rewind();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Quantizer quantizer_;
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void init()
+{
+ quantizer_.schedule(Q_ACTION_REWIND, rewindQ_);
+ clock::rewind();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void run(Frame bufferSize)
+{
+ Frame start = clock::getCurrentFrame();
+ Frame end = start + bufferSize;
+ Frame total = clock::getFramesInLoop();
+ Frame bar = clock::getFramesInBar();
+
+ for (Frame i = start, local = 0; i < end; i++, local++) {
+
+ Frame global = i % total; // wraps around 'total'
+
+ if (global == 0)
+ mixer::pumpEvent({ mixer::EventType::SEQUENCER_FIRST_BEAT, local, { 0, 0, global } });
+ else
+ if (global % bar == 0)
+ mixer::pumpEvent({ mixer::EventType::SEQUENCER_BAR, local, { 0, 0, global } });
+
+ const std::vector<Action>* as = recorder::getActionsOnFrame(global);
+ if (as != nullptr)
+ for (const Action& a : *as)
+ mixer::pumpEvent({ mixer::EventType::ACTION, local, a });
+ }
+
+ quantizer_.advance(Range<Frame>(start, end), clock::getQuantizerStep());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void parse(const mixer::EventBuffer& events)
+{
+ for (const mixer::Event& e : events) {
+ if (e.type == mixer::EventType::SEQUENCER_START) {
+ start_(); break;
+ }
+ if (e.type == mixer::EventType::SEQUENCER_STOP) {
+ stop_(); break;
+ }
+ if (e.type == mixer::EventType::SEQUENCER_REWIND_REQ) {
+ rewind_(); break;
+ }
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void advance(AudioBuffer& outBuf)
+{
+ for (Frame i = 0; i < outBuf.countFrames(); i++) {
+ clock::sendMIDIsync();
+ clock::incrCurrentFrame();
+ renderMetronome_(outBuf, i);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void start()
+{
+ switch (clock::getStatus()) {
+ case ClockStatus::STOPPED:
+ clock::setStatus(ClockStatus::RUNNING);
+ break;
+ case ClockStatus::WAITING:
+ clock::setStatus(ClockStatus::RUNNING);
+ recManager::stopActionRec();
+ break;
+ default:
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void stop()
+{
+ clock::setStatus(ClockStatus::STOPPED);
+
+ /* If recordings (both input and action) are active deactivate them, but
+ store the takes. RecManager takes care of it. */
+
+ if (recManager::isRecordingAction())
+ recManager::stopActionRec();
+ else
+ if (recManager::isRecordingInput())
+ recManager::stopInputRec();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void rewind()
+{
+ if (clock::canQuantize())
+ quantizer_.trigger(Q_ACTION_REWIND);
+ else
+ rewindQ_(/*delta=*/0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+bool isMetronomeOn() { return metronome_.running; }
+void toggleMetronome() { metronome_.running = !metronome_.running; }
+void setMetronome(bool v) { metronome_.running = v; }
+}}}; // giada::m::sequencer::
+
+
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_SEQUENCER_H
+#define G_SEQUENCER_H
+
+
+#include "core/mixer.h"
+
+
+namespace giada {
+namespace m
+{
+class AudioBuffer;
+namespace sequencer
+{
+void init();
+
+/* parse
+Parses sequencer events that might occur in a block and advances the internal
+quantizer. */
+
+void run(Frame bufferSize);
+void parse(const mixer::EventBuffer& events);
+void advance(AudioBuffer& outBuf);
+
+void start();
+void stop();
+void rewind();
+
+bool isMetronomeOn();
+void toggleMetronome();
+void setMetronome(bool v);
+}}} // giada::m::sequencer::
+
+
+#endif
using Pixel = int;
using Frame = int;
-enum class ClockStatus { STOPPED, WAITING, RUNNING };
+enum class Thread { MAIN, MIDI, AUDIO };
+
+enum class ClockStatus { STOPPED, WAITING, RUNNING, ON_BEAT, ON_BAR, ON_FIRST_BEAT, VOID };
enum class ChannelType : int { SAMPLE = 1, MIDI, MASTER, PREVIEW };
ENDING = 1, WAIT, PLAY, OFF, EMPTY, MISSING, WRONG
};
-enum class ChannelMode : int
+enum class SamplePlayerMode : int
{
LOOP_BASIC = 1, LOOP_ONCE, LOOP_REPEAT, LOOP_ONCE_BAR,
SINGLE_BASIC, SINGLE_PRESS, SINGLE_RETRIG, SINGLE_ENDLESS
enum class RecTriggerMode : int { NORMAL = 0, SIGNAL };
-enum class PreviewMode : int { NONE = 0, NORMAL, LOOP };
enum class EventType : int { AUTO = 0, MANUAL };
};
}
+void Wave::copyData(const AudioBuffer& b)
+{
+ buffer.copyData(b);
+}
+
+
/* -------------------------------------------------------------------------- */
channels than m_channels. */
void copyData(const float* data, int frames, int offset=0);
+ void copyData(const AudioBuffer& b);
void alloc(int size, int channels, int rate, int bits, const std::string& path);
/* -------------------------------------------------------------------------- */
-void normalizeHard(ID waveId, int a, int b)
+constexpr int SMOOTH_SIZE = 32;
+
+
+void normalize(ID waveId, int a, int b)
{
model::onSwap(m::model::waves, waveId, [&](Wave& w)
{
float peak = getPeak_(w, a, b);
- if (peak == 0.0f || peak > 1.0f) // as in ::normalizeSoft
+ if (peak == 0.0f || peak > 1.0f)
return;
for (int i=a; i<b; i++) {
/* -------------------------------------------------------------------------- */
-void fade(ID waveId, int a, int b, int type)
+void fade(ID waveId, int a, int b, Fade type)
{
u::log::print("[wfx::fade] fade from %d to %d (range = %d)\n", a, b, b-a);
model::onSwap(m::model::waves, waveId, [&](Wave& w)
{
- if (type == FADE_IN)
+ if (type == Fade::IN)
for (int i=a; i<=b; i++, m+=d)
fadeFrame_(w, i, m);
else
return;
}
- fade(waveId, a, a+SMOOTH_SIZE, FADE_IN);
- fade(waveId, b-SMOOTH_SIZE, b, FADE_OUT);
+ fade(waveId, a, a+SMOOTH_SIZE, Fade::IN);
+ fade(waveId, b-SMOOTH_SIZE, b, Fade::OUT);
}
#define G_WAVE_FX_H
+#include "core/types.h"
+
+
namespace giada {
namespace m
{
class Wave;
-
namespace wfx
{
-static const int FADE_IN = 0;
-static const int FADE_OUT = 1;
-static const int SMOOTH_SIZE = 32;
+enum class Fade { IN, OUT };
/* monoToStereo
Converts a 1-channel Wave to a 2-channels wave. It works on a free Wave object,
int monoToStereo(Wave& w);
-/* normalizeHard
+/* normalize
Normalizes the wave in range a-b by altering values in memory. */
-void normalizeHard(ID waveId, int a, int b);
+void normalize(ID waveId, int a, int b);
void silence(ID waveId, int a, int b);
void cut(ID waveId, int a, int b);
void paste(const Wave& src, ID waveId, int a);
/* fade
-Fades in or fades out selection. Fade In = type 0, Fade Out = type 1 */
+Fades in or fades out selection. Can be Fade::IN or Fade::OUT. */
-void fade(ID waveId, int a, int b, int type);
+void fade(ID waveId, int a, int b, Fade type);
/* smooth
Smooth edges of selection. */
#include <cassert>
#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/clock.h"
#include "core/const.h"
#include "core/recorderHandler.h"
#include "core/recorder.h"
#include "core/action.h"
-#include "recorder.h"
+#include "glue/events.h"
+#include "glue/recorder.h"
#include "actionEditor.h"
{
namespace mr = m::recorder;
+ // TODO - use MidiEvent(float)
m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, G_MAX_VELOCITY);
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
const m::Action a1 = mr::rec(channelId, 0, e1);
if (frame == -1) // Vertical points, nothing to do here
return;
+ // TODO - use MidiEvent(float)
m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value);
const m::Action a2 = mr::rec(channelId, frame, e2);
bool isSinglePressMode_(ID channelId)
{
bool b;
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
{
- const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
- b = sc.mode == ChannelMode::SINGLE_PRESS;
+ b = c.samplePlayer->state->mode == SamplePlayerMode::SINGLE_PRESS;
});
return b;
}
/* -------------------------------------------------------------------------- */
+SampleData::SampleData(const m::SamplePlayer& s)
+: channelMode(s.state->mode.load())
+, isLoopMode (s.state->isAnyLoopMode())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Data::Data(const m::Channel& c)
+: channelId (c.id)
+, channelName(c.state->name)
+, actions (m::recorder::getActionsOnChannel(c.id))
+{
+ if (c.getType() == ChannelType::SAMPLE)
+ sample = std::make_optional<SampleData>(*c.samplePlayer);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ return Data(mm::get(mm::channels, channelId));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2)
{
namespace mr = m::recorder;
void deleteMidiAction(ID channelId, const m::Action& a)
{
namespace mr = m::recorder;
- namespace cr = c::recorder;
assert(a.isValid());
assert(a.event.getStatus() == m::MidiEvent::NOTE_ON);
key_on/key_off sequence. Check if 'next' exist first: could be orphaned. */
if (a.next != nullptr) {
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- m::MidiChannel& mc = static_cast<m::MidiChannel&>(c);
- if (mc.isPlaying() && !mc.mute)
- mc.sendMidi(a.next->event, 0);
- });
+ events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN);
mr::deleteAction(a.id, a.next->id);
}
else
namespace cr = c::recorder;
if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo
- mr::deleteAction(a.next->id);
- mr::deleteAction(a.id);
+ mr::deleteAction(a.id, a.next->id);
+ else
+ mr::deleteAction(a.id);
recorder::updateChannel(channelId, /*updateActionEditor=*/false);
}
/* Deleting a boundary action wipes out everything. If is volume, remember
to restore _i and _d members in channel. */
/* TODO - move this to c::*/
+ /* TODO - FIX*/
if (mrh::isBoundaryEnvelopeAction(a)) {
if (a.isVolumeEnvelope()) {
+ /*
m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
{
c.volume_i = 1.0;
c.volume_d = 0.0;
- });
+ });*/
}
mr::clearActions(channelId, a.event.getStatus());
}
#define G_GLUE_ACTION_EDITOR_H
+#include <optional>
#include <vector>
+#include <string>
#include "core/types.h"
namespace m
{
struct Action;
-class SampleChannel;
-class MidiChannel;
+class Channel;
+class SamplePlayer;
}
namespace c {
namespace actionEditor
{
-std::vector<m::Action> getActions(ID channelId);
+struct SampleData
+{
+ SampleData(const m::SamplePlayer&);
+
+ SamplePlayerMode channelMode;
+ bool isLoopMode;
+};
+
+struct Data
+{
+ Data() = default;
+ Data(const m::Channel&);
+
+ ID channelId;
+ std::string channelName;
+ std::vector<m::Action> actions;
+
+ std::optional<SampleData> sample;
+};
+
+Data getData(ID channelId);
/* MIDI actions. */
#include "utils/gui.h"
#include "utils/fs.h"
#include "utils/log.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/model/model.h"
#include "core/kernelAudio.h"
#include "core/mixerHandler.h"
#include "core/conf.h"
#include "core/wave.h"
#include "core/recorder.h"
+#include "core/recManager.h"
#include "core/plugin.h"
#include "core/waveManager.h"
#include "main.h"
else if (res == G_RES_ERR_NO_DATA)
v::gdAlert("No file specified.");
}
+} // {anonymous}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void onRefreshSampleEditor_(bool gui, std::function<void(v::gdSampleEditor*)> f)
+SampleData::SampleData(const m::SamplePlayer& s, const m::AudioReceiver& a)
+: waveId (s.getWaveId())
+, mode (s.state->mode.load())
+, isLoop (s.state->isAnyLoopMode())
+, pitch (s.state->pitch.load())
+, m_samplePlayer (&s)
+, m_audioReceiver(&a)
{
- v::gdSampleEditor* gdEditor = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
- if (gdEditor == nullptr)
- return;
- if (!gui) Fl::lock();
- f(gdEditor);
- if (!gui) Fl::unlock();
}
-} // {anonymous}
+Frame SampleData::a_getTracker() const { return a_get(m_samplePlayer->state->tracker); }
+Frame SampleData::a_getBegin() const { return a_get(m_samplePlayer->state->begin); }
+Frame SampleData::a_getEnd() const { return a_get(m_samplePlayer->state->end); }
+bool SampleData::a_getInputMonitor() const { return a_get(m_audioReceiver->state->inputMonitor); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiData::MidiData(const m::MidiSender& m)
+: m_midiSender(&m)
+{
+}
+
+bool MidiData::a_isOutputEnabled() const { return a_get(m_midiSender->state->enabled); }
+int MidiData::a_getFilter() const { return a_get(m_midiSender->state->filter); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Data::Data(const m::Channel& c)
+: id (c.id)
+, columnId (c.getColumnId())
+#ifdef WITH_VST
+, pluginIds (c.pluginIds)
+#endif
+, type (c.getType())
+, height (c.state->height)
+, name (c.state->name)
+, volume (c.state->volume.load())
+, pan (c.state->pan.load())
+, key (c.state->key.load())
+, hasActions (c.state->hasActions)
+, m_channel (c)
+{
+ if (c.getType() == ChannelType::SAMPLE)
+ sample = std::make_optional<SampleData>(*c.samplePlayer, *c.audioReceiver);
+ else
+ if (c.getType() == ChannelType::MIDI)
+ midi = std::make_optional<MidiData>(*c.midiSender);
+}
+
+
+bool Data::a_getSolo() const { return a_get(m_channel.state->solo); }
+bool Data::a_getMute() const { return a_get(m_channel.state->mute); }
+ChannelStatus Data::a_getPlayStatus() const { return a_get(m_channel.state->playStatus); }
+ChannelStatus Data::a_getRecStatus() const { return a_get(m_channel.state->recStatus); }
+bool Data::a_getReadActions() const { return a_get(m_channel.state->readActions); }
+bool Data::a_isArmed() const { return a_get(m_channel.state->armed); }
+bool Data::a_isRecordingInput() const { return m::recManager::isRecordingInput(); }
+bool Data::a_isRecordingAction() const { return m::recManager::isRecordingAction(); }
+
+
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ return Data(mm::get(mm::channels, channelId));
+}
+
+
+std::vector<Data> getChannels()
+{
+ namespace mm = m::model;
+ mm::ChannelsLock cl(mm::channels);
+
+ std::vector<Data> out;
+ for (const m::Channel* ch : mm::channels)
+ if (!ch->isInternal())
+ out.push_back(Data(*ch));
+
+ return out;
+}
+
+
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void setArm(ID channelId, bool value)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = value; });
-}
-
-
-void toggleArm(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = !c.armed; });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void setInputMonitor(ID channelId, bool value)
{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- static_cast<m::SampleChannel&>(c).inputMonitor = value;
+ c.audioReceiver->state->inputMonitor.store(value);
});
}
/* -------------------------------------------------------------------------- */
-void setVolume(ID channelId, float value, bool gui, bool editor)
+void setSamplePlayerMode(ID channelId, SamplePlayerMode m)
{
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { c.volume = value; });
-
- /* Changing channel volume? Update wave editor (if it's shown). */
-
- if (editor)
- onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->volumeTool->rebuild(); });
-
- if (!gui) {
- Fl::lock();
- G_MainWin->keyboard->getChannel(channelId)->vol->value(value);
- Fl::unlock();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setPitch(ID channelId, float val, bool gui)
-{
m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- static_cast<m::SampleChannel&>(c).setPitch(val);
- });
-
- onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->pitchTool->rebuild(); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setPan(ID channelId, float val, bool gui)
-{
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { c.setPan(val); });
-
- onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->panTool->rebuild(); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setMute(ID channelId, bool value)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setMute(value); });
-}
-
-
-void toggleMute(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setMute(!ch.mute); });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setSampleMode(ID channelId, ChannelMode m)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
{
- static_cast<m::SampleChannel&>(ch).mode = m;
+ c.samplePlayer->state->mode.store(m);
});
- u::gui::refreshActionEditor();
-}
-
+ /* TODO - brutal rebuild! Just rebuild the specific channel instead */
+ G_MainWin->keyboard->rebuild();
-/* -------------------------------------------------------------------------- */
-
-
-void setSolo(ID channelId, bool value)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(value); });
- m::mh::updateSoloCount();
-}
-
-
-void toggleSolo(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(!ch.solo); });
- m::mh::updateSoloCount();
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void start(ID channelId, int velocity, bool record)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
- {
- if (record && !ch.recordStart(m::clock::canQuantize()))
- return;
- ch.start(/*localFrame=*/0, m::clock::canQuantize(), velocity); // Frame 0: user-generated event
- });
+ u::gui::refreshActionEditor();
}
/* -------------------------------------------------------------------------- */
-void kill(ID channelId, bool record)
+void setHeight(ID channelId, Pixel p)
{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
- {
- if (record && !ch.recordKill())
- return;
- ch.kill(/*localFrame=*/0); // Frame 0: user-generated event
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stop(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- ch.recordStop();
- ch.stop();
- });
+ c.state->height = p;
+ });
}
{
m::mh::renameChannel(channelId, name);
}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleReadingActions(ID channelId)
-{
- /* When you call startReadingRecs with conf::treatRecsAsLoops, the
- member value ch->readActions actually is not set to true immediately, because
- the channel is in wait mode (REC_WAITING). ch->readActions will become true on
- the next first beat. So a 'stop rec' command should occur also when
- ch->readActions is false but the channel is in wait mode; this check will
- handle the case of when you press 'R', the channel goes into REC_WAITING and
- then you press 'R' again to undo the status. */
-
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
- {
- if (!ch.hasActions)
- return;
- if (ch.readActions || (!ch.readActions && ch.recStatus == ChannelStatus::WAIT))
- ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops,
- m::conf::conf.recsStopOnChanHalt);
- else
- ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt);
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startReadingActions(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
- {
- ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt);
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopReadingActions(ID channelId)
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch)
- {
- ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops,
- m::conf::conf.recsStopOnChanHalt);
- });
-}
-
}}}; // giada::c::channel::
- /* -----------------------------------------------------------------------------
+ /* -----------------------------------------------------------------------------
*
* Giada - Your Hardcore Loopmachine
*
#define G_GLUE_CHANNEL_H
+#include <optional>
+#include <atomic>
#include <string>
#include <vector>
+#include "core/model/model.h"
#include "core/types.h"
namespace m
{
class Channel;
+class SamplePlayer;
}
namespace c {
namespace channel
{
+struct SampleData
+{
+ SampleData() = delete;
+ SampleData(const m::SamplePlayer&, const m::AudioReceiver&);
+
+ Frame a_getTracker() const;
+ Frame a_getBegin() const;
+ Frame a_getEnd() const;
+ bool a_getInputMonitor() const;
+
+ ID waveId;
+ SamplePlayerMode mode;
+ bool isLoop;
+ float pitch;
+
+private:
+
+ const m::SamplePlayer* m_samplePlayer;
+ const m::AudioReceiver* m_audioReceiver;
+};
+
+struct MidiData
+{
+ MidiData() = delete;
+ MidiData(const m::MidiSender&);
+
+ bool a_isOutputEnabled() const;
+ int a_getFilter() const;
+
+private:
+
+ const m::MidiSender* m_midiSender;
+};
+
+struct Data
+{
+ Data() = default;
+ Data(const m::Channel&);
+
+ bool a_getMute() const;
+ bool a_getSolo() const;
+ ChannelStatus a_getPlayStatus() const;
+ ChannelStatus a_getRecStatus() const;
+ bool a_getReadActions() const;
+ bool a_isArmed() const;
+ bool a_isRecordingInput() const;
+ bool a_isRecordingAction() const;
+
+ ID id;
+ ID columnId;
+#ifdef WITH_VST
+ std::vector<ID> pluginIds;
+#endif
+ ChannelType type;
+ Pixel height;
+ std::string name;
+ float volume;
+ float pan;
+ int key;
+ bool hasActions;
+
+ std::optional<SampleData> sample;
+ std::optional<MidiData> midi;
+
+private:
+
+ const m::Channel& m_channel;
+};
+
+/* getChannels
+Returns a single viewModel object filled with data from a channel. */
+
+Data getData(ID channelId);
+
+/* getChannels
+Returns a vector of viewModel objects filled with data from channels. */
+
+std::vector<Data> getChannels();
+
+/* a_get
+Returns an atomic property from a Channel, by locking it first. */
+
+template <typename T>
+T a_get(const std::atomic<T>& a)
+{
+ m::model::ChannelsLock l(m::model::channels);
+ return a.load();
+}
+
/* addChannel
Adds an empty new channel to the stack. */
/* loadChannel
Fills an existing channel with a wave. */
-int loadChannel(ID channelId, const std::string& fname);
+int loadChannel(ID columnId, const std::string& fname);
/* addAndLoadChannel
Adds a new Sample Channel and fills it with a wave right away. */
void freeChannel(ID channelId);
/* cloneChannel
-Makes an exact copy of Channel *ch. */
+Makes an exact copy of a channel. */
void cloneChannel(ID channelId);
/* set*
Sets several channel properties. */
-void setArm(ID channelId, bool value);
-void toggleArm(ID channelId);
void setInputMonitor(ID channelId, bool value);
-void setMute(ID channelId, bool value);
-void toggleMute(ID channelId);
-void setSolo(ID channelId, bool value);
-void toggleSolo(ID channelId);
-void setVolume(ID channelId, float v, bool gui=true, bool editor=false);
void setName(ID channelId, const std::string& name);
-void setPitch(ID channelId, float val, bool gui=true);
-void setPan(ID channelId, float val, bool gui=true);
-void setSampleMode(ID channelId, ChannelMode m);
-
-void start(ID channelId, int velocity, bool record);
-void kill(ID channelId, bool record);
-void stop(ID channelId);
-
-/* toggleReadingRecs
-Handles the 'R' button. If gui == true the signal comes from an user interaction
-on the GUI, otherwise it's a MIDI/Jack/external signal. */
-
-void toggleReadingActions(ID channelId);
-void startReadingActions(ID channelId);
-void stopReadingActions(ID channelId);
+void setHeight(ID channelId, Pixel p);
+void setSamplePlayerMode(ID channelId, SamplePlayerMode m);
}}}; // giada::c::channel::
#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include <FL/Fl.H>
+#include "core/model/model.h"
+#include "core/const.h"
+#include "core/clock.h"
+#include "core/mixer.h"
+#include "core/midiEvent.h"
+#include "core/pluginHost.h"
+#include "core/sequencer.h"
+#include "core/mixerHandler.h"
+#include "core/conf.h"
+#include "core/recManager.h"
+#include "utils/log.h"
+#include "gui/dialogs/sampleEditor.h"
+#include "gui/dialogs/mainWindow.h"
+#include "gui/dialogs/warnings.h"
+#include "gui/elems/mainWindow/mainIO.h"
+#include "gui/elems/mainWindow/mainTimer.h"
+#include "gui/elems/basics/dial.h"
+#include "gui/elems/mainWindow/keyboard/keyboard.h"
+#include "gui/elems/mainWindow/keyboard/channel.h"
+#include "gui/elems/sampleEditor/volumeTool.h"
+#include "gui/elems/sampleEditor/pitchTool.h"
+#include "gui/elems/sampleEditor/panTool.h"
+#include "glue/sampleEditor.h"
+#include "glue/plugin.h"
+#include "glue/main.h"
+#include "events.h"
+
+
+extern giada::v::gdMainWindow* G_MainWin;
+
+
+namespace giada {
+namespace c {
+namespace events
+{
+namespace
+{
+void pushEvent_(m::mixer::Event e, Thread t)
+{
+ bool res = true;
+ if (t == Thread::MAIN)
+ res = m::mixer::UIevents.push(e);
+ else
+ if (t == Thread::MIDI)
+ res = m::mixer::MidiEvents.push(e);
+ else
+ assert(false);
+
+ if (!res)
+ G_DEBUG("[events] Queue full!\n");
+}
+} // {anonymous}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+void pressChannel(ID channelId, int velocity, Thread t)
+{
+ m::MidiEvent e;
+ e.setVelocity(velocity);
+ pushEvent_({ m::mixer::EventType::KEY_PRESS, 0, {0, channelId, 0, e} }, t);
+}
+
+
+void releaseChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::KEY_RELEASE, 0, {0, channelId} }, t);
+}
+
+
+void killChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::KEY_KILL, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setChannelVolume(ID channelId, float v, Thread t)
+{
+ v = std::clamp(v, 0.0f, G_MAX_VOLUME);
+
+ pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, channelId, 0, {v} } }, t);
+
+ sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.volumeTool->update(v); });
+
+ if (t != Thread::MAIN) {
+ Fl::lock();
+ G_MainWin->keyboard->getChannel(channelId)->vol->value(v);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setChannelPitch(ID channelId, float v, Thread t)
+{
+ v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH);
+
+ pushEvent_({ m::mixer::EventType::CHANNEL_PITCH, 0, { 0, channelId, 0, {v} } }, t);
+
+ sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sendChannelPan(ID channelId, float v)
+{
+ v = std::clamp(v, 0.0f, G_MAX_PAN);
+
+ /* Pan event is currently triggered only by the main thread. */
+ pushEvent_({ m::mixer::EventType::CHANNEL_PAN, 0, { 0, channelId, 0, {v} } }, Thread::MAIN);
+
+ sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); });
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleMuteChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_MUTE, 0, {0, channelId} }, t);
+}
+
+
+void toggleSoloChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_SOLO, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleArmChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_ARM, 0, {0, channelId} }, t);
+}
+
+
+void toggleReadActionsChannel(ID channelId, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, {0, channelId} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::MIDI, 0, {0, channelId, 0, e} }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleMetronome()
+{
+ m::sequencer::toggleMetronome();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void setMasterInVolume(float v, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_IN_CHANNEL_ID, 0, {v} }}, t);
+
+ if (t != Thread::MAIN) {
+ Fl::lock();
+ G_MainWin->mainIO->setInVol(v);
+ Fl::unlock();
+ }
+}
+
+
+void setMasterOutVolume(float v, Thread t)
+{
+ pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_OUT_CHANNEL_ID, 0, {v} }}, t);
+
+ if (t != Thread::MAIN) {
+ Fl::lock();
+ G_MainWin->mainIO->setOutVol(v);
+ Fl::unlock();
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void multiplyBeats()
+{
+ main::setBeats(m::clock::getBeats() * 2, m::clock::getBars());
+}
+
+
+void divideBeats()
+{
+ main::setBeats(m::clock::getBeats() / 2, m::clock::getBars());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void startSequencer(Thread t)
+{
+ pushEvent_({ m::mixer::EventType::SEQUENCER_START, 0 }, t);
+}
+
+
+void stopSequencer(Thread t)
+{
+ pushEvent_({ m::mixer::EventType::SEQUENCER_STOP, 0 }, t);
+}
+
+
+void toggleSequencer(Thread t)
+{
+ m::clock::isRunning() ? stopSequencer(t) : startSequencer(t);
+}
+
+
+void rewindSequencer(Thread t)
+{
+ pushEvent_({ m::mixer::EventType::SEQUENCER_REWIND_REQ, 0 }, t);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void toggleActionRecording()
+{
+ m::recManager::toggleActionRec(m::conf::conf.recTriggerMode);
+}
+
+
+void toggleInputRecording()
+{
+ if (!m::recManager::toggleInputRec(m::conf::conf.recTriggerMode))
+ v::gdAlert("No channels armed/available for audio recording.");
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui)
+{
+ m::pluginHost::setPluginParameter(pluginId, paramIndex, value);
+ c::plugin::updateWindow(pluginId, gui);
+}
+#endif
+}}}; // giada::c::events::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_GLUE_EVENTS_H
+#define G_GLUE_EVENTS_H
+
+
+#include "core/types.h"
+
+
+/* giada::c::events
+Functions that take care of live event dispatching. Every live gesture that
+comes from the UI, MIDI thread or keyboard interaction and wants to change the
+internal engine state must call these functions. */
+
+namespace giada {
+namespace m
+{
+class MidiEvent;
+}
+namespace c {
+namespace events
+{
+/* Channel*
+Channel-related events. */
+
+void pressChannel (ID channelId, int velocity, Thread t);
+void releaseChannel (ID channelId, Thread t);
+void killChannel (ID channelId, Thread t);
+void setChannelVolume (ID channelId, float v, Thread t);
+void setChannelPitch (ID channelId, float v, Thread t);
+void sendChannelPan (ID channelId, float v);
+void toggleMuteChannel (ID channelId, Thread t);
+void toggleSoloChannel (ID channelId, Thread t);
+void toggleArmChannel (ID channelId, Thread t);
+void toggleReadActionsChannel(ID channelId, Thread t);
+void sendMidiToChannel (ID channelId, m::MidiEvent e, Thread t);
+
+/* Main*
+Master I/O, transport and other engine-related events. */
+
+void toggleMetronome ();
+void setMasterInVolume (float v, Thread t);
+void setMasterOutVolume (float v, Thread t);
+void multiplyBeats ();
+void divideBeats ();
+void startSequencer (Thread t);
+void stopSequencer (Thread t);
+void toggleSequencer (Thread t);
+void rewindSequencer (Thread t);
+void toggleActionRecording();
+void toggleInputRecording ();
+
+/* Plug-ins. */
+
+#ifdef WITH_VST
+void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui);
+#endif
+}}} // giada::c::events::
+
+
+#endif
#include "utils/log.h"
#include "utils/math.h"
#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/channel.h"
-#include "core/channels/midiChannel.h"
#include "core/recorder.h"
#include "core/conf.h"
#include "core/recManager.h"
{
namespace
{
-void refreshMidiWindows_()
+void rebuildMidiWindows_()
{
- Fl::lock();
- u::gui::refreshSubWindow(WID_MIDI_INPUT);
- u::gui::refreshSubWindow(WID_MIDI_OUTPUT);
- Fl::unlock();
+ u::gui::rebuildSubWindow(WID_MIDI_INPUT);
+ u::gui::rebuildSubWindow(WID_MIDI_OUTPUT);
}
} // {anonymous}
/* -------------------------------------------------------------------------- */
-void keyPress(ID channelId, bool ctrl, bool shift, int velocity)
+Channel_InputData::Channel_InputData(const m::Channel& c)
+: channelId (c.id)
+, channelType (c.getType())
+, enabled (c.midiLearner.state->enabled.load())
+, velocityAsVol(c.samplePlayer ? c.samplePlayer->state->velocityAsVol.load() : 0)
+, filter (c.midiLearner.state->filter.load())
+, keyPress (c.midiLearner.state->keyPress.load())
+, keyRelease (c.midiLearner.state->keyRelease.load())
+, kill (c.midiLearner.state->kill.load())
+, arm (c.midiLearner.state->arm.load())
+, volume (c.midiLearner.state->volume.load())
+, mute (c.midiLearner.state->mute.load())
+, solo (c.midiLearner.state->solo.load())
+, pitch (c.midiLearner.state->pitch.load())
+, readActions (c.midiLearner.state->readActions.load())
{
- if (ctrl)
- c::channel::toggleMute(channelId);
- else
- if (shift)
- c::channel::kill(channelId, /*record=*/true);
- else
- c::channel::start(channelId, velocity, /*record=*/true);
+#ifdef WITH_VST
+ for (ID id : c.pluginIds) {
+ m::Plugin& p = m::model::get(m::model::plugins, id);
+
+ PluginData pd;
+ pd.id = p.id;
+ pd.name = p.getName();
+ for (int i = 0; i < p.getNumParameters(); i++)
+ pd.params.push_back({ i, p.getParameterName(i), p.midiInParams.at(i) });
+
+ plugins.push_back(pd);
+ }
+#endif
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s)
+: enabled(s.state->enabled.load())
+, filter (s.state->filter.load())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel_OutputData::Channel_OutputData(const m::Channel& c)
+: channelId (c.id)
+, lightningEnabled(c.midiLighter.state->enabled.load())
+, lightningPlaying(c.midiLighter.state->playing.load())
+, lightningMute (c.midiLighter.state->mute.load())
+, lightningSolo (c.midiLighter.state->solo.load())
+{
+ if (c.getType() == ChannelType::MIDI)
+ output = std::make_optional<MidiChannel_OutputData>(*c.midiSender);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Master_InputData::Master_InputData(const m::model::MidiIn& midiIn)
+: enabled (midiIn.enabled)
+, filter (midiIn.filter)
+, rewind (midiIn.rewind)
+, startStop (midiIn.startStop)
+, actionRec (midiIn.actionRec)
+, inputRec (midiIn.inputRec)
+, volumeIn (midiIn.volumeIn)
+, volumeOut (midiIn.volumeOut)
+, beatDouble(midiIn.beatDouble)
+, beatHalf (midiIn.beatHalf)
+, metronome (midiIn.metronome)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Channel_InputData channel_getInputData(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+#ifdef WITH_VST
+ mm::PluginsLock ml(mm::plugins);
+#endif
+
+ return Channel_InputData(mm::get(mm::channels, channelId));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Channel_OutputData channel_getOutputData(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ return Channel_OutputData(mm::get(mm::channels, channelId));
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Master_InputData master_getInputData()
+{
+ namespace mm = m::model;
+
+ mm::MidiInLock l(mm::midiIn);
+ return Master_InputData(*mm::midiIn.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_enableMidiLearn(ID channelId, bool v)
+{
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.midiLearner.state->enabled.store(v);
+ });
+ rebuildMidiWindows_();
}
/* -------------------------------------------------------------------------- */
-void keyRelease(ID channelId, bool ctrl, bool shift)
+void channel_enableMidiLightning(ID channelId, bool v)
{
- if (!ctrl && !shift)
- c::channel::stop(channelId);
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.midiLighter.state->enabled.store(v);
+ });
+ rebuildMidiWindows_();
}
/* -------------------------------------------------------------------------- */
-void setSampleChannelKey(ID channelId, int k)
+void channel_enableMidiOutput(ID channelId, bool v)
{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- c.key = k;
+ c.midiSender->state->enabled.store(v);
+ });
+ rebuildMidiWindows_();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_enableVelocityAsVol(ID channelId, bool v)
+{
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.samplePlayer->state->velocityAsVol.store(v);
});
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_setMidiInputFilter(ID channelId, int ch)
+{
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.midiLearner.state->filter.store(ch);
+ });
+}
+
- Fl::lock();
- G_MainWin->keyboard->getChannel(channelId)->mainButton->setKey(k);
- Fl::unlock();
+void channel_setMidiOutputFilter(ID channelId, int ch)
+{
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.midiSender->state->filter.store(ch);
+ });
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void channel_setKey(ID channelId, int k)
+{
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ {
+ c.state->key.store(k);
+ }, /*rebuild=*/true);
}
/* -------------------------------------------------------------------------- */
-void startChannelMidiLearn(int param, ID channelId)
+void channel_startMidiLearn(int param, ID channelId)
{
- m::midiDispatcher::startChannelLearn(param, channelId, refreshMidiWindows_);
+ m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_);
}
-void startMasterMidiLearn(int param)
+void master_startMidiLearn(int param)
{
- m::midiDispatcher::startMasterLearn(param, refreshMidiWindows_);
+ m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_);
}
#ifdef WITH_VST
-void startPluginMidiLearn(int paramIndex, ID pluginId)
+void plugin_startMidiLearn(int paramIndex, ID pluginId)
{
- m::midiDispatcher::startPluginLearn(paramIndex, pluginId, refreshMidiWindows_);
+ m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_);
}
#endif
void stopMidiLearn()
{
m::midiDispatcher::stopLearn();
- refreshMidiWindows_();
+ rebuildMidiWindows_();
}
/* -------------------------------------------------------------------------- */
-void clearChannelMidiLearn(int param, ID channelId)
+void channel_clearMidiLearn(int param, ID channelId)
{
- m::midiDispatcher::clearChannelLearn(param, channelId, refreshMidiWindows_);
+ m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_);
}
-void clearMasterMidiLearn (int param)
+void master_clearMidiLearn (int param)
{
- m::midiDispatcher::clearMasterLearn(param, refreshMidiWindows_);
+ m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_);
}
#ifdef WITH_VST
-void clearPluginMidiLearn (int param, ID pluginId)
+void plugin_clearMidiLearn (int param, ID pluginId)
{
- m::midiDispatcher::clearPluginLearn(param, pluginId, refreshMidiWindows_);
+ m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_);
}
#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void master_enableMidiLearn(bool v)
+{
+ m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
+ {
+ m.enabled = v;
+ });
+ rebuildMidiWindows_();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void master_setMidiFilter(int c)
+{
+ m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
+ {
+ m.filter = c;
+ });
+}
}}} // giada::c::io::
namespace c {
namespace io
{
-/* keyPress / keyRelease
-Handle the key pressure, either via mouse/keyboard or MIDI. */
+struct PluginParamData
+{
+ int index;
+ std::string name;
+ uint32_t value;
+};
+
+struct PluginData
+{
+ ID id;
+ std::string name;
+ std::vector<PluginParamData> params;
+};
+
+struct Channel_InputData
+{
+ Channel_InputData() = default;
+ Channel_InputData(const m::Channel&);
+
+ ID channelId;
+ ChannelType channelType;
+ bool enabled;
+ bool velocityAsVol;
+ int filter;
+
+ uint32_t keyPress;
+ uint32_t keyRelease;
+ uint32_t kill;
+ uint32_t arm;
+ uint32_t volume;
+ uint32_t mute;
+ uint32_t solo;
+ uint32_t pitch;
+ uint32_t readActions;
+
+ std::vector<PluginData> plugins;
+};
+
+struct Master_InputData
+{
+ Master_InputData() = default;
+ Master_InputData(const m::model::MidiIn&);
+
+ bool enabled;
+ int filter;
+
+ uint32_t rewind;
+ uint32_t startStop;
+ uint32_t actionRec;
+ uint32_t inputRec;
+ uint32_t volumeIn;
+ uint32_t volumeOut;
+ uint32_t beatDouble;
+ uint32_t beatHalf;
+ uint32_t metronome;
+};
-void keyPress (ID channelId, bool ctrl, bool shift, int velocity);
-void keyRelease(ID channelId, bool ctrl, bool shift);
+struct MidiChannel_OutputData
+{
+ MidiChannel_OutputData(const m::MidiSender&);
+
+ bool enabled;
+ int filter;
+};
+
+struct Channel_OutputData
+{
+ Channel_OutputData() = default;
+ Channel_OutputData(const m::Channel&);
+
+ ID channelId;
+ bool lightningEnabled;
+ uint32_t lightningPlaying;
+ uint32_t lightningMute;
+ uint32_t lightningSolo;
+
+ std::optional<MidiChannel_OutputData> output;
+};
-/* setSampleChannelKey
+Channel_InputData channel_getInputData(ID channelId);
+Channel_OutputData channel_getOutputData(ID channelId);
+Master_InputData master_getInputData();
+
+/* Channel functions. */
+
+void channel_enableMidiLearn(ID channelId, bool v);
+void channel_enableMidiLightning(ID channelId, bool v);
+void channel_enableMidiOutput(ID channelId, bool v);
+void channel_enableVelocityAsVol(ID channelId, bool v);
+void channel_setMidiInputFilter(ID channelId, int c);
+void channel_setMidiOutputFilter(ID channelId, int c);
+
+/* channel_setKey
Set key 'k' to Sample Channel 'channelId'. Used for keyboard bindings. */
-void setSampleChannelKey(ID channelId, int k);
+void channel_setKey(ID channelId, int k);
-void startChannelMidiLearn(int param, ID channelId);
-void startMasterMidiLearn (int param);
+/* MIDI Learning functions. */
+
+void channel_startMidiLearn(int param, ID channelId);
+void channel_clearMidiLearn(int param, ID channelId);
+void master_clearMidiLearn (int param);
+void master_startMidiLearn (int param);
void stopMidiLearn();
-void clearChannelMidiLearn(int param, ID channelId);
-void clearMasterMidiLearn (int param);
#ifdef WITH_VST
-void startPluginMidiLearn (int paramIndex, ID pluginId);
-void clearPluginMidiLearn (int param, ID pluginId);
+void plugin_startMidiLearn (int paramIndex, ID pluginId);
+void plugin_clearMidiLearn (int param, ID pluginId);
#endif
+
+/* Master functions. */
+
+void master_enableMidiLearn(bool v);
+void master_setMidiFilter(int c);
}}} // giada::c::io::
#endif
#include "utils/string.h"
#include "utils/log.h"
#include "core/model/model.h"
-#include "core/channels/midiChannel.h"
#include "core/mixerHandler.h"
#include "core/mixer.h"
#include "core/clock.h"
float previous = m::clock::getBpm();
m::clock::setBpm(current);
- m::recorderHandler::updateBpm(previous, current, m::clock::getQuanto());
- m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
+ m::recorderHandler::updateBpm(previous, current, m::clock::getQuantizerStep());
+ m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
/* This function might get called by Jack callback BEFORE the UI is up
and running, that is when G_MainWin == nullptr. */
/* -------------------------------------------------------------------------- */
+Timer::Timer(const m::model::Clock& c)
+: bpm (c.bpm)
+, beats (c.beats)
+, bars (c.bars)
+, quantize (c.quantize)
+, isUsingJack (m::kernelAudio::getAPI() == G_SYS_API_JACK)
+, isRecordingInput(m::recManager::isRecordingInput())
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m)
+: masterOutVol (out.state->volume.load())
+, masterInVol (in.state->volume.load())
+#ifdef WITH_VST
+, masterOutHasPlugins(out.pluginIds.size() > 0)
+, masterInHasPlugins (in.pluginIds.size() > 0)
+#endif
+, inToOut (m.inToOut)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+float IO::a_getMasterOutPeak()
+{
+ return m::mixer::peakOut.load();
+}
+
+
+float IO::a_getMasterInPeak()
+{
+ return m::mixer::peakIn.load();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Timer getTimer()
+{
+ namespace mm = m::model;
+
+ mm::ClockLock c(mm::clock);
+ return Timer(*mm::clock.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+IO getIO()
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ mm::MixerLock ml(mm::mixer);
+
+ return IO(mm::get(mm::channels, m::mixer::MASTER_OUT_CHANNEL_ID),
+ mm::get(mm::channels, m::mixer::MASTER_IN_CHANNEL_ID),
+ *mm::mixer.get());
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void setBpm(const char* v1, const char* v2)
{
/* Never change this stuff while recording audio. */
return;
m::clock::setBeats(beats, bars);
- m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
+ m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
G_MainWin->mainTimer->setMeter(m::clock::getBeats(), m::clock::getBars());
u::gui::refreshActionEditor(); // in case the action editor is open
/* -------------------------------------------------------------------------- */
-void setOutVol(float v, bool gui)
-{
- m::mh::setOutVol(v);
-
- if (!gui) {
- Fl::lock();
- G_MainWin->mainIO->setOutVol(v);
- Fl::unlock();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setInVol(float v, bool gui)
-{
- m::mh::setInVol(v);
-
- if (!gui) {
- Fl::lock();
- G_MainWin->mainIO->setInVol(v);
- Fl::unlock();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void clearAllSamples()
{
- if (!v::gdConfirmWin("Warning", "Clear all samples: are you sure?"))
+ if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?"))
return;
G_MainWin->delSubWindow(WID_SAMPLE_EDITOR);
m::clock::setStatus(ClockStatus::STOPPED);
/* -------------------------------------------------------------------------- */
-void resetToInitState(bool createColumns)
-{
- if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
- return;
- m::init::reset();
- m::mixer::enable();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void beatsMultiply()
-{
- setBeats(m::clock::getBeats() * 2, m::clock::getBars());
-}
-
-void beatsDivide()
-{
- setBeats(m::clock::getBeats() / 2, m::clock::getBars());
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleInputRec()
+void setInToOut(bool v)
{
- if (!m::recManager::toggleInputRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode)))
- v::gdAlert("No channels armed/available for audio recording.");
+ m::mh::setInToOut(v);
}
/* -------------------------------------------------------------------------- */
-void toggleActionRec()
+void closeProject(bool createColumns)
{
- m::recManager::isRecordingAction() ? stopActionRec() : startActionRec();
-}
-
-
-void startActionRec()
-{
- m::recManager::startActionRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode));
-}
-
-
-void stopActionRec()
-{
- m::recManager::stopActionRec();
- u::gui::refreshActionEditor(); // If Action Editor window is open
+ if (!v::gdConfirmWin("Warning", "Close project: are you sure?"))
+ return;
+ m::init::reset();
+ m::mixer::enable();
}
-
}}} // giada::c::main::
namespace giada {
+namespace m
+{
+struct Channel;
+namespace model
+{
+struct Clock;
+struct Mixer;
+}}
namespace c {
namespace main
{
+struct Timer
+{
+ Timer() = default;
+ Timer(const m::model::Clock& c);
+
+ float bpm;
+ int beats;
+ int bars;
+ int quantize;
+ bool isUsingJack;
+ bool isRecordingInput;
+};
+
+struct IO
+{
+ IO() = default;
+ IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m);
+
+ float masterOutVol;
+ float masterInVol;
+#ifdef WITH_VST
+ bool masterOutHasPlugins;
+ bool masterInHasPlugins;
+#endif
+ bool inToOut;
+
+ float a_getMasterOutPeak();
+ float a_getMasterInPeak();
+};
+
+/* get*
+Returns viewModel objects filled with data. */
+
+Timer getTimer();
+IO getIO();
+
/* setBpm (1)
Sets bpm value from string to float. */
void setBeats(int beats, int bars);
void quantize(int val);
-void setOutVol(float v, bool gui=true);
-void setInVol(float v, bool gui=true);
void clearAllSamples();
void clearAllActions();
-/* resetToInitState
-Resets Giada to init state. If resetGui also refresh all widgets. If
-createColumns also build initial empty columns. */
-
-void resetToInitState(bool createColumns);
-
-/* beatsDivide/Multiply
-Shrinks or enlarges the number of beats by 2. */
-
-void beatsMultiply();
-void beatsDivide();
-
+/* setInToOut
+Enables the "hear what you playing" feature. */
+void setInToOut(bool v);
+/* closeProject
+Resets Giada to init state. If resetGui also refresh all widgets. If
+createColumns also build initial empty columns. */
-
-
-
-
-
-void rewind();
-void play();
-
-/* toggleInputRec
-Handles the input recording.*/
-
-void toggleInputRec();
-
-void toggleActionRec();
-void startActionRec();
-void stopActionRec();
-
-
-void toggleMetronome();
-
+void closeProject(bool createColumns);
}}} // giada::c::main::
#endif
#include <cassert>
#include <FL/Fl.H>
#include "core/model/model.h"
-#include "core/channels/channel.h"
#include "core/pluginManager.h"
#include "core/pluginHost.h"
#include "core/mixer.h"
namespace c {
namespace plugin
{
-namespace
+Param::Param(const m::Plugin& p, int index)
+: index (index)
+, pluginId(p.id)
+, name (p.getParameterName(index))
+, text (p.getParameterText(index))
+, label (p.getParameterLabel(index))
+, value (p.getParameter(index))
{
-void updatePluginEditor_(ID pluginId, bool gui)
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugin::Plugin(const m::Plugin& p, ID channelId)
+: id (p.id)
+, channelId (channelId)
+, valid (p.valid)
+, hasEditor (p.hasEditor())
+, isBypassed (p.isBypassed())
+, name (p.getName())
+, uniqueId (p.getUniqueId())
+, currentProgram(p.getCurrentProgram())
+, m_plugin (p)
+{
+ for (int i = 0; i < p.getNumPrograms(); i++)
+ programs.push_back({ i, p.getProgramName(i) });
+ for (int i = 0; i < p.getNumParameters(); i++)
+ paramIndexes.push_back(i);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+juce::AudioProcessorEditor* Plugin::createEditor() const
+{
+ m::model::PluginsLock l(m::model::plugins);
+ return m_plugin.createEditor();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugins::Plugins(const m::Channel& c)
+: channelId(c.id)
+, pluginIds(c.pluginIds)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Plugins getPlugins(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ return Plugins(mm::get(mm::channels, channelId));
+}
+
+
+Plugin getPlugin(ID pluginId, ID channelId)
+{
+ m::model::PluginsLock l(m::model::plugins);
+ return Plugin(m::model::get(m::model::plugins, pluginId), channelId);
+}
+
+
+Param getParam (int index, ID pluginId)
+{
+ m::model::PluginsLock l(m::model::plugins);
+ return Param(m::model::get(m::model::plugins, pluginId), index);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void updateWindow(ID pluginId, bool gui)
{
m::model::PluginsLock l(m::model::plugins);
const m::Plugin& p = m::model::get(m::model::plugins, pluginId);
child->updateParameters(!gui);
if (!gui) Fl::unlock();
}
-} // {anonymous}
-/* -------------------------------------------------------------------------- */
-/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
void setProgram(ID pluginId, int programIndex)
{
m::pluginHost::setPluginProgram(pluginId, programIndex);
- updatePluginEditor_(pluginId, /*gui=*/true);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void setParameter(ID pluginId, int paramIndex, float value, bool gui)
-{
- m::pluginHost::setPluginParameter(pluginId, paramIndex, value);
- updatePluginEditor_(pluginId, gui);
+ updateWindow(pluginId, /*gui=*/true);
}
#ifdef WITH_VST
+#include <vector>
+#include <string>
#include "core/pluginHost.h"
#include "core/types.h"
+namespace juce {
+class AudioProcessorEditor;
+}
+
+
namespace giada {
namespace m
{
namespace c {
namespace plugin
{
-void addPlugin(int pluginListIndex, ID channelId);
+struct Program
+{
+ int index;
+ std::string name;
+};
-void swapPlugins(ID pluginId1, ID pluginId2, ID channelId);
+struct Param
+{
+ Param() = default;
+ Param(const m::Plugin&, int index);
+
+ int index;
+ ID pluginId;
+ std::string name;
+ std::string text;
+ std::string label;
+ float value;
+};
+
+struct Plugin
+{
+ Plugin() = default;
+ Plugin(const m::Plugin&, ID channelId);
-void freePlugin(ID pluginId, ID channelId);
+ juce::AudioProcessorEditor* createEditor() const;
-void setParameter(ID pluginId, int paramIndex, float value, bool gui=true);
+ ID id;
+ ID channelId;
+ bool valid;
+ bool hasEditor;
+ bool isBypassed;
+ std::string name;
+ std::string uniqueId;
+ int currentProgram;
-void setProgram(ID pluginId, int programIndex);
+ std::vector<Program> programs;
+ std::vector<int> paramIndexes;
+
+private:
+
+ const m::Plugin& m_plugin;
+};
+struct Plugins
+{
+ Plugins() = default;
+ Plugins(const m::Channel&);
+
+ ID channelId;
+ std::vector<ID> pluginIds;
+};
+
+/* get*
+Returns ViewModel objects. */
+
+Plugins getPlugins(ID channelId);
+Plugin getPlugin (ID pluginId, ID channelId);
+Param getParam (int index, ID pluginId);
+
+/* updateWindow
+Updates the editor-less plug-in window. This is useless if the plug-in has an
+editor. */
+
+void updateWindow(ID pluginId, bool gui);
+
+void addPlugin(int pluginListIndex, ID channelId);
+void swapPlugins(ID pluginId1, ID pluginId2, ID channelId);
+void freePlugin(ID pluginId, ID channelId);
+void setProgram(ID pluginId, int programIndex);
void toggleBypass(ID pluginId);
/* setPluginPathCb
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/keyboard/sampleChannel.h"
#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/const.h"
#include "core/clock.h"
#include "core/model/model.h"
void updateChannel(ID channelId, bool updateActionEditor)
{
- /* TODO - optimization needed. This functions swaps a channel only to set a
- boolean flag. Query the channel first and swap it only if the flag has
- actually changed. */
-
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- c.hasActions = m::recorder::hasActions(channelId);
+ c.state->hasActions = m::recorder::hasActions(channelId);
});
if (updateActionEditor)
#include <cassert>
#include <FL/Fl.H>
+#include "glue/events.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/sampleEditor.h"
#include "gui/dialogs/warnings.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/waveFx.h"
#include "core/wave.h"
#include "core/waveManager.h"
#include "core/mixerHandler.h"
std::unique_ptr<m::Wave> waveBuffer_;
+Frame previewTracker_ = 0;
+
/* -------------------------------------------------------------------------- */
void resetBeginEnd_(ID channelId)
{
- Frame begin;
- Frame end;
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
{
- begin = static_cast<m::SampleChannel&>(c).begin;
- end = static_cast<m::SampleChannel&>(c).end;
+ Frame begin = c.samplePlayer->state->begin.load();
+ Frame end = c.samplePlayer->state->end.load();
+ setBeginEnd(channelId, begin, end);
});
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* updateWavePtr_
+Updates the Wave pointer in Channel::WaveReader. */
+
+void updateWavePtr_(ID channelId, ID waveId)
+{
+ namespace mm = m::model;
+
+ mm::WavesLock wl(mm::waves);
+ const m::Wave& wave = mm::get(mm::waves, waveId);
- setBeginEnd(channelId, begin, end);
+ mm::onSwap(mm::channels, channelId, [&](m::Channel& c)
+ {
+ c.samplePlayer->loadWave(&wave);
+ });
}
}; // {anonymous}
/* -------------------------------------------------------------------------- */
+Data::Data(const m::Channel& c, const m::Wave& w)
+: channelId (c.id)
+, waveId (w.id)
+, name (c.state->name)
+, volume (c.state->volume.load())
+, pan (c.state->pan.load())
+, pitch (c.samplePlayer->state->pitch.load())
+, begin (c.samplePlayer->state->begin.load())
+, end (c.samplePlayer->state->end.load())
+, shift (c.samplePlayer->state->shift.load())
+, waveSize (w.getSize())
+, waveBits (w.getBits())
+, waveDuration(w.getDuration())
+, waveRate (w.getRate())
+, wavePath (w.getPath())
+, isLogical (w.isLogical())
+{
+}
+
+/* TODO - use c::channel::a_get() */
+ChannelStatus Data::a_getPreviewStatus() const
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock l(mm::channels);
+ return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load();
+}
+
+/* TODO - use c::channel::a_get() */
+Frame Data::a_getPreviewTracker() const
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock l(mm::channels);
+ return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).samplePlayer->state->tracker.load();
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+Data getData(ID channelId)
+{
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ mm::WavesLock wl(mm::waves);
+
+ const m::Channel& channel = mm::get(mm::channels, channelId);
+ const m::Wave& wave = mm::get(mm::waves, channel.samplePlayer->getWaveId());
+
+ /* Prepare the preview channel. */
+
+ m::Channel& preview = mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID);
+ preview.samplePlayer->loadWave(&wave);
+
+ return Data(channel, wave);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f)
+{
+ v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
+ if (se == nullptr)
+ return;
+ if (!gui) Fl::lock();
+ f(*se);
+ if (!gui) Fl::unlock();
+}
+
+
v::gdSampleEditor* getSampleEditorWindow()
{
v::gdSampleEditor* se = static_cast<v::gdSampleEditor*>(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR));
void setBeginEnd(ID channelId, int b, int e)
{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- static_cast<m::SampleChannel&>(c).setBegin(b);
- static_cast<m::SampleChannel&>(c).setEnd(e);
+ b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1);
+ e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1);
+ if (b >= e) b = e - 1;
+ else if (e < b) e = b + 1;
+
+ c.samplePlayer->state->begin.store(b);
+ c.samplePlayer->state->end.store(e);
+ if (c.samplePlayer->state->tracker.load() < b)
+ c.samplePlayer->state->tracker.store(b);
});
+ /* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */
getSampleEditorWindow()->rebuild();
}
{
copy(waveId, a, b);
m::wfx::cut(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
resetBeginEnd_(channelId);
}
void copy(ID waveId, int a, int b)
{
m::model::WavesLock lock(m::model::waves);
-
waveBuffer_ = m::waveManager::createFromWave(m::model::get(m::model::waves, waveId), a, b);
}
}
m::wfx::paste(*waveBuffer_, waveId, a);
+ updateWavePtr_(channelId, waveId);
/* Shift begin/end points to keep the previous position. */
m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- begin = static_cast<m::SampleChannel&>(c).begin;
- end = static_cast<m::SampleChannel&>(c).end;
+ begin = c.samplePlayer->state->begin.load();
+ end = c.samplePlayer->state->end.load();
});
if (a < begin && a < end)
/* -------------------------------------------------------------------------- */
-void silence(ID waveId, int a, int b)
+void silence(ID channelId, ID waveId, int a, int b)
{
m::wfx::silence(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
}
/* -------------------------------------------------------------------------- */
-void fade(ID waveId, int a, int b, int type)
+void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type)
{
m::wfx::fade(waveId, a, b, type);
+ updateWavePtr_(channelId, waveId);
}
/* -------------------------------------------------------------------------- */
-void smoothEdges(ID waveId, int a, int b)
+void smoothEdges(ID channelId, ID waveId, int a, int b)
{
m::wfx::smooth(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
}
/* -------------------------------------------------------------------------- */
-void reverse(ID waveId, int a, int b)
+void reverse(ID channelId, ID waveId, int a, int b)
{
m::wfx::reverse(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
}
/* -------------------------------------------------------------------------- */
-void normalizeHard(ID waveId, int a, int b)
+void normalize(ID channelId, ID waveId, int a, int b)
{
- m::wfx::normalizeHard(waveId, a, b);
+ m::wfx::normalize(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
}
void trim(ID channelId, ID waveId, int a, int b)
{
m::wfx::trim(waveId, a, b);
+ updateWavePtr_(channelId, waveId);
resetBeginEnd_(channelId);
}
/* -------------------------------------------------------------------------- */
-void setPlayHead(ID channelId, Frame f)
-{
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- static_cast<m::SampleChannel&>(c).trackerPreview = f;
- });
- getSampleEditorWindow()->refresh();
+/* TODO - this arcane logic of keeping previewTracker_ will go away as soon as
+the One-shot pause mode is implemented:
+ https://github.com/monocasual/giada/issues/88 */
+
+void playPreview(bool loop)
+{
+ setPreviewTracker(previewTracker_);
+ channel::setSamplePlayerMode(m::mixer::PREVIEW_CHANNEL_ID, loop ? SamplePlayerMode::SINGLE_ENDLESS : SamplePlayerMode::SINGLE_BASIC);
+ events::pressChannel(m::mixer::PREVIEW_CHANNEL_ID, G_MAX_VELOCITY, Thread::MAIN);
}
-/* -------------------------------------------------------------------------- */
+void stopPreview()
+{
+ /* Let the Sample Editor show the initial tracker position, then kill the
+ channel. */
+ setPreviewTracker(previewTracker_);
+ getSampleEditorWindow()->refresh();
+ events::killChannel(m::mixer::PREVIEW_CHANNEL_ID, Thread::MAIN);
+}
-void setPreview(ID channelId, PreviewMode mode)
+void setPreviewTracker(Frame f)
{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ namespace mm = m::model;
+
+ mm::onGet(mm::channels, m::mixer::PREVIEW_CHANNEL_ID, [&](m::Channel& c)
{
- static_cast<m::SampleChannel&>(c).previewMode = mode;
+ c.samplePlayer->state->tracker.store(f);
});
-}
+ previewTracker_ = f;
-/* -------------------------------------------------------------------------- */
+ getSampleEditorWindow()->refresh();
+}
-void rewindPreview(ID channelId)
+void cleanupPreview()
{
- setPlayHead(channelId, 0);
+ namespace mm = m::model;
+
+ mm::ChannelsLock cl(mm::channels);
+ mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).samplePlayer->loadWave(nullptr);
}
/* -------------------------------------------------------------------------- */
-void toNewChannel(ID channelId, int a, int b)
+void toNewChannel(ID channelId, ID waveId, int a, int b)
{
ID columnId = G_MainWin->keyboard->getChannel(channelId)->getColumnId();
- ID waveId;
-
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- waveId = static_cast<m::SampleChannel&>(c).waveId;
- });
m::model::onGet(m::model::waves, waveId, [&](m::Wave& w)
{
return;
std::string wavePath;
- Frame waveSize;
- m::model::onGet(m::model::waves, waveId, [&](m::Wave& w)
+ m::model::onGet(m::model::waves, waveId, [&](const m::Wave& w)
{
wavePath = w.getPath();
- waveSize = w.getSize();
});
- if (channel::loadChannel(channelId, wavePath) != G_RES_OK)
+ if (channel::loadChannel(channelId, wavePath) != G_RES_OK) {
+ v::gdAlert("Unable to reload sample!");
return;
+ }
- ID newWaveId;
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
- {
- m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
- newWaveId = sc.waveId;
- });
-
- getSampleEditorWindow()->setWaveId(newWaveId);
getSampleEditorWindow()->rebuild();
}
void shift(ID channelId, ID waveId, int offset)
{
Frame shift;
-
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c)
{
- shift = static_cast<m::SampleChannel&>(c).shift;
+ shift = c.samplePlayer->state->shift.load();
});
m::wfx::shift(waveId, offset - shift);
+ updateWavePtr_(channelId, waveId);
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- static_cast<m::SampleChannel&>(c).shift = offset;
+ c.samplePlayer->state->shift.store(offset);
});
+
+ getSampleEditorWindow()->shiftTool->update(offset);
}
}}}; // giada::c::sampleEditor::
#define G_GLUE_SAMPLE_EDITOR_H
+#include <functional>
+#include <string>
#include "core/types.h"
+#include "core/waveFx.h"
namespace giada {
+namespace m
+{
+class Channel;
+class Wave;
+}
+namespace v
+{
+class gdSampleEditor;
+}
namespace c {
namespace sampleEditor
{
+struct Data
+{
+ Data() = default;
+ Data(const m::Channel&, const m::Wave&);
+
+ ChannelStatus a_getPreviewStatus() const;
+ Frame a_getPreviewTracker() const;
+
+ ID channelId;
+ ID waveId;
+ std::string name;
+ float volume;
+ float pan;
+ float pitch;
+ Frame begin;
+ Frame end;
+ Frame shift;
+ Frame waveSize;
+ int waveBits;
+ int waveDuration;
+ int waveRate;
+ std::string wavePath;
+ bool isLogical;
+};
+
+/* onRefresh --- TODO - wrong name */
+
+void onRefresh(bool gui, std::function<void(v::gdSampleEditor&)> f);
+
+/* getData
+Returns a Data object filled with data from a channel. */
+
+Data getData(ID channelId);
+
/* setBeginEnd
Sets start/end points in the sample editor. */
void paste(ID channelId, ID waveId, int a);
void trim(ID channelId, ID waveId, int a, int b);
-void reverse(ID waveId, int a, int b);
-void normalizeHard(ID waveId, int a, int b);
-void silence(ID waveId, int a, int b);
-void fade(ID waveId, int a, int b, int type);
-void smoothEdges(ID waveId, int a, int b);
+void reverse(ID channelId, ID waveId, int a, int b);
+void normalize(ID channelId, ID waveId, int a, int b);
+void silence(ID channelId, ID waveId, int a, int b);
+void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type);
+void smoothEdges(ID channelId, ID waveId, int a, int b);
void shift(ID channelId, ID waveId, int offset);
void reload(ID channelId, ID waveId);
bool isWaveBufferFull();
-/* setPlayHead
-Changes playhead's position. Used in preview. */
-
-void setPlayHead(ID channelId, Frame f);
-
-void setPreview(ID channelId, PreviewMode mode);
-void rewindPreview(ID channelId);
+void playPreview(bool loop);
+void stopPreview();
+void setPreviewTracker(Frame f);
+void cleanupPreview();
/* toNewChannel
Copies the selected range into a new sample channel. */
-void toNewChannel(ID channelId, int a, int b);
+void toNewChannel(ID channelId, ID waveId, int a, int b);
}}}; // giada::c::sampleEditor::
#endif
#include <cassert>
#include "core/model/model.h"
#include "core/model/storage.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/mixer.h"
#include "core/wave.h"
#include "core/mixerHandler.h"
if (isWavePathUnique_(w, path))
return path;
+ // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp())
int k = 0;
path = makeWavePath_(base, w, k);
while (!isWavePathUnique_(w, path))
/* -------------------------------------------------------------------------- */
-void saveWavesToProject_(const std::string& base)
+void saveWavesToProject_(const std::string& basePath)
{
- for (size_t i = 0; i < m::model::waves.size(); i++) {
- m::model::onSwap(m::model::waves, m::model::getId(m::model::waves, i), [&](m::Wave& w)
- {
- w.setPath(makeUniqueWavePath_(base, w));
- m::waveManager::save(w, w.getPath()); // TODO - error checking
- });
+ /* No need for a hard Wave swap here: nobody is reading the path data. */
+
+ m::model::WavesLock l(m::model::waves);
+
+ for (m::Wave* w : m::model::waves) {
+ w->setPath(makeUniqueWavePath_(basePath, *w));
+ m::waveManager::save(*w, w->getPath()); // TODO - error checking
}
}
} // {anonymous}
void loadProject(void* data)
{
- v::gdBrowserLoad* browser = (v::gdBrowserLoad*) data;
+ v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
std::string fullPath = browser->getSelectedItem();
bool isProject = u::fs::isProject(browser->getSelectedItem());
the current samplerate != patch samplerate. Clock needs to update frames
in sequencer. */
- m::mixer::allocVirtualInput(m::clock::getFramesInLoop());
m::mh::updateSoloCount();
m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate);
m::clock::recomputeFrames();
+ m::mixer::allocRecBuffer(m::clock::getFramesInLoop());
/* Mixer is ready to go back online. */
void saveProject(void* data)
{
- v::gdBrowserSave* browser = (v::gdBrowserSave*) data;
- std::string name = u::fs::stripExt(browser->getName());
- std::string folderPath = browser->getCurrentPath();
- std::string fullPath = folderPath + G_SLASH + name + ".gprj";
- std::string gptcPath = fullPath + G_SLASH + name + ".gptc";
+ v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
+ std::string name = u::fs::stripExt(browser->getName());
+ std::string folderPath = browser->getCurrentPath();
+ std::string fullPath = folderPath + G_SLASH + name + ".gprj";
+ std::string gptcPath = fullPath + G_SLASH + name + ".gptc";
if (name == "") {
v::gdAlert("Please choose a project name.");
void loadSample(void* data)
{
- v::gdBrowserLoad* browser = (v::gdBrowserLoad*) data;
+ v::gdBrowserLoad* browser = static_cast<v::gdBrowserLoad*>(data);
std::string fullPath = browser->getSelectedItem();
if (fullPath.empty())
void saveSample(void* data)
{
- v::gdBrowserSave* browser = (v::gdBrowserSave*) data;
+ v::gdBrowserSave* browser = static_cast<v::gdBrowserSave*>(data);
std::string name = browser->getName();
std::string folderPath = browser->getCurrentPath();
+ ID channelId = browser->getChannelId();
if (name == "") {
v::gdAlert("Please choose a file name.");
return;
ID waveId;
- m::model::onGet(m::model::channels, browser->getChannelId(), [&](m::Channel& c)
+ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
{
- waveId = static_cast<m::SampleChannel&>(c).waveId;
+ waveId = c.samplePlayer->getWaveId();
});
- size_t waveIndex = m::model::getIndex(m::model::waves, waveId);
+ std::size_t waveIndex = m::model::getIndex(m::model::waves, waveId);
std::unique_ptr<m::Wave> wave = m::model::waves.clone(waveIndex);
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "../gui/elems/mainWindow/mainTransport.h"
-#include "../gui/dialogs/mainWindow.h"
-#include "../core/clock.h"
-#include "../core/conf.h"
-#include "../core/const.h"
-#include "../core/kernelAudio.h"
-#include "../core/kernelMidi.h"
-#include "../core/mixerHandler.h"
-#include "../core/mixer.h"
-#include "../core/recorder.h"
-#include "../core/recManager.h"
-#include "transport.h"
-
-
-extern gdMainWindow* G_MainWin;
-
-
-using namespace giada::m;
-
-
-namespace giada {
-namespace c {
-namespace transport
-{
-void startStopSeq(bool gui)
-{
- clock::isRunning() ? stopSeq(gui) : startSeq(gui);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void startSeq(bool gui)
-{
- switch (clock::getStatus()) {
- case ClockStatus::STOPPED:
- clock::setStatus(ClockStatus::RUNNING);
- break;
- case ClockStatus::WAITING:
- clock::setStatus(ClockStatus::RUNNING);
- m::recManager::stopActionRec();
- G_MainWin->mainTransport->setRecTriggerModeActive(true);
- G_MainWin->mainTransport->updateRecAction(0);
- G_MainWin->mainTransport->updateRecInput(0);
- break;
- default:
- break;
- }
-
-#if defined(__linux__) || defined(__FreeBSD__)
- kernelAudio::jackStart();
-#endif
-
- if (!gui) {
- Fl::lock();
- G_MainWin->mainTransport->updatePlay(1);
- Fl::unlock();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void stopSeq(bool gui)
-{
- mh::stopSequencer();
-
-#if defined(__linux__) || defined(__FreeBSD__)
- kernelAudio::jackStop();
-#endif
-
- /* What to do if we stop the sequencer and some action recs are active?
- Deactivate the button and delete any 'rec on' status. */
-
- if (recorder::isActive()) {
- recorder::disable();
- Fl::lock();
- G_MainWin->mainTransport->updateRecAction(0);
- Fl::unlock();
- }
-
- /* If input recs are active (who knows why) we must deactivate them. One
- might stop the sequencer while an input rec is running. */
-
- if (mixer::recording) {
- mh::stopInputRec();
- Fl::lock();
- G_MainWin->mainTransport->updateRecInput(0);
- Fl::unlock();
- }
-
- if (!gui) {
- Fl::lock();
- G_MainWin->mainTransport->updatePlay(0);
- Fl::unlock();
- }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void rewindSeq(bool gui, bool notifyJack)
-{
- mh::rewindSequencer();
-
- /* FIXME - potential desync when Quantizer is enabled from this point on.
- Mixer would wait, while the following calls would be made regardless of its
- state. */
-
-#ifdef __linux__
- if (notifyJack)
- kernelAudio::jackSetPosition(0);
-#endif
-
- if (conf::midiSync == MIDI_SYNC_CLOCK_M)
- kernelMidi::send(MIDI_POSITION_PTR, 0, 0);
-}
-
-/* -------------------------------------------------------------------------- */
-
-
-void toggleMetronome(bool gui)
-{
- mixer::toggleMetronome();
- if (!gui) {
- Fl::lock();
- G_MainWin->mainTransport->updateMetronome(mixer::isMetronomeOn());
- Fl::unlock();
- }
-}
-
-}}} // giada::c::transport::
\ No newline at end of file
#include <FL/fl_draw.H>
#include "utils/gui.h"
#include "utils/string.h"
-#include "core/channels/channel.h"
-#include "core/model/model.h"
#include "core/conf.h"
+#include "core/action.h"
#include "core/const.h"
#include "core/clock.h"
+#include "core/midiEvent.h"
+#include "glue/channel.h"
#include "gui/elems/actionEditor/gridTool.h"
-#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/scrollPack.h"
#include "gui/elems/basics/choice.h"
#include "baseActionEditor.h"
namespace v
{
gdBaseActionEditor::gdBaseActionEditor(ID channelId)
-: gdWindow (640, 284),
- channelId(channelId),
- ratio (G_DEFAULT_ZOOM_RATIO)
+: gdWindow (640, 284)
+, channelId(channelId)
+, ratio (G_DEFAULT_ZOOM_RATIO)
{
using namespace giada::m;
/* -------------------------------------------------------------------------- */
-std::vector<m::Action> gdBaseActionEditor::getActions() const
-{
- return m_actions;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void gdBaseActionEditor::computeWidth()
{
fullWidth = frameToPixel(m::clock::getFramesInSeq());
{
u::gui::setFavicon(this);
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- std::string l = "Action Editor";
- if (c.name != "") l += " - " + c.name;
- copy_label(l.c_str());
- });
+ std::string l = "Action Editor";
+ if (m_data.channelName != "") l += " - " + m_data.channelName;
+ copy_label(l.c_str());
set_non_modal();
size_range(640, 284);
#include "core/types.h"
+#include "glue/actionEditor.h"
#include "gui/dialogs/window.h"
class geChoice;
class geButton;
-class geScroll;
namespace giada {
namespace v
{
class geGridTool;
-
-
+class geScrollPack;
class gdBaseActionEditor : public gdWindow
{
public:
Pixel frameToPixel(Frame f) const;
Frame pixelToFrame(Pixel p, bool snap=true) const;
int getActionType() const;
- std::vector<m::Action> getActions() const;
-
- geChoice* actionType;
- geGridTool* gridTool;
- geButton* zoomInBtn;
- geButton* zoomOutBtn;
- geScroll* viewport; // widget container
ID channelId;
+ geChoice* actionType;
+ geGridTool* gridTool;
+ geButton* zoomInBtn;
+ geButton* zoomOutBtn;
+ geScrollPack* viewport; // widget container
+
float ratio;
Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer
Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range
static constexpr float MIN_RATIO = 25.0f;
static constexpr float MAX_RATIO = 40000.0f;
- std::vector<m::Action> m_actions;
-
gdBaseActionEditor(ID channelId);
void zoomIn();
void centerViewportOut();
void prepareWindow();
+
+ c::actionEditor::Data m_data;
};
}} // giada::v::
#include <string>
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
#include "core/graphics.h"
#include "glue/actionEditor.h"
-#include "gui/elems/basics/scroll.h"
+#include "glue/channel.h"
+#include "gui/elems/basics/scrollPack.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/resizerBar.h"
#include "gui/elems/basics/box.h"
gdMidiActionEditor::gdMidiActionEditor(ID channelId)
: gdBaseActionEditor(channelId)
{
- computeWidth();
-
- Fl_Group* upperArea = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w()-16, G_GUI_UNIT);
-
- upperArea->begin();
+ end();
- gridTool = new geGridTool(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+ computeWidth();
- geBox *b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, G_GUI_UNIT); // padding actionType - zoomButtons
- zoomInBtn = new geButton(w()-8-40-4, G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
- zoomOutBtn = new geButton(w()-8-20, G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm);
-
- upperArea->end();
+ gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
+ gridTool = new geGridTool(0, 0);
+ geBox* b1 = new geBox (0, 0, w() - 150, G_GUI_UNIT); // padding actionType - zoomButtons
+ zoomInBtn = new geButton (0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm);
+ zoomOutBtn = new geButton (0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm);
+ upperArea->add(gridTool);
+ upperArea->add(b1);
+ upperArea->add(zoomInBtn);
+ upperArea->add(zoomOutBtn);
upperArea->resizable(b1);
- zoomInBtn->callback(cb_zoomIn, (void*)this);
- zoomOutBtn->callback(cb_zoomOut, (void*)this);
-
/* Main viewport: contains all widgets. */
- viewport = new geScroll(G_GUI_OUTER_MARGIN, upperArea->y()+upperArea->h()+G_GUI_OUTER_MARGIN, w()-16, h()-44);
-
- m_ne = new geNoteEditor(viewport->x(), viewport->y(), this);
- m_ner = new geResizerBar(m_ne->x(), m_ne->y()+m_ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+ viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
+ upperArea->w(), h()-44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
+ m_ne = new geNoteEditor (0, 0, this);
+ m_ner = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+ m_ve = new geVelocityEditor(0, 0, this);
+ m_ver = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
viewport->add(m_ne);
viewport->add(m_ner);
-
- m_ve = new geVelocityEditor(viewport->x(), m_ne->y()+m_ne->h()+RESIZER_BAR_H);
- m_ver = new geResizerBar(m_ve->x(), m_ve->y()+m_ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
viewport->add(m_ve);
viewport->add(m_ver);
- end();
+ zoomInBtn->callback(cb_zoomIn, (void*)this);
+ zoomOutBtn->callback(cb_zoomOut, (void*)this);
+
+ add(upperArea);
+ add(viewport);
+ resizable(upperArea);
+
prepareWindow();
rebuild();
}
void gdMidiActionEditor::rebuild()
{
- m_actions = c::actionEditor::getActions(channelId);
+ m_data = c::actionEditor::getData(channelId);
computeWidth();
- m_ne->rebuild();
+ m_ne->rebuild(m_data);
m_ner->size(m_ne->w(), m_ner->h());
- m_ve->rebuild();
+ m_ve->rebuild(m_data);
m_ver->size(m_ve->w(), m_ver->h());
}
}} // giada::v::
#include <string>
#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
#include "core/const.h"
#include "core/midiEvent.h"
#include "core/graphics.h"
#include "glue/actionEditor.h"
-#include "gui/elems/basics/scroll.h"
+#include "glue/channel.h"
+#include "gui/elems/basics/pack.h"
+#include "gui/elems/basics/scrollPack.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/resizerBar.h"
#include "gui/elems/basics/choice.h"
gdSampleActionEditor::gdSampleActionEditor(ID channelId)
: gdBaseActionEditor(channelId)
{
+ end();
+
computeWidth();
/* Container with zoom buttons and the action type selector. Scheme of the
resizable boxes: |[--b1--][actionType][--b2--][+][-]| */
- Fl_Group* upperArea = new Fl_Group(8, 8, w()-16, 20);
-
- upperArea->begin();
-
- actionType = new geChoice(8, 8, 80, 20);
- gridTool = new geGridTool(actionType->x()+actionType->w()+8, 8);
- actionType->add("Key press");
- actionType->add("Key release");
- actionType->add("Kill chan");
- actionType->value(0);
-
- if (!canChangeActionType())
- actionType->deactivate();
-
- geBox* b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons
- zoomInBtn = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
- zoomOutBtn = new geButton(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
-
- upperArea->end();
+ gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL);
+ actionType = new geChoice (0, 0, 80, 20);
+ gridTool = new geGridTool(0, 0);
+ geBox* b1 = new geBox (0, 0, w() - 232, 20); // padding actionType - zoomButtons
+ zoomInBtn = new geButton (0, 0, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm);
+ zoomOutBtn = new geButton (0, 0, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm);
+ upperArea->add(actionType);
+ upperArea->add(gridTool);
+ upperArea->add(b1);
+ upperArea->add(zoomInBtn);
+ upperArea->add(zoomOutBtn);
upperArea->resizable(b1);
+ actionType->add("Key press");
+ actionType->add("Key release");
+ actionType->add("Kill chan");
+ actionType->value(0);
+ if (!canChangeActionType())
+ actionType->deactivate();
+
zoomInBtn->callback(cb_zoomIn, (void*)this);
zoomOutBtn->callback(cb_zoomOut, (void*)this);
/* Main viewport: contains all widgets. */
- viewport = new geScroll(8, 36, w()-16, h()-44);
-
- m_ae = new geSampleActionEditor(viewport->x(), viewport->y());
- m_aer = new geResizerBar(m_ae->x(), m_ae->y()+m_ae->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+ viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN,
+ upperArea->w(), h()-44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0);
+ m_ae = new geSampleActionEditor(0, 0, this);
+ m_aer = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
+ m_ee = new geEnvelopeEditor (0, 0, "volume", this);
+ m_eer = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
viewport->add(m_ae);
viewport->add(m_aer);
-
- m_ee = new geEnvelopeEditor(viewport->x(), m_ae->y()+m_ae->h()+RESIZER_BAR_H, "volume");
- m_eer = new geResizerBar(m_ee->x(), m_ee->y()+m_ee->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL);
viewport->add(m_ee);
viewport->add(m_eer);
- end();
+ add(upperArea);
+ add(viewport);
+ resizable(upperArea);
+
prepareWindow();
rebuild();
}
bool gdSampleActionEditor::canChangeActionType()
{
- bool res;
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- const m::SampleChannel& sc = static_cast<const m::SampleChannel&>(c);
- res = sc.mode != ChannelMode::SINGLE_PRESS && !sc.isAnyLoopMode();
- });
- return res;
+ return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS &&
+ m_data.sample->isLoopMode == false;
}
void gdSampleActionEditor::rebuild()
{
- m_actions = c::actionEditor::getActions(channelId);
+ m_data = c::actionEditor::getData(channelId);
canChangeActionType() ? actionType->activate() : actionType->deactivate();
computeWidth();
- m_ae->rebuild();
+ m_ae->rebuild(m_data);
m_aer->size(m_ae->w(), m_aer->h());
- m_ee->rebuild();
+ m_ee->rebuild(m_data);
m_eer->size(m_ee->w(), m_eer->h());
}
}} // giada::v::
{
class geSampleActionEditor;
class geEnvelopeEditor;
-
class gdSampleActionEditor : public gdBaseActionEditor
{
public:
private:
+ bool canChangeActionType();
+
geSampleActionEditor* m_ae;
geResizerBar* m_aer;
geEnvelopeEditor* m_ee;
geResizerBar* m_eer;
-
- bool canChangeActionType();
};
}} // giada::v::
#include "glue/channel.h"
#include "utils/gui.h"
-#include "core/channels/channel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "core/conf.h"
namespace giada {
namespace v
{
-gdChannelNameInput::gdChannelNameInput(ID channelId)
-: gdWindow (u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"),
- m_channelId(channelId)
+gdChannelNameInput::gdChannelNameInput(const c::channel::Data& d)
+: gdWindow(u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"),
+ m_data (d)
{
set_modal();
m_cancel = new geButton(m_ok->x() - 70 - G_GUI_OUTER_MARGIN, m_ok->y(), 70, G_GUI_UNIT, "Cancel");
end();
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- m_name->value(c.name.c_str());
- });
+ m_name->value(m_data.name.c_str());
m_ok->shortcut(FL_Enter);
m_ok->callback(cb_update, (void*)this);
void gdChannelNameInput::cb_update()
{
- c::channel::setName(m_channelId, m_name->value());
+ c::channel::setName(m_data.id, m_name->value());
do_callback();
}
{
public:
- gdChannelNameInput(ID channelId);
+ gdChannelNameInput(const c::channel::Data& d);
private:
void cb_update();
void cb_cancel();
- ID m_channelId;
+ const c::channel::Data& m_data;
geInput* m_name;
geButton* m_ok;
#include "utils/string.h"
#include "glue/io.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/conf.h"
#include "utils/log.h"
#include "gui/elems/basics/box.h"
namespace giada {
namespace v
{
-gdKeyGrabber::gdKeyGrabber(ID channelId)
-: gdWindow (300, 126, "Key configuration"),
- m_channelId(channelId)
+gdKeyGrabber::gdKeyGrabber(const c::channel::Data& d)
+: gdWindow(300, 126, "Key configuration")
+, m_data (d)
{
begin();
m_text = new geBox(8, 8, 284, 80, "");
m_clear->callback(cb_clear, (void*)this);
m_cancel->callback(cb_cancel, (void*)this);
- rebuild();
+ updateText(m_data.key);
u::gui::setFavicon(this);
set_modal();
void gdKeyGrabber::rebuild()
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- updateText(c.key);
- });
+ updateText(m_data.key);
}
void gdKeyGrabber::setButtonLabel(int key)
{
- c::io::setSampleChannelKey(m_channelId, key);
+ c::io::channel_setKey(m_data.id, key);
}
/* -------------------------------------------------------------------------- */
{
std::string tmp = "Press a key.\n\nCurrent binding: ";
if (key != 0)
- tmp += static_cast<char>(key);
+ tmp += static_cast<wchar_t>(key);
else
tmp += "[none]";
m_text->copy_label(tmp.c_str());
&& x != FL_End
&& x != ' ')
{
- u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_channelId);
+ u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_data.id);
setButtonLabel(x);
updateText(x);
break;
namespace giada {
+namespace c {
+namespace channel
+{
+struct Data;
+}}
namespace v
{
class gdKeyGrabber : public gdWindow
{
public:
- gdKeyGrabber(ID channelId);
+ gdKeyGrabber(const c::channel::Data& d);
int handle(int e) override;
void rebuild() override;
void setButtonLabel(int key);
void updateText(int key);
- ID m_channelId;
+ const c::channel::Data& m_data;
geBox* m_text;
geButton* m_clear;
mainMenu = new v::geMainMenu(8, 0);
#if defined(WITH_VST)
- mainIO = new v::geMainIO(408, 8);
+ mainIO = new v::geMainIO(412, 8);
#else
mainIO = new v::geMainIO(476, 8);
#endif
/* -------------------------------------------------------------------------- */
-void gdMidiInputBase::refresh()
-{
- for (geMidiLearnerBase* l : m_learners)
- l->refresh();
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
void gdMidiInputBase::cb_close(Fl_Widget* w, void* p) { ((gdMidiInputBase*)p)->cb_close(); }
#include "gui/dialogs/window.h"
-#include "gui/elems/midiIO/midiLearnerBase.h"
+#include "gui/elems/midiIO/midiLearner.h"
class geButton;
virtual ~gdMidiInputBase();
- void refresh() override;
-
protected:
- gdMidiInputBase(int x, int y, int w, int h, const char* title);
-
- static const int LEARNER_WIDTH = 284;
+ gdMidiInputBase(int x, int y, int w, int h, const char* title="");
static void cb_close(Fl_Widget* w, void* p);
void cb_close();
- std::vector<geMidiLearnerBase*> m_learners;
-
geButton* m_ok;
geCheck* m_enable;
geChoice* m_channel;
#include <FL/Fl_Pack.H>
#include "utils/gui.h"
#include "utils/log.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
#include "core/const.h"
#include "core/conf.h"
#ifdef WITH_VST
#include "core/plugin.h"
#endif
#include "utils/string.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
-#include "gui/elems/midiIO/midiLearnerPlugin.h"
-#include "gui/elems/basics/scroll.h"
+#include "gui/elems/midiIO/midiLearner.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
+#include "gui/elems/basics/scrollPack.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/choice.h"
#include "gui/elems/basics/check.h"
+#include "gui/elems/basics/group.h"
#include "midiInputChannel.h"
namespace giada {
namespace v
{
-gdMidiInputChannel::gdMidiInputChannel(ID channelId)
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, m::conf::conf.midiInputW,
- m::conf::conf.midiInputH, "MIDI Input Setup"),
- m_channelId (channelId)
+geChannelLearnerPack::geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& channel)
+: geMidiLearnerPack(x, y, "Channel")
{
- m::model::ChannelsLock l(m::model::channels);
- const m::Channel& c = m::model::get(m::model::channels, m_channelId);
+ setCallbacks(
+ [channelId=channel.channelId] (int param) { c::io::channel_startMidiLearn(param, channelId); },
+ [channelId=channel.channelId] (int param) { c::io::channel_clearMidiLearn(param, channelId); }
+ );
+ addMidiLearner("keyPress", G_MIDI_IN_KEYPRESS);
+ addMidiLearner("key release", G_MIDI_IN_KEYREL);
+ addMidiLearner("key kill", G_MIDI_IN_KILL);
+ addMidiLearner("arm", G_MIDI_IN_ARM);
+ addMidiLearner("mute", G_MIDI_IN_MUTE);
+ addMidiLearner("solo", G_MIDI_IN_SOLO);
+ addMidiLearner("volume", G_MIDI_IN_VOLUME);
+ addMidiLearner("pitch", G_MIDI_IN_PITCH, /*visible=*/channel.channelType == ChannelType::SAMPLE);
+ addMidiLearner("read actions", G_MIDI_IN_READ_ACTIONS, /*visible=*/channel.channelType == ChannelType::SAMPLE);
+}
- copy_label(std::string("MIDI Input Setup (channel " + std::to_string(c.id) + ")").c_str());
-
- int extra = c.type == ChannelType::SAMPLE ? 28 : 0;
- Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20 + extra);
- groupHeader->begin();
+/* -------------------------------------------------------------------------- */
- m_enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT,
- "Enable MIDI input");
- m_channel = new geChoice(m_enable->x()+m_enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT);
- m_veloAsVol = new geCheck(G_GUI_OUTER_MARGIN, m_enable->y()+m_enable->h()+G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT,
- "Velocity drives volume (one-shot only)");
- groupHeader->resizable(nullptr);
- groupHeader->end();
+void geChannelLearnerPack::update(const c::io::Channel_InputData& d)
+{
+ learners[0]->update(d.keyPress);
+ learners[1]->update(d.keyRelease);
+ learners[2]->update(d.kill);
+ learners[3]->update(d.arm);
+ learners[4]->update(d.volume);
+ learners[5]->update(d.mute);
+ learners[6]->update(d.solo);
+ learners[7]->update(d.pitch);
+ learners[8]->update(d.readActions);
+ setEnabled(d.enabled);
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
- m_container = new geScroll(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN,
- w()-16, h()-72-extra);
- m_container->begin();
- addChannelLearners();
#ifdef WITH_VST
- addPluginLearners();
+
+gePluginLearnerPack::gePluginLearnerPack(int x, int y, const c::io::PluginData& plugin)
+: geMidiLearnerPack(x, y, plugin.name)
+{
+ setCallbacks(
+ [pluginId=plugin.id] (int param) { c::io::plugin_startMidiLearn(param, pluginId); },
+ [pluginId=plugin.id] (int param) { c::io::plugin_clearMidiLearn(param, pluginId); }
+ );
+
+ for (const c::io::PluginParamData& param : plugin.params)
+ addMidiLearner(param.name, param.index);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePluginLearnerPack::update(const c::io::PluginData& d, bool enabled)
+{
+ std::size_t i = 0;
+ for (const c::io::PluginParamData& param : d.params)
+ learners[i++]->update(param.value);
+ setEnabled(enabled);
+}
+
#endif
- m_container->end();
- for (auto* l : m_learners)
- c.midiIn ? l->activate() : l->deactivate();
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiInputChannel::gdMidiInputChannel(ID channelId)
+: gdMidiInputBase(m::conf::conf.midiInputX,
+ m::conf::conf.midiInputY,
+ m::conf::conf.midiInputW,
+ m::conf::conf.midiInputH)
+, m_channelId (channelId)
+, m_data (c::io::channel_getInputData(channelId))
+{
+ end();
+
+ copy_label(std::string("MIDI Input Setup (channel " + std::to_string(channelId) + ")").c_str());
+
+ /* Header */
+
+ geGroup* groupHeader = new geGroup(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+ m_enable = new geCheck(0, 0, 120, G_GUI_UNIT, "Enable MIDI input");
+ m_channel = new geChoice(m_enable->x() + m_enable->w() + 44, 0, 120, G_GUI_UNIT);
+ m_veloAsVol = new geCheck(0, m_enable->y() + m_enable->h() + G_GUI_OUTER_MARGIN, w() - 16, G_GUI_UNIT,
+ "Velocity drives volume (Sample Channels)");
+ groupHeader->add(m_enable);
+ groupHeader->add(m_channel);
+ groupHeader->add(m_veloAsVol);
+ groupHeader->resizable(nullptr);
+
+ /* Main scrollable content. */
- Fl_Group* groupButtons = new Fl_Group(8, m_container->y()+m_container->h()+8, m_container->w(), 20);
- groupButtons->begin();
+ m_container = new geScrollPack(G_GUI_OUTER_MARGIN, groupHeader->y() + groupHeader->h() + G_GUI_OUTER_MARGIN,
+ w() - 16, h() - groupHeader->h() - 52);
+ m_container->add(new geChannelLearnerPack(0, 0, m_data));
+#ifdef WITH_VST
+ for (c::io::PluginData& plugin : m_data.plugins)
+ m_container->add(new gePluginLearnerPack(0, 0, plugin));
+#endif
- geBox* spacer = new geBox(groupButtons->x(), groupButtons->y(), 100, 20); // spacer window border <-> buttons
- m_ok = new geButton(w()-88, groupButtons->y(), 80, 20, "Close");
+ /* Footer buttons. */
+ geGroup* groupButtons = new geGroup(G_GUI_OUTER_MARGIN, m_container->y() + m_container->h() + G_GUI_OUTER_MARGIN);
+ geBox* spacer = new geBox(0, 0, w() - 80, G_GUI_UNIT); // spacer window border <-> buttons
+ m_ok = new geButton(w() - 96, 0, 80, G_GUI_UNIT, "Close");
+ groupButtons->add(spacer);
+ groupButtons->add(m_ok);
groupButtons->resizable(spacer);
- groupButtons->end();
m_ok->callback(cb_close, (void*)this);
-
- m_enable->value(c.midiIn);
m_enable->callback(cb_enable, (void*)this);
- if (c.type == ChannelType::SAMPLE) {
- m_veloAsVol->value(static_cast<const m::SampleChannel&>(c).midiInVeloAsVol);
- m_veloAsVol->callback(cb_veloAsVol, (void*)this);
- }
- else
- m_veloAsVol->hide();
-
m_channel->add("Channel (any)");
m_channel->add("Channel 1");
m_channel->add("Channel 2");
m_channel->add("Channel 14");
m_channel->add("Channel 15");
m_channel->add("Channel 16");
- m_channel->value(c.midiInFilter == -1 ? 0 : c.midiInFilter + 1);
m_channel->callback(cb_setChannel, (void*)this);
- resizable(m_container);
+ m_veloAsVol->callback(cb_veloAsVol, (void*)this);
- end();
+ add(groupHeader);
+ add(m_container);
+ add(groupButtons);
+ resizable(m_container);
u::gui::setFavicon(this);
set_modal();
+ rebuild();
show();
}
/* -------------------------------------------------------------------------- */
-void gdMidiInputChannel::addChannelLearners()
+void gdMidiInputChannel::rebuild()
{
- m::model::ChannelsLock l(m::model::channels);
- const m::Channel& c = m::model::get(m::model::channels, m_channelId);
-
- Fl_Pack* pack = new Fl_Pack(m_container->x(), m_container->y(), LEARNER_WIDTH, 200);
- pack->spacing(G_GUI_INNER_MARGIN);
- pack->begin();
- geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, "Channel");
- header->box(FL_BORDER_BOX);
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key press", G_MIDI_IN_KEYPRESS, c.midiInKeyPress, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key release", G_MIDI_IN_KEYREL, c.midiInKeyRel, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key kill", G_MIDI_IN_KILL, c.midiInKill, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "arm", G_MIDI_IN_ARM, c.midiInArm, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "mute", G_MIDI_IN_MUTE, c.midiInVolume, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "solo", G_MIDI_IN_SOLO, c.midiInMute, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "volume", G_MIDI_IN_VOLUME, c.midiInSolo, m_channelId));
- if (c.type == ChannelType::SAMPLE) {
- const m::SampleChannel& sc = static_cast<const m::SampleChannel&>(c);
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "pitch", G_MIDI_IN_PITCH, sc.midiInPitch, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "read actions", G_MIDI_IN_READ_ACTIONS, sc.midiInReadActions, m_channelId));
- }
- pack->end();
-}
+ m_data = c::io::channel_getInputData(m_channelId);
+ m_enable->value(m_data.enabled);
-/* -------------------------------------------------------------------------- */
-
+ if (m_data.channelType == ChannelType::SAMPLE) {
+ m_veloAsVol->activate();
+ m_veloAsVol->value(m_data.velocityAsVol);
+ }
+ else
+ m_veloAsVol->deactivate();
+ int i = 0;
+ static_cast<geChannelLearnerPack*>(m_container->getChild(i++))->update(m_data);
#ifdef WITH_VST
+ for (c::io::PluginData& plugin : m_data.plugins)
+ static_cast<gePluginLearnerPack*>(m_container->getChild(i++))->update(plugin, m_data.enabled);
+#endif
-void gdMidiInputChannel::addPluginLearners()
-{
- m::model::ChannelsLock cl(m::model::channels);
- m::model::PluginsLock ml(m::model::plugins);
-
- m::Channel& c = m::model::get(m::model::channels, m_channelId);
-
- int i = 1;
- for (ID id : c.pluginIds) {
-
- m::Plugin& p = m::model::get(m::model::plugins, id);
-
- Fl_Pack* pack = new Fl_Pack(m_container->x() + (i++ * (LEARNER_WIDTH + G_GUI_OUTER_MARGIN)),
- m_container->y(), LEARNER_WIDTH, 200);
- pack->spacing(G_GUI_INNER_MARGIN);
- pack->begin();
-
- geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, p.getName().c_str());
- header->box(FL_BORDER_BOX);
-
- for (int k = 0; k < p.getNumParameters(); k++)
- m_learners.push_back(new geMidiLearnerPlugin(0, 0, LEARNER_WIDTH, p.getParameterName(k), k, p.midiInParams.at(k), p.id));
+ m_channel->value(m_data.filter == -1 ? 0 : m_data.filter + 1);
- pack->end();
+ if (m_data.enabled) {
+ m_channel->activate();
+ if (m_data.channelType == ChannelType::SAMPLE)
+ m_veloAsVol->activate();
+ }
+ else {
+ m_channel->deactivate();
+ m_veloAsVol->deactivate();
}
}
-#endif
-
/* -------------------------------------------------------------------------- */
void gdMidiInputChannel::cb_enable()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- c.midiIn = m_enable->value();
- });
-
- m_enable->value() ? m_channel->activate() : m_channel->deactivate();
-
- for (auto* l : m_learners)
- m_enable->value() ? l->activate() : l->deactivate();
+ c::io::channel_enableMidiLearn(m_data.channelId, m_enable->value());
}
void gdMidiInputChannel::cb_veloAsVol()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- static_cast<m::SampleChannel&>(c).midiInVeloAsVol = m_veloAsVol->value();
- });
+ c::io::channel_enableVelocityAsVol(m_data.channelId, m_veloAsVol->value());
}
void gdMidiInputChannel::cb_setChannel()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- c.midiInFilter = m_channel->value() == 0 ? -1 : m_channel->value() - 1;
- u::log::print("[gdMidiInputChannel] Set MIDI channel to %d\n", c.midiInFilter);
- });
+ c::io::channel_setMidiInputFilter(m_data.channelId,
+ m_channel->value() == 0 ? -1 : m_channel->value() - 1);
}
}} // giada::v::
#define GD_MIDI_INPUT_CHANNEL_H
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
#include "midiInputBase.h"
-class geScroll;
class geCheck;
class geChoice;
namespace giada {
namespace v
{
-class geMidiLearner;
+class geScrollPack;
+class geChannelLearnerPack : public geMidiLearnerPack
+{
+public:
+
+ geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& d);
+
+ void update(const c::io::Channel_InputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_VST
+
+class gePluginLearnerPack : public geMidiLearnerPack
+{
+public:
+
+ gePluginLearnerPack(int x, int y, const c::io::PluginData&);
+
+ void update(const c::io::PluginData&, bool enabled);
+};
+
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+
+
+
class gdMidiInputChannel : public gdMidiInputBase
{
public:
gdMidiInputChannel(ID channelId);
+ void rebuild() override;
+
private:
static void cb_enable(Fl_Widget* w, void* p);
void cb_setChannel();
void cb_veloAsVol();
- void addChannelLearners();
-
-#ifdef WITH_VST
-
- void addPluginLearners();
-
-#endif
-
ID m_channelId;
+
+ c::io::Channel_InputData m_data;
- geScroll* m_container;
- geCheck* m_veloAsVol;
+ geScrollPack* m_container;
+ geCheck* m_veloAsVol;
};
}} // giada::v::
#include "utils/gui.h"
#include "core/conf.h"
#include "core/const.h"
-#include "core/model/model.h"
-#include "gui/elems/midiIO/midiLearnerMaster.h"
+#include "gui/elems/midiIO/midiLearner.h"
+#include "gui/elems/basics/scrollPack.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/check.h"
#include "gui/elems/basics/choice.h"
+#include "gui/elems/basics/group.h"
#include "midiInputMaster.h"
namespace giada {
namespace v
{
-gdMidiInputMaster::gdMidiInputMaster()
-: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+geMasterLearnerPack::geMasterLearnerPack(int x, int y)
+: geMidiLearnerPack(x, y)
{
- set_modal();
+ setCallbacks(
+ [] (int param) { c::io::master_startMidiLearn(param); },
+ [] (int param) { c::io::master_clearMidiLearn(param); }
+ );
+ addMidiLearner("rewind", G_MIDI_IN_REWIND);
+ addMidiLearner("play/stop", G_MIDI_IN_START_STOP);
+ addMidiLearner("action recording", G_MIDI_IN_ACTION_REC);
+ addMidiLearner("input recording", G_MIDI_IN_INPUT_REC);
+ addMidiLearner("metronome", G_MIDI_IN_METRONOME);
+ addMidiLearner("input volume", G_MIDI_IN_VOLUME_IN);
+ addMidiLearner("output volume", G_MIDI_IN_VOLUME_OUT);
+ addMidiLearner("sequencer ×2", G_MIDI_IN_BEAT_DOUBLE);
+ addMidiLearner("sequencer ÷2", G_MIDI_IN_BEAT_HALF);
+}
- m::model::midiIn.lock();
- const m::model::MidiIn* midiIn = m::model::midiIn.get();
- Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20);
- groupHeader->begin();
+/* -------------------------------------------------------------------------- */
- m_enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT,
- "Enable MIDI input");
- m_channel = new geChoice(m_enable->x()+m_enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT);
- groupHeader->resizable(nullptr);
- groupHeader->end();
-
- Fl_Pack* pack = new Fl_Pack(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN,
- LEARNER_WIDTH, 212);
- pack->spacing(G_GUI_INNER_MARGIN);
- pack->begin();
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "rewind", G_MIDI_IN_REWIND, midiIn->rewind));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "play/stop", G_MIDI_IN_START_STOP, midiIn->startStop));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "action recording", G_MIDI_IN_ACTION_REC, midiIn->actionRec));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input recording", G_MIDI_IN_INPUT_REC, midiIn->inputRec));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "metronome", G_MIDI_IN_METRONOME, midiIn->volumeIn));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input volume", G_MIDI_IN_VOLUME_IN, midiIn->volumeOut));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "output volume", G_MIDI_IN_VOLUME_OUT, midiIn->beatDouble));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer ×2", G_MIDI_IN_BEAT_DOUBLE, midiIn->beatHalf));
- m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer ÷2", G_MIDI_IN_BEAT_HALF, midiIn->metronome));
- pack->end();
- m_ok = new geButton(w()-88, pack->y()+pack->h()+G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
+void geMasterLearnerPack::update(const c::io::Master_InputData& d)
+{
+ learners[0]->update(d.rewind);
+ learners[1]->update(d.startStop);
+ learners[2]->update(d.actionRec);
+ learners[3]->update(d.inputRec);
+ learners[4]->update(d.metronome);
+ learners[5]->update(d.volumeIn);
+ learners[6]->update(d.volumeOut);
+ learners[7]->update(d.beatDouble);
+ learners[8]->update(d.beatHalf);
+ setEnabled(d.enabled);
+}
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiInputMaster::gdMidiInputMaster()
+: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)")
+{
end();
- for (geMidiLearnerBase* l : m_learners)
- midiIn->enabled ? l->activate() : l->deactivate();
+ geGroup* groupHeader = new geGroup(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN);
+ m_enable = new geCheck(0, 0, 120, G_GUI_UNIT, "Enable MIDI input");
+ m_channel = new geChoice(m_enable->x() + m_enable->w() + 44, 0, 120, G_GUI_UNIT);
+ groupHeader->resizable(nullptr);
+ groupHeader->add(m_enable);
+ groupHeader->add(m_channel);
- m_ok->callback(cb_close, (void*)this);
+ m_learners = new geMasterLearnerPack(G_GUI_OUTER_MARGIN, groupHeader->y() + groupHeader->h() + G_GUI_OUTER_MARGIN);
+ m_ok = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
- m_enable->value(midiIn->enabled);
+ add(groupHeader);
+ add(m_learners);
+ add(m_ok);
+
+ m_ok->callback(cb_close, (void*)this);
m_enable->callback(cb_enable, (void*)this);
m_channel->add("Channel (any)");
m_channel->add("Channel 14");
m_channel->add("Channel 15");
m_channel->add("Channel 16");
-
- m_channel->value(midiIn->filter - 1 ? 0 : midiIn->filter + 1);
m_channel->callback(cb_setChannel, (void*)this);
- midiIn->enabled ? m_channel->activate() : m_channel->deactivate();
-
- m::model::midiIn.unlock();
u::gui::setFavicon(this);
+
+ set_modal();
+ rebuild();
show();
}
/* -------------------------------------------------------------------------- */
+void gdMidiInputMaster::rebuild()
+{
+ m_data = c::io::master_getInputData();
+
+ m_enable->value(m_data.enabled);
+ m_channel->value(m_data.filter - 1 ? 0 : m_data.filter + 1);
+ m_learners->update(m_data);
+
+ m_data.enabled ? m_channel->activate() : m_channel->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void gdMidiInputMaster::cb_enable(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_enable(); }
void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_setChannel(); }
void gdMidiInputMaster::cb_enable()
{
- m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
- {
- m.enabled = m_enable->value();
- });
-
- m_enable->value() ? m_channel->activate() : m_channel->deactivate();
-
- for (geMidiLearnerBase* l : m_learners)
- m_enable->value() ? l->activate() : l->deactivate();
+ c::io::master_enableMidiLearn(m_enable->value());
}
void gdMidiInputMaster::cb_setChannel()
{
- m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m)
- {
- m.filter = m_channel->value() == 0 ? -1 : m_channel->value() - 1;
- });
+ c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1);
}
}} // giada::v::
#define GD_MIDI_INPUT_MASTER_H
-#include "core/model/model.h"
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
#include "midiInputBase.h"
namespace giada {
namespace v
{
+class geMasterLearnerPack : public geMidiLearnerPack
+{
+public:
+
+ geMasterLearnerPack(int x, int y);
+
+ void update(const c::io::Master_InputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
class gdMidiInputMaster : public gdMidiInputBase
{
public:
gdMidiInputMaster();
+ void rebuild() override;
+
private:
static void cb_enable(Fl_Widget* w, void* p);
static void cb_setChannel(Fl_Widget* w, void* p);
void cb_enable();
void cb_setChannel();
+
+ c::io::Master_InputData m_data;
+
+ geMasterLearnerPack* m_learners;
};
}} // giada::v::
#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearner.h"
#include "gui/elems/basics/check.h"
#include "midiOutputBase.h"
namespace giada {
namespace v
{
-gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId)
-: gdWindow (w, h, "Midi Output Setup"),
- m_channelId(channelId)
+geLightningLearnerPack::geLightningLearnerPack(int x, int y, ID channelId)
+: geMidiLearnerPack(x, y)
{
+ setCallbacks(
+ [channelId] (int param) { c::io::channel_startMidiLearn(param, channelId); },
+ [channelId] (int param) { c::io::channel_clearMidiLearn(param, channelId); }
+ );
+ addMidiLearner("playing", G_MIDI_OUT_L_PLAYING);
+ addMidiLearner("mute", G_MIDI_OUT_L_MUTE);
+ addMidiLearner("solo", G_MIDI_OUT_L_SOLO);
}
/* -------------------------------------------------------------------------- */
-gdMidiOutputBase::~gdMidiOutputBase()
+void geLightningLearnerPack::update(const c::io::Channel_OutputData& d)
{
- c::io::stopMidiLearn();
+ learners[0]->update(d.lightningPlaying);
+ learners[1]->update(d.lightningMute);
+ learners[2]->update(d.lightningSolo);
+ setEnabled(d.lightningEnabled);
}
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
-void gdMidiOutputBase::refresh()
+gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId)
+: gdWindow (w, h, "Midi Output Setup")
+, m_channelId(channelId)
{
- for (geMidiLearnerBase* l : m_learners)
- l->refresh();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+gdMidiOutputBase::~gdMidiOutputBase()
+{
+ c::io::stopMidiLearn();
}
void gdMidiOutputBase::cb_enableLightning()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- c.midiOutL = m_enableLightning->value();
- });
-
- for (geMidiLearnerBase* l : m_learners)
- m_enableLightning->value() ? l->activate() : l->deactivate();
+ c::io::channel_enableMidiLightning(m_channelId, m_enableLightning->value());
}
/* -------------------------------------------------------------------------- */
-void gdMidiOutputBase::setTitle(int chanNum)
+void gdMidiOutputBase::setTitle(ID channelId)
{
- std::string tmp = "MIDI Output Setup (channel " + std::to_string(chanNum) + ")";
+ std::string tmp = "MIDI Output Setup (channel " + std::to_string(channelId) + ")";
copy_label(tmp.c_str());
}
-
}} // giada::v::
#include "core/types.h"
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearnerPack.h"
+#include "gui/elems/midiIO/midiLearner.h"
#include "gui/dialogs/window.h"
-#include "gui/elems/midiIO/midiLearnerBase.h"
class geButton;
Both MidiOutputMidiCh and MidiOutputSampleCh have the MIDI lighting widget set.
In addition MidiOutputMidiCh has the MIDI message output box. */
-/* TODO - gdMidiOutput is almost the same thing of gdMidiInput. Create another
-parent class gdMidiIO to inherit from */
-
namespace giada {
namespace v
{
-class geMidiLearner;
+class geLightningLearnerPack : public geMidiLearnerPack
+{
+public:
+
+ geLightningLearnerPack(int x, int y, ID channelId);
+
+ void update(const c::io::Channel_OutputData&);
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
class gdMidiOutputBase : public gdWindow
{
public:
gdMidiOutputBase(int w, int h, ID channelId);
~gdMidiOutputBase();
- void refresh() override;
-
protected:
/* cb_close
/* setTitle
* set window title. */
- void setTitle(int chanNum);
+ void setTitle(ID channelId);
ID m_channelId;
-
- std::vector<geMidiLearnerBase*> m_learners;
- geButton* m_close;
- geCheck* m_enableLightning;
+ c::io::Channel_OutputData m_data;
+
+ geLightningLearnerPack* m_learners;
+ geButton* m_close;
+ geCheck* m_enableLightning;
};
}} // giada::v::
* -------------------------------------------------------------------------- */
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "utils/gui.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
+#include <FL/Fl_Pack.H>
+#include "glue/io.h"
+#include "gui/elems/midiIO/midiLearner.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/check.h"
#include "gui/elems/basics/choice.h"
+#include "utils/gui.h"
#include "midiOutputMidiCh.h"
gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId)
: gdMidiOutputBase(300, 168, channelId)
{
- m::model::ChannelsLock l(m::model::channels);
- m::MidiChannel& c = static_cast<m::MidiChannel&>(m::model::get(m::model::channels, m_channelId));
-
+ end();
setTitle(m_channelId + 1);
- begin();
- m_enableOut = new geCheck(x()+8, y()+8, 150, 20, "Enable MIDI output");
- m_chanListOut = new geChoice(w()-108, y()+8, 100, 20);
+ m_enableOut = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 150, G_GUI_UNIT, "Enable MIDI output");
+ m_chanListOut = new geChoice(w()-108, G_GUI_OUTER_MARGIN, 100, G_GUI_UNIT);
- m_enableLightning = new geCheck(x()+8, m_chanListOut->y()+m_chanListOut->h()+8, 120, 20, "Enable MIDI lightning output");
- m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+8,
- w()-16, "playing", G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+32,
- w()-16, "mute", G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+56,
- w()-16, "solo", G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId));
+ m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, m_chanListOut->y() + m_chanListOut->h() + G_GUI_OUTER_MARGIN,
+ 120, G_GUI_UNIT, "Enable MIDI lightning output");
+
+ m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN,
+ m_enableLightning->y() + m_enableLightning->h() + G_GUI_OUTER_MARGIN, channelId);
+
+ m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close");
- m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close");
-
- end();
+ add(m_enableOut);
+ add(m_chanListOut);
+ add(m_enableLightning);
+ add(m_learners);
+ add(m_close);
m_chanListOut->add("Channel 1");
m_chanListOut->add("Channel 2");
m_chanListOut->add("Channel 16");
m_chanListOut->value(0);
- if (c.midiOut)
- m_enableOut->value(1);
- else
- m_chanListOut->deactivate();
-
- m_enableLightning->value(c.midiOutL);
- for (geMidiLearnerBase* l : m_learners)
- c.midiOutL ? l->activate() : l->deactivate();
-
- m_chanListOut->value(c.midiOutChan);
m_chanListOut->callback(cb_setChannel, (void*)this);
-
m_enableOut->callback(cb_enableOut, (void*)this);
m_enableLightning->callback(cb_enableLightning, (void*)this);
m_close->callback(cb_close, (void*)this);
- set_modal();
u::gui::setFavicon(this);
+
+ set_modal();
+ rebuild();
show();
}
/* -------------------------------------------------------------------------- */
+void gdMidiOutputMidiCh::rebuild()
+{
+ m_data = c::io::channel_getOutputData(m_channelId);
+
+ assert(m_data.output.has_value());
+
+ m_learners->update(m_data);
+ m_chanListOut->value(m_data.output->filter);
+ m_enableOut->value(m_data.output->enabled);
+
+ m_data.output->enabled ? m_chanListOut->activate() : m_chanListOut->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
void gdMidiOutputMidiCh::cb_enableOut (Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_enableOut(); }
void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_setChannel(); }
void gdMidiOutputMidiCh::cb_enableOut()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- static_cast<m::MidiChannel&>(c).midiOut = m_enableOut->value();
- static_cast<m::MidiChannel&>(c).midiOutChan = m_chanListOut->value();
- });
-
- m_enableOut->value() ? m_chanListOut->activate() : m_chanListOut->deactivate();
+ c::io::channel_enableMidiOutput(m_channelId, m_enableOut->value());
}
void gdMidiOutputMidiCh::cb_setChannel()
{
- m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- static_cast<m::MidiChannel&>(c).midiOutChan = m_chanListOut->value();
- });
+ c::io::channel_setMidiOutputFilter(m_channelId, m_chanListOut->value());
}
}} // giada::v::
gdMidiOutputMidiCh(ID channelId);
+ void rebuild() override;
+
private:
static void cb_enableOut (Fl_Widget* w, void* p);
* -------------------------------------------------------------------------- */
+#include <FL/Fl_Pack.H>
#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
#include "utils/gui.h"
-#include "gui/elems/midiIO/midiLearnerChannel.h"
+#include "gui/elems/midiIO/midiLearner.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/check.h"
#include "midiOutputSampleCh.h"
gdMidiOutputSampleCh::gdMidiOutputSampleCh(ID channelId)
: gdMidiOutputBase(300, 140, channelId)
{
- m::model::ChannelsLock l(m::model::channels);
- m::Channel& c = m::model::get(m::model::channels, m_channelId);
-
- setTitle(c.id);
+ end();
+ setTitle(m_channelId);
- m_enableLightning = new geCheck(8, 8, 120, 20, "Enable MIDI lightning output");
- m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+8, w()-16, "playing",
- G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+32, w()-16, "mute",
- G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId));
- m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+56, w()-16, "solo",
- G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId));
+ m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, 20, "Enable MIDI lightning output");
- m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close");
- m_close->callback(cb_close, (void*)this);
+ m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN,
+ m_enableLightning->y() + m_enableLightning->h() + 8, channelId);
+
+ m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + 8, 80, 20, "Close");
+
+ add(m_enableLightning);
+ add(m_learners);
+ add(m_close);
- m_enableLightning->value(c.midiOutL);
+ m_close->callback(cb_close, (void*)this);
m_enableLightning->callback(cb_enableLightning, (void*)this);
- for (geMidiLearnerBase* l : m_learners)
- c.midiOutL ? l->activate() : l->deactivate();
+ u::gui::setFavicon(this);
set_modal();
- u::gui::setFavicon(this);
+ rebuild();
show();
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gdMidiOutputSampleCh::rebuild()
+{
+ m_data = c::io::channel_getOutputData(m_channelId);
+
+ m_enableLightning->value(m_data.lightningEnabled);
+ m_learners->update(m_data);
+}
}} // giada::v::
public:
gdMidiOutputSampleCh(ID channelId);
+
+ void rebuild() override;
};
}} // giada::v::
#include "glue/plugin.h"
#include "utils/gui.h"
-#include "core/channels/channel.h"
#include "core/conf.h"
#include "core/pluginManager.h"
#include "core/pluginHost.h"
namespace giada {
namespace v
{
-gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID chanID)
-: gdWindow(X, Y, W, H, "Available plugins"),
- m_chanID(chanID)
+gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId)
+: gdWindow (X, Y, W, H, "Available plugins")
+, m_channelId(channelId)
{
/* top area */
Fl_Group *group_top = new Fl_Group(8, 8, w()-16, 20);
int pluginIndex = browser->value() - 3; // subtract header lines
if (pluginIndex < 0)
return;
- c::plugin::addPlugin(pluginIndex, m_chanID);
+ c::plugin::addPlugin(pluginIndex, m_channelId);
do_callback();
}
}} // giada::v::
{
public:
- gdPluginChooser(int x, int y, int w, int h, ID chanID);
+ gdPluginChooser(int x, int y, int w, int h, ID channelId);
~gdPluginChooser();
private:
geButton* cancelBtn;
gePluginBrowser* browser;
- ID m_chanID;
+ ID m_channelId;
};
}} // giada::v::
#include <cassert>
#include <string>
-#include "core/model/model.h"
-#include "core/channels/channel.h"
#include "core/conf.h"
#include "core/const.h"
-#include "core/pluginHost.h"
#include "utils/string.h"
#include "utils/gui.h"
#include "gui/elems/basics/liquidScroll.h"
namespace giada {
namespace v
{
-gdPluginList::gdPluginList(ID chanID)
-: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204),
- m_channelId(chanID)
+gdPluginList::gdPluginList(ID channelId)
+: gdWindow (m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204)
+, m_channelId(channelId)
{
end();
list->end();
add(list);
- rebuild();
-
- if (m_channelId == m::mixer::MASTER_OUT_CHANNEL_ID)
- label("Master Out Plug-ins");
- else
- if (m_channelId == m::mixer::MASTER_IN_CHANNEL_ID)
- label("Master In Plug-ins");
- else {
- std::string l = "Channel " + u::string::iToString(m_channelId + 1) + " Plug-ins";
- copy_label(l.c_str());
- }
-
u::gui::setFavicon(this);
set_non_modal();
+ rebuild();
show();
}
void gdPluginList::rebuild()
{
+ m_plugins = c::plugin::getPlugins(m_channelId);
+
+ if (m_plugins.channelId == m::mixer::MASTER_OUT_CHANNEL_ID)
+ label("Master Out Plug-ins");
+ else
+ if (m_plugins.channelId == m::mixer::MASTER_IN_CHANNEL_ID)
+ label("Master In Plug-ins");
+ else {
+ std::string l = "Channel " + u::string::iToString(m_plugins.channelId) + " Plug-ins";
+ copy_label(l.c_str());
+ }
+
/* Clear the previous list. */
list->clear();
list->scroll_to(0, 0);
- m::model::ChannelsLock l(m::model::channels);
-
- const m::Channel& ch = m::model::get(m::model::channels, m_channelId);
-
- for (ID pluginId : ch.pluginIds)
- list->addWidget(new gePluginElement(pluginId, m_channelId, 0, 0, 0));
+ for (ID pluginId : m_plugins.pluginIds)
+ list->addWidget(new gePluginElement(0, 0, 0, c::plugin::getPlugin(pluginId, m_plugins.channelId)));
addPlugin = list->addWidget(new geButton(0, 0, 0, G_GUI_UNIT, "-- add new plugin --"));
int ww = m::conf::conf.pluginChooserW;
int wh = m::conf::conf.pluginChooserH;
u::gui::openSubWindow(G_MainWin, new v::gdPluginChooser(wx, wy, ww, wh,
- m_channelId), WID_FX_CHOOSER);
+ m_plugins.channelId), WID_FX_CHOOSER);
}
#define GD_PLUGINLIST_H
-#include "core/pluginHost.h"
+#include "glue/plugin.h"
#include "window.h"
{
public:
- gdPluginList(ID chanID);
+ gdPluginList(ID channelId);
~gdPluginList();
void rebuild() override;
geLiquidScroll* list;
ID m_channelId;
+ c::plugin::Plugins m_plugins;
};
-
}} // giada::v::
#include <FL/fl_draw.H>
+#include "glue/plugin.h"
#include "utils/gui.h"
-#include "core/plugin.h"
-#include "core/model/model.h"
#include "core/const.h"
#include "gui/elems/basics/liquidScroll.h"
#include "gui/elems/plugin/pluginParameter.h"
namespace giada {
namespace v
{
-gdPluginWindow::gdPluginWindow(ID pluginId)
-: gdWindow (450, 156),
- m_pluginId(pluginId)
+gdPluginWindow::gdPluginWindow(const c::plugin::Plugin& plugin)
+: gdWindow(450, 156)
+, m_plugin(plugin)
{
set_non_modal();
m_list = new geLiquidScroll(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN,
w()-(G_GUI_OUTER_MARGIN*2), h()-(G_GUI_OUTER_MARGIN*2));
-
- m::model::PluginsLock l(m::model::plugins);
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
m_list->type(Fl_Scroll::VERTICAL_ALWAYS);
m_list->begin();
- int labelWidth = getLabelWidth();
- int numParams = p.getNumParameters();
- for (int i=0; i<numParams; i++) {
- int py = m_list->y() + (i * (G_GUI_UNIT + G_GUI_INNER_MARGIN));
+ int labelWidth = 100; // TODO
+ for (int index : m_plugin.paramIndexes) {
+ int py = m_list->y() + (index * (G_GUI_UNIT + G_GUI_INNER_MARGIN));
int pw = m_list->w() - m_list->scrollbar_size() - (G_GUI_OUTER_MARGIN*3);
- new v::gePluginParameter(i, m_pluginId, m_list->x(), py, pw, labelWidth);
+ new v::gePluginParameter(m_list->x(), py, pw, labelWidth, c::plugin::getParam(index, m_plugin.id));
}
m_list->end();
end();
- label(p.getName().c_str());
+ label(m_plugin.name.c_str());
size_range(450, (G_GUI_UNIT + (G_GUI_OUTER_MARGIN*2)));
resizable(m_list);
/* -------------------------------------------------------------------------- */
-void gdPluginWindow::updateParameter(int index, bool changeSlider)
-{
- static_cast<v::gePluginParameter*>(m_list->child(index))->update(changeSlider);
-}
-
-
void gdPluginWindow::updateParameters(bool changeSlider)
{
- m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
- {
- for (int i=0; i<p.getNumParameters(); i++) {
- static_cast<v::gePluginParameter*>(m_list->child(i))->update(changeSlider);
- }
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-int gdPluginWindow::getLabelWidth() const
-{
- m::model::PluginsLock l(m::model::plugins);
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
- int width = 0;
- int numParams = p.getNumParameters();
- for (int i=0; i<numParams; i++) {
- int wl = 0, hl = 0;
- fl_measure(p.getParameterName(i).c_str(), wl, hl);
- if (wl > width)
- width = wl;
- }
- return width;
+ for (int index : m_plugin.paramIndexes)
+ static_cast<v::gePluginParameter*>(m_list->child(index))->update(c::plugin::getParam(index, m_plugin.id), changeSlider);
}
}} // giada::v::
namespace giada {
+namespace c {
+namespace plugin
+{
+class Plugin;
+}}
namespace m
{
class Plugin;
{
public:
- gdPluginWindow(ID pluginId);
+ gdPluginWindow(const c::plugin::Plugin&);
- void updateParameter(int index, bool changeSlider=false);
void updateParameters(bool changeSlider=false);
private:
-
- int getLabelWidth() const;
- ID m_pluginId;
+ const c::plugin::Plugin& m_plugin;
geLiquidScroll* m_list;
};
#include <FL/x.H>
#include "utils/log.h"
#include "utils/gui.h"
-#include "core/pluginHost.h"
-#include "core/model/model.h"
-#include "core/plugin.h"
+#include "glue/plugin.h"
#include "core/const.h"
#include "pluginWindowGUI.h"
#ifdef G_OS_MAC
namespace giada {
namespace v
{
-gdPluginWindowGUI::gdPluginWindowGUI(ID pluginId)
+gdPluginWindowGUI::gdPluginWindowGUI(const c::plugin::Plugin& p)
#ifdef G_OS_MAC
-: gdWindow (Fl::w(), Fl::h()),
+: gdWindow(Fl::w(), Fl::h())
#else
-: gdWindow (320, 200),
+: gdWindow(320, 200)
#endif
- m_pluginId(pluginId),
- m_ui (nullptr)
+, m_plugin(p)
+, m_ui (nullptr)
{
show();
resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH);
-
#endif
Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this);
- m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
- {
- copy_label(p.getName().c_str());
- });
+ copy_label(m_plugin.name.c_str());
}
void gdPluginWindowGUI::openEditor(void* parent)
{
- m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p)
- {
- m_ui = p.createEditor();
- });
+ m_ui = m_plugin.createEditor();
if (m_ui == nullptr) {
u::log::print("[gdPluginWindowGUI::openEditor] unable to create editor!\n");
return;
namespace giada {
+namespace c {
+namespace plugin
+{
+struct Plugin;
+}}
namespace v
{
class gdPluginWindowGUI : public gdWindow
{
public:
- gdPluginWindowGUI(ID pluginId);
+ gdPluginWindowGUI(const c::plugin::Plugin&);
~gdPluginWindowGUI();
private:
void openEditor(void* parent);
void closeEditor();
- ID m_pluginId;
+ const c::plugin::Plugin& m_plugin;
juce::AudioProcessorEditor* m_ui;
};
#include <FL/Fl_Group.H>
#include "glue/channel.h"
#include "glue/sampleEditor.h"
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
#include "core/waveFx.h"
#include "core/conf.h"
#include "core/const.h"
namespace giada {
namespace v
{
-gdSampleEditor::gdSampleEditor(ID channelId, ID waveId)
+gdSampleEditor::gdSampleEditor(ID channelId)
: gdWindow (m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY,
- m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH),
- m_channelId(channelId),
- m_waveId (waveId)
+ m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH)
+, m_channelId(channelId)
{
Fl_Group* upperBar = createUpperBar();
- waveTools = new geWaveTools(channelId, waveId, G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN,
+ waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN,
w()-16, h()-128);
Fl_Group* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y()+waveTools->h()+G_GUI_OUTER_MARGIN,
m::conf::conf.sampleEditorGridVal = atoi(grid->text());
m::conf::conf.sampleEditorGridOn = snap->value();
- c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE);
+ c::sampleEditor::stopPreview();
+ c::sampleEditor::cleanupPreview();
}
void gdSampleEditor::rebuild()
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- copy_label(c.name.c_str());
- });
-
- volumeTool->rebuild();
- waveTools->rebuild();
- panTool->rebuild();
- pitchTool->rebuild();
- rangeTool->rebuild();
- shiftTool->rebuild();
-
- m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w)
- {
- updateInfo(w);
- if (w.isLogical()) // Logical samples (aka takes) cannot be reloaded.
- reload->deactivate();
- });
+ m_data = c::sampleEditor::getData(m_channelId);
+
+ copy_label(m_data.name.c_str());
+
+ waveTools->rebuild(m_data);
+ volumeTool->rebuild(m_data);
+ panTool->rebuild(m_data);
+ pitchTool->rebuild(m_data);
+ rangeTool->rebuild(m_data);
+ shiftTool->rebuild(m_data);
+
+ updateInfo();
+
+ if (m_data.isLogical) // Logical samples (aka takes) cannot be reloaded.
+ reload->deactivate();
}
void gdSampleEditor::refresh()
{
waveTools->refresh();
-
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- play->setStatus(c.previewMode == PreviewMode::LOOP || c.previewMode == PreviewMode::NORMAL ? 1 : 0);
- });
+ play->setStatus(m_data.a_getPreviewStatus() == ChannelStatus::PLAY);
}
return g;
}
-
+\
/* -------------------------------------------------------------------------- */
-
+\
Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h)
{
Fl_Group* g = new Fl_Group(x, y, 572, h);
g->begin();
g->resizable(0);
- volumeTool = new geVolumeTool(m_channelId, g->x(), g->y());
- panTool = new gePanTool(m_channelId, volumeTool->x()+volumeTool->w()+4, g->y());
+ volumeTool = new geVolumeTool(m_data, g->x(), g->y());
+ panTool = new gePanTool(m_data, volumeTool->x()+volumeTool->w()+4, g->y());
- pitchTool = new gePitchTool(m_channelId, g->x(), panTool->y()+panTool->h()+8);
+ pitchTool = new gePitchTool(m_data, g->x(), panTool->y()+panTool->h()+8);
- rangeTool = new geRangeTool(m_channelId, m_waveId, g->x(), pitchTool->y()+pitchTool->h()+8);
- shiftTool = new geShiftTool(m_channelId, m_waveId, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8);
+ rangeTool = new geRangeTool(m_data, g->x(), pitchTool->y()+pitchTool->h()+8);
+ shiftTool = new geShiftTool(m_data, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8);
reload = new geButton(g->x()+g->w()-70, shiftTool->y(), 70, 20, "Reload");
g->end();
void gdSampleEditor::cb_togglePreview()
{
- if (play->getStatus())
- c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE);
+ if (!play->getStatus())
+ c::sampleEditor::playPreview(loop->value());
else
- c::sampleEditor::setPreview(m_channelId, loop->value() ? PreviewMode::LOOP : PreviewMode::NORMAL);
+ c::sampleEditor::stopPreview();
}
void gdSampleEditor::cb_rewindPreview()
{
- c::sampleEditor::rewindPreview(m_channelId);
+ c::sampleEditor::setPreviewTracker(m_data.begin);
}
void gdSampleEditor::cb_reload()
{
- c::sampleEditor::reload(m_channelId, m_waveId);
+ c::sampleEditor::reload(m_data.channelId, m_data.waveId);
redraw();
}
/* -------------------------------------------------------------------------- */
-void gdSampleEditor::updateInfo(const m::Wave& w)
+void gdSampleEditor::updateInfo()
{
- std::string bitDepth = w.getBits() != 0 ? u::string::iToString(w.getBits()) : "(unknown)";
+ std::string bitDepth = m_data.waveBits != 0 ? u::string::iToString(m_data.waveBits) : "(unknown)";
std::string infoText =
- "File: " + w.getPath() + "\n"
- "Size: " + u::string::iToString(w.getSize()) + " frames\n"
- "Duration: " + u::string::iToString(w.getDuration()) + " seconds\n"
+ "File: " + m_data.wavePath + "\n"
+ "Size: " + u::string::iToString(m_data.waveSize) + " frames\n"
+ "Duration: " + u::string::iToString(m_data.waveDuration) + " seconds\n"
"Bit depth: " + bitDepth + "\n"
- "Frequency: " + u::string::iToString(w.getRate()) + " Hz\n";
+ "Frequency: " + u::string::iToString(m_data.waveRate) + " Hz\n";
info->copy_label(infoText.c_str());
}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void gdSampleEditor::setWaveId(ID id)
-{
- m_waveId = id;
- waveTools->waveId = id;
- waveTools->waveform->setWaveId(id);
-}
}} // giada::v::
#include "core/types.h"
+#include "glue/sampleEditor.h"
#include "window.h"
namespace giada {
namespace m
{
-class SampleChannel;
class Wave;
}
namespace v
class gePitchTool;
class geRangeTool;
class geShiftTool;
-
class gdSampleEditor : public gdWindow
{
friend class geWaveform;
public:
- gdSampleEditor(ID channelId, ID waveId);
+ gdSampleEditor(ID channelId);
~gdSampleEditor();
void rebuild() override;
void refresh() override;
- void updateInfo(const m::Wave& w);
- void setWaveId(ID id);
-
geChoice* grid;
geCheck* snap;
geBox* sep1;
void cb_enableSnap();
void cb_togglePreview();
void cb_rewindPreview();
-
+
+ void updateInfo();
+
ID m_channelId;
- ID m_waveId;
+
+ c::sampleEditor::Data m_data;
};
}} // giada::v::
#include <cassert>
#include <FL/Fl.H>
-#include "core/channels/channel.h"
#include "core/init.h"
-#include "core/const.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/recManager.h"
-#include "core/conf.h"
-#include "glue/io.h"
-#include "glue/main.h"
+#include "glue/events.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/elems/mainWindow/keyboard/channel.h"
#include "gui/elems/mainWindow/keyboard/keyboard.h"
/* -------------------------------------------------------------------------- */
-void perform_(const geChannel* gch, int event)
+void perform_(ID channelId, int event)
{
- if (event == FL_KEYDOWN)
- c::io::keyPress(gch->channelId, Fl::event_ctrl(), Fl::event_shift(), G_MAX_VELOCITY);
+ if (event == FL_KEYDOWN) {
+ if (Fl::event_ctrl())
+ c::events::toggleMuteChannel(channelId, Thread::MAIN);
+ else
+ if (Fl::event_shift())
+ c::events::killChannel(channelId, Thread::MAIN);
+ else
+ c::events::pressChannel(channelId, G_MAX_VELOCITY, Thread::MAIN);
+ }
else
if (event == FL_KEYUP)
- c::io::keyRelease(gch->channelId, Fl::event_ctrl(), Fl::event_shift());
+ c::events::releaseChannel(channelId, Thread::MAIN);
}
G_MainWin->keyboard->forEachChannel([=](geChannel& c)
{
if (c.handleKey(event))
- perform_(&c, event);
+ perform_(c.getData().id, event);
});
}
if (event == FL_KEYDOWN) {
if (Fl::event_key() == FL_BackSpace && !backspace_) {
backspace_ = true;
- m::mh::rewindSequencer();
+ c::events::rewindSequencer(Thread::MAIN);
}
else if (Fl::event_key() == FL_End && !end_) {
end_ = true;
- c::main::toggleInputRec();
+ c::events::toggleInputRecording();
}
else if (Fl::event_key() == FL_Enter && !enter_) {
enter_ = true;
- m::recManager::toggleActionRec(static_cast<RecTriggerMode>(m::conf::conf.recTriggerMode));
+ c::events::toggleActionRecording();
}
else if (Fl::event_key() == ' ' && !space_) {
space_ = true;
- m::mh::toggleSequencer();
+ c::events::toggleSequencer(Thread::MAIN);
}
else if (Fl::event_key() == FL_Escape && !esc_) {
esc_ = true;
/* -------------------------------------------------------------------------- */
-void dispatchTouch(const geChannel* gch, bool status)
+void dispatchTouch(const geChannel& gch, bool status)
{
triggerSignalCb_();
- perform_(gch, status ? FL_KEYDOWN : FL_KEYUP);
+ perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP);
}
/* dispatchTouch
Processes a mouse click/touch event. */
-void dispatchTouch(const geChannel* gch, bool status);
+void dispatchTouch(const geChannel& gch, bool status);
void setSignalCallback(std::function<void()> f);
}}} // giada::v::dispatcher
namespace giada {
namespace v
{
-geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h)
-: Fl_Group(x, y, w, h),
- m_base (static_cast<gdBaseActionEditor*>(window())),
- m_action(nullptr)
+geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h,
+ gdBaseActionEditor* base)
+: Fl_Group(x, y, w, h)
+, m_data (nullptr)
+, m_base (base)
+, m_action(nullptr)
{
}
geBaseAction* geBaseActionEditor::getActionAtCursor() const
{
- for (int i=0; i<children(); i++) {
+ for (int i = 0; i < children(); i++) {
geBaseAction* a = static_cast<geBaseAction*>(child(i));
if (a->hovered)
return a;
{
public:
- geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h);
-
- /* updateActions
- Rebuild the actions widgets from scratch. */
+ /* updateActions
+ Rebuild the actions widgets from scratch. */
- virtual void rebuild() = 0;
+ virtual void rebuild(c::actionEditor::Data& d) = 0;
/* handle
Override base FL_Group events. */
protected:
+ geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*);
+
+ c::actionEditor::Data* m_data;
+
+ /* m_base
+ Pointer to parent class. */
+
gdBaseActionEditor* m_base;
/* m_action
#include <FL/fl_draw.H>
#include "utils/log.h"
#include "utils/math.h"
-#include "core/channels/sampleChannel.h"
#include "core/const.h"
#include "core/conf.h"
#include "core/action.h"
#include "core/recorder.h"
#include "glue/actionEditor.h"
+#include "glue/channel.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
#include "envelopePoint.h"
#include "envelopeEditor.h"
namespace giada {
namespace v
{
-geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l)
-: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH)
+geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b)
+: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b)
{
copy_label(l);
}
/* -------------------------------------------------------------------------- */
-void geEnvelopeEditor::rebuild()
+void geEnvelopeEditor::rebuild(c::actionEditor::Data& d)
{
- namespace mr = m::recorder;
- namespace ca = c::actionEditor;
+ m_data = &d;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- for (const m::Action& a : m_base->getActions()) {
+ for (const m::Action& a : m_data->actions) {
if (a.event.getStatus() != m::MidiEvent::ENVELOPE)
continue;
add(new geEnvelopePoint(frameToX(a.frame), valueToY(a.event.getVelocity()), a));
Frame f = m_base->pixelToFrame(Fl::event_x() - x());
int v = yToValue(Fl::event_y() - y());
- c::actionEditor::recordEnvelopeAction(m_base->channelId, f, v);
+ c::actionEditor::recordEnvelopeAction(m_data->channelId, f, v);
- m_base->rebuild();
+ m_base->rebuild(); // TODO - USELESS
}
void geEnvelopeEditor::onDeleteAction()
{
- c::actionEditor::deleteEnvelopeAction(m_base->channelId, m_action->a1);
+ c::actionEditor::deleteEnvelopeAction(m_data->channelId, m_action->a1);
- m_base->rebuild();
+ m_base->rebuild(); // TODO - USELESS
}
{
Frame f = m_base->pixelToFrame((m_action->x() - x()) + geEnvelopePoint::SIDE / 2);
float v = yToValue(m_action->y() - y(), geEnvelopePoint::SIDE);
- c::actionEditor::updateEnvelopeAction(m_base->channelId, m_action->a1, f, v);
+ c::actionEditor::updateEnvelopeAction(m_data->channelId, m_action->a1, f, v);
m_base->rebuild();
}
namespace v
{
class geEnvelopePoint;
-
-
class geEnvelopeEditor : public geBaseActionEditor
{
public:
- geEnvelopeEditor(Pixel x, Pixel y, const char* l);
+ geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*);
~geEnvelopeEditor();
void draw() override;
- void rebuild() override;
+ void rebuild(c::actionEditor::Data& d) override;
private:
#include <FL/Fl.H>
-#include "core/channels/midiChannel.h"
#include "core/const.h"
#include "core/conf.h"
#include "gui/dialogs/actionEditor/midiActionEditor.h"
namespace v
{
geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base)
-: geScroll(x, y, 200, 422),
- m_base (base)
+: geScroll(x, y, 200, 422)
+, m_base (base)
{
- pianoRoll = new gePianoRoll(x, y, m_base->fullWidth);
-
- size(m_base->fullWidth, m::conf::conf.pianoRollH);
+ end();
type(Fl_Scroll::VERTICAL_ALWAYS);
+ size(m_base->fullWidth, m::conf::conf.pianoRollH);
+
+ pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, base);
+ add(pianoRoll);
}
/* -------------------------------------------------------------------------- */
-void geNoteEditor::rebuild()
+void geNoteEditor::rebuild(c::actionEditor::Data& d)
{
size(m_base->fullWidth, h());
- pianoRoll->rebuild();
+ pianoRoll->rebuild(d);
}
}} // giada::v::
\ No newline at end of file
{
class gdMidiActionEditor;
class gePianoRoll;
-
-
class geNoteEditor : public geScroll
{
public:
geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base);
~geNoteEditor();
- void rebuild();
+ void rebuild(c::actionEditor::Data& d);
void scroll();
gePianoRoll* pianoRoll;
#include <cassert>
#include <FL/Fl.H>
-#include "core/channels/midiChannel.h"
#include "core/conf.h"
#include "core/const.h"
#include "core/clock.h"
#include "utils/log.h"
#include "utils/string.h"
#include "utils/math.h"
+#include "glue/channel.h"
#include "glue/actionEditor.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
#include "pianoItem.h"
namespace giada {
namespace v
{
-gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W)
- : geBaseActionEditor(X, Y, W, 40),
- pick (0)
+gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, gdBaseActionEditor* b)
+: geBaseActionEditor(X, Y, W, 40, b)
+, pick (0)
{
position(x(), m::conf::conf.pianoRollY == -1 ? y()-(h()/2) : m::conf::conf.pianoRollY);
}
int octave = MAX_OCTAVES;
- for (int i=1; i<=MAX_KEYS+1; i++) {
+ for (int i = 1; i <= MAX_KEYS+1; i++) {
/* print key note label. C C# D D# E F F# G G# A A# B */
{
fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0);
-#if defined(__APPLE__) // TODO - is this still useful?
- for (Pixel i=36; i<m_base->fullWidth; i+=36) /// TODO: i < ae->coverX is faster
+// TODO - is this APPLE thing still useful?
+#if defined(__APPLE__)
+ for (Pixel i = 36; i < m_base->fullWidth; i += 36) /// TODO: i < m_base->loopWidth is faster
fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 1, 0);
#else
- for (Pixel i=CELL_W; i<m_base->loopWidth; i+=CELL_W)
+ for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W)
fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0);
#endif
{
Frame frame = m_base->pixelToFrame(Fl::event_x() - x());
int note = yToNote(Fl::event_y() - y());
- c::actionEditor::recordMidiAction(m_base->channelId, note, G_MAX_VELOCITY,
+ c::actionEditor::recordMidiAction(m_data->channelId, note, G_MAX_VELOCITY,
frame);
m_base->rebuild(); // Rebuild velocityEditor as well
void gePianoRoll::onDeleteAction()
{
- c::actionEditor::deleteMidiAction(m_base->channelId, m_action->a1);
+ c::actionEditor::deleteMidiAction(m_data->channelId, m_action->a1);
m_base->rebuild(); // Rebuild velocityEditor as well
}
int note = yToNote(m_action->y() - y());
int velocity = m_action->a1.event.getVelocity();
- ca::updateMidiAction(m_base->channelId, m_action->a1, note, velocity, f1, f2);
+ ca::updateMidiAction(m_data->channelId, m_action->a1, note, velocity, f1, f2);
m_base->rebuild(); // Rebuild velocityEditor as well
}
/* -------------------------------------------------------------------------- */
-void gePianoRoll::rebuild()
+void gePianoRoll::rebuild(c::actionEditor::Data& d)
{
- namespace ca = c::actionEditor;
+ m_data = &d;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, (MAX_KEYS + 1) * CELL_H);
- for (const m::Action& a1 : m_base->getActions())
+ for (const m::Action& a1 : m_data->actions)
{
if (a1.event.getStatus() == m::MidiEvent::NOTE_OFF)
continue;
static const Pixel CELL_H = 20;
static const Pixel CELL_W = 40;
- gePianoRoll(Pixel x, Pixel y, Pixel w);
+ gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*);
void draw() override;
int handle(int e) override;
- void rebuild() override;
+ void rebuild(c::actionEditor::Data& d) override;
Pixel pick;
#include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
#include "core/const.h"
#include "core/action.h"
#include "sampleAction.h"
#include <cassert>
#include <FL/Fl.H>
#include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
#include "core/recorder.h"
#include "core/const.h"
#include "core/conf.h"
#include "core/action.h"
#include "utils/log.h"
#include "glue/actionEditor.h"
+#include "glue/channel.h"
#include "gui/dialogs/actionEditor/baseActionEditor.h"
#include "sampleAction.h"
#include "sampleActionEditor.h"
namespace giada {
namespace v
{
-geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y)
-: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH)
+geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
+: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b)
{
}
/* -------------------------------------------------------------------------- */
-void geSampleActionEditor::rebuild()
+void geSampleActionEditor::rebuild(c::actionEditor::Data& d)
{
- namespace mr = m::recorder;
- namespace ca = c::actionEditor;
+ m_data = &d;
- bool isSinglePressMode;
- bool isAnyLoopMode;
-
- m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c)
- {
- const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
- isSinglePressMode = sc.mode == ChannelMode::SINGLE_PRESS;
- isAnyLoopMode = sc.isAnyLoopMode();
- });
+ bool isSinglePressMode = m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS;
+ bool isAnyLoopMode = m_data->sample->isLoopMode;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- for (const m::Action& a1 : m_base->getActions()) {
+ for (const m::Action& a1 : m_data->actions) {
if (a1.event.getStatus() == m::MidiEvent::ENVELOPE || isNoteOffSinglePress(a1))
continue;
- m::Action a2 = a1.next != nullptr ? *a1.next : m::Action{};
+ const m::Action& a2 = a1.next != nullptr ? *a1.next : m::Action{};
Pixel px = x() + m_base->frameToPixel(a1.frame);
Pixel py = y() + 4;
void geSampleActionEditor::onAddAction()
{
Frame f = m_base->pixelToFrame(Fl::event_x() - x());
- c::actionEditor::recordSampleAction(m_base->channelId, m_base->getActionType(), f);
-
- m_base->rebuild();
+ c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f);
}
void geSampleActionEditor::onDeleteAction()
{
- c::actionEditor::deleteSampleAction(m_base->channelId, m_action->a1);
-
- m_base->rebuild();
+ c::actionEditor::deleteSampleAction(m_data->channelId, m_action->a1);
}
f2 = m_base->pixelToFrame(p2);
}
- ca::updateSampleAction(m_base->channelId, m_action->a1, type, f1, f2);
+ ca::updateSampleAction(m_data->channelId, m_action->a1, type, f1, f2);
m_base->rebuild();
}
bool geSampleActionEditor::isNoteOffSinglePress(const m::Action& a)
{
- bool res;
- m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c)
- {
- const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
- res = sc.mode == ChannelMode::SINGLE_PRESS && a.event.getStatus() == m::MidiEvent::NOTE_OFF;
- });
- return res;
+ return m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS &&
+ a.event.getStatus() == m::MidiEvent::NOTE_OFF;
+
}
}} // giada::v::
{
public:
- geSampleActionEditor(Pixel x, Pixel y);
+ geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*);
~geSampleActionEditor();
void draw() override;
- void rebuild() override;
+ void rebuild(c::actionEditor::Data& d) override;
private:
#include <FL/fl_draw.H>
#include "utils/log.h"
#include "utils/math.h"
-#include "core/channels/midiChannel.h"
#include "core/const.h"
#include "core/conf.h"
#include "core/action.h"
namespace giada {
namespace v
{
-geVelocityEditor::geVelocityEditor(Pixel x, Pixel y)
-: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH)
+geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b)
+: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b)
{
}
/* -------------------------------------------------------------------------- */
-void geVelocityEditor::rebuild()
+void geVelocityEditor::rebuild(c::actionEditor::Data& d)
{
- namespace ca = c::actionEditor;
+ m_data = &d;
/* Remove all existing actions and set a new width, according to the current
zoom level. */
clear();
size(m_base->fullWidth, h());
- for (const m::Action& action : m_base->getActions())
- {
+ for (const m::Action& action : m_data->actions) {
+
if (action.event.getStatus() == m::MidiEvent::NOTE_OFF)
continue;
{
public:
- geVelocityEditor(Pixel x, Pixel y);
+ geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*);
~geVelocityEditor();
void draw() override;
- void rebuild() override;
+ void rebuild(c::actionEditor::Data& d) override;
private:
void onMoveAction() override;
void onRefreshAction() override;
- void onAddAction() override{};
- void onDeleteAction() override{};
- void onResizeAction() override{};
+ void onAddAction() override {};
+ void onDeleteAction() override {};
+ void onResizeAction() override {};
Pixel valueToY(int v) const;
int yToValue(Pixel y) const;
*
* Giada - Your Hardcore Loopmachine
*
- * geBaseButton
- * Base class for every button widget.
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
*
* Giada - Your Hardcore Loopmachine
*
- * geBaseButton
- * Base class for every button widget.
- *
* -----------------------------------------------------------------------------
*
* Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
#include <string>
#include <FL/Fl_Button.H>
+/* geBaseButton
+Base class for every button widget. */
class geBaseButton : public Fl_Button
{
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <algorithm>
+#include "group.h"
+
+
+namespace giada {
+namespace v
+{
+geGroup::geGroup(int x, int y) : Fl_Group(x, y, 0, 0)
+{
+ end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::size_t geGroup::countChildren() const
+{
+ return m_widgets.size();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geGroup::add(Fl_Widget* widget)
+{
+ widget->position(widget->x() + x(), widget->y() + y());
+
+ Fl_Group::add(widget);
+ m_widgets.push_back(widget);
+
+ int newW = 0;
+ int newH = 0;
+
+ for (const Fl_Widget* wg : m_widgets) {
+ newW = std::max(newW, (wg->x() + wg->w()) - x());
+ newH = std::max(newH, (wg->y() + wg->h()) - y());
+ }
+
+ /* Don't call size(newW, newH) as it changes widgets position. Adjust width
+ and height manually instead. */
+
+ w(newW); h(newH);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geGroup::getChild(std::size_t i)
+{
+ return m_widgets.at(i); // Throws std::out_of_range in case
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geGroup::getLastChild()
+{
+ return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
+}
+}}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_GROUP_H
+#define GE_GROUP_H
+
+
+#include <vector>
+#include <FL/Fl_Group.H>
+
+
+namespace giada {
+namespace v
+{
+/* geGroup
+A group that resizes itself accoring to the content. */
+
+class geGroup : public Fl_Group
+{
+public:
+
+ geGroup(int x, int y);
+
+ /* countChildren
+ Returns the number of widgets contained in this group. */
+
+ std::size_t countChildren() const;
+
+ /* add
+ Adds a Fl_Widget 'w' to this group. Coordinates are relative to the group,
+ so origin starts at (0, 0). */
+
+ void add(Fl_Widget* w);
+
+ Fl_Widget* getChild(std::size_t i);
+ Fl_Widget* getLastChild();
+
+private:
+
+ /* m_widgets
+ The internal Fl_Scroll::array_ is unreliable when inspected with the child()
+ method. Let's keep track of widgets that belong to this group manually. */
+
+ std::vector<Fl_Widget*> m_widgets;
+};
+}}
+
+
+#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "core/const.h"
+#include "pack.h"
+
+
+namespace giada {
+namespace v
+{
+gePack::gePack(int x, int y, Direction d, int gutter)
+: geGroup (x, y)
+, m_direction(d)
+, m_gutter (gutter)
+{
+ end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePack::add(Fl_Widget* widget)
+{
+ if (countChildren() == 0)
+ widget->position(0, 0);
+ else
+ if (m_direction == Direction::HORIZONTAL)
+ widget->position((getLastChild()->x() + getLastChild()->w() + m_gutter) - x(), 0);
+ else
+ widget->position(0, (getLastChild()->y() + getLastChild()->h() + m_gutter) - y());
+
+ geGroup::add(widget);
+}
+}}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * geScroll
+ * Custom scroll with nice scrollbars and something else.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_PACK_H
+#define GE_PACK_H
+
+
+#include "core/const.h"
+#include "gui/elems/basics/group.h"
+
+
+namespace giada {
+namespace v
+{
+enum class Direction { HORIZONTAL, VERTICAL };
+
+/* gePack
+A stack of widgets that resize itself according to its content. */
+
+class gePack : public geGroup
+{
+public:
+
+ gePack(int x, int y, Direction d, int gutter=G_GUI_INNER_MARGIN);
+
+ /* add
+ Adds a Fl_Widget 'w' to this pack. Coordinates are relative to the group,
+ so origin starts at (0, 0). */
+
+ void add(Fl_Widget* w);
+
+private:
+
+ Direction m_direction;
+ int m_gutter;
+};
+}}
+
+
+#endif
geScroll::geScroll(int x, int y, int w, int h, int t)
- : Fl_Scroll(x, y, w, h)
+: Fl_Scroll(x, y, w, h)
{
+ end();
type(t);
scrollbar.color(G_COLOR_GREY_2);
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/const.h"
+#include "boxtypes.h"
+#include "scrollPack.h"
+
+
+namespace giada {
+namespace v
+{
+geScrollPack::geScrollPack(int x, int y, int w, int h, int type, Direction dir,
+ int gutter)
+: geScroll (x, y, w, h, type)
+, m_direction(dir)
+, m_gutter (gutter)
+{
+ end();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+std::size_t geScrollPack::countChildren() const
+{
+ return m_widgets.size();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geScrollPack::add(Fl_Widget* w)
+{
+ if (countChildren() == 0)
+ w->position(x(), y());
+ else
+ if (m_direction == Direction::HORIZONTAL)
+ w->position((getLastChild()->x() + getLastChild()->w() + m_gutter), y());
+ else
+ w->position(x(), (getLastChild()->y() + getLastChild()->h() + m_gutter));
+
+ geScroll::add(w);
+ m_widgets.push_back(w);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geScrollPack::getChild(std::size_t i)
+{
+ return m_widgets.at(i); // Throws std::out_of_range in case
+}
+
+/* -------------------------------------------------------------------------- */
+
+
+Fl_Widget* geScrollPack::getLastChild()
+{
+ return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case
+}
+}}
\ No newline at end of file
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_SCROLL_PACK_H
+#define GE_SCROLL_PACK_H
+
+
+#include "gui/elems/basics/scroll.h"
+#include "gui/elems/basics/pack.h"
+
+
+namespace giada {
+namespace v
+{
+/* geScrollPack
+A scrollable viewport that contains packed widgets. */
+
+class geScrollPack : public geScroll
+{
+public:
+
+ geScrollPack(int x, int y, int w, int h, int type=Fl_Scroll::BOTH,
+ Direction d=Direction::HORIZONTAL, int gutter=G_GUI_INNER_MARGIN);
+
+ /* countChildren
+ Returns the number of widgets contained in this group. */
+
+ std::size_t countChildren() const;
+
+ void add(Fl_Widget* w);
+
+ Fl_Widget* getChild(std::size_t i);
+ Fl_Widget* getLastChild();
+
+private:
+
+ /* m_widgets
+ The internal Fl_Scroll::array_ is unreliable when inspected with the child()
+ method. Let's keep track of widgets that belong to this group manually. */
+
+ std::vector<Fl_Widget*> m_widgets;
+
+ Direction m_direction;
+ int m_gutter;
+};
+}}
+
+
+#endif
{
begin();
- Fl_Group* radioGrp_1 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex
- new geBox(x(), y()+10, 70, 25, "When a channel with recorded actions is halted:", FL_ALIGN_LEFT);
- recsStopOnChanHalt_1 = new geRadio(x()+25, y()+35, 280, 20, "stop it immediately");
- recsStopOnChanHalt_0 = new geRadio(x()+25, y()+60, 280, 20, "play it until finished");
- radioGrp_1->end();
-
- Fl_Group* radioGrp_2 = new Fl_Group(x(), radioGrp_1->y()+radioGrp_1->h(), w(), 70); // radio group for the mutex
- new geBox(x(), y()+80, 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT);
- chansStopOnSeqHalt_1 = new geRadio(x()+25, y()+105, 280, 20, "stop immediately all dynamic channels");
- chansStopOnSeqHalt_0 = new geRadio(x()+25, y()+130, 280, 20, "play all dynamic channels until finished");
+ Fl_Group* radioGrp_2 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex
+ new geBox(x(), radioGrp_2->y(), 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT);
+ chansStopOnSeqHalt_1 = new geRadio(x()+25, radioGrp_2->y() + 25, 280, 20, "stop immediately all dynamic channels");
+ chansStopOnSeqHalt_0 = new geRadio(x()+25, radioGrp_2->y() + 50, 280, 20, "play all dynamic channels until finished");
radioGrp_2->end();
treatRecsAsLoops = new geCheck(x(), radioGrp_2->y()+radioGrp_2->h() + 15, 280, 20, "Treat one shot channels with actions as loops");
labelsize(G_GUI_FONT_SIZE_BASE);
selection_color(G_COLOR_GREY_4);
- m::conf::conf.recsStopOnChanHalt == 1 ? recsStopOnChanHalt_1->value(1) : recsStopOnChanHalt_0->value(1);
m::conf::conf.chansStopOnSeqHalt == 1 ? chansStopOnSeqHalt_1->value(1) : chansStopOnSeqHalt_0->value(1);
treatRecsAsLoops->value(m::conf::conf.treatRecsAsLoops);
inputMonitorDefaultOn->value(m::conf::conf.inputMonitorDefaultOn);
- recsStopOnChanHalt_1->callback(cb_radio_mutex, (void*)this);
- recsStopOnChanHalt_0->callback(cb_radio_mutex, (void*)this);
chansStopOnSeqHalt_1->callback(cb_radio_mutex, (void*)this);
chansStopOnSeqHalt_0->callback(cb_radio_mutex, (void*)this);
}
void geTabBehaviors::save()
{
- m::conf::conf.recsStopOnChanHalt = recsStopOnChanHalt_1->value() == 1 ? 1 : 0;
m::conf::conf.chansStopOnSeqHalt = chansStopOnSeqHalt_1->value() == 1 ? 1 : 0;
m::conf::conf.treatRecsAsLoops = treatRecsAsLoops->value() == 1 ? 1 : 0;
m::conf::conf.inputMonitorDefaultOn = inputMonitorDefaultOn->value() == 1 ? 1 : 0;
void save();
- geRadio *recsStopOnChanHalt_1;
- geRadio *recsStopOnChanHalt_0;
geRadio *chansStopOnSeqHalt_1;
geRadio *chansStopOnSeqHalt_0;
geCheck *treatRecsAsLoops;
#include <FL/Fl.H>
#include <FL/fl_draw.H>
-#include "core/channels/channel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "core/graphics.h"
#include "core/pluginHost.h"
#include "utils/gui.h"
#include "glue/channel.h"
+#include "glue/events.h"
#include "gui/dialogs/mainWindow.h"
#include "gui/dialogs/pluginList.h"
#include "gui/elems/basics/button.h"
namespace giada {
namespace v
{
-geChannel::geChannel(int X, int Y, int W, int H, ID channelId)
-: Fl_Group (X, Y, W, H),
- channelId(channelId)
+geChannel::geChannel(int X, int Y, int W, int H, c::channel::Data d)
+: Fl_Group (X, Y, W, H)
+, m_channel(d)
{
}
void geChannel::refresh()
{
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- if (mainButton->visible())
- mainButton->refresh();
- if (c.recStatus == ChannelStatus::WAIT || c.playStatus == ChannelStatus::WAIT)
- blink();
- playButton->setStatus(c.isPlaying());
- mute->setStatus(c.mute);
- solo->setStatus(c.solo);
- });
+ ChannelStatus playStatus = m_channel.a_getPlayStatus();
+ ChannelStatus recStatus = m_channel.a_getRecStatus();
+
+ if (mainButton->visible())
+ mainButton->refresh();
+
+ if (recStatus == ChannelStatus::WAIT || playStatus == ChannelStatus::WAIT)
+ blink();
+
+ playButton->setStatus(playStatus == ChannelStatus::PLAY || playStatus == ChannelStatus::ENDING);
+ mute->setStatus(m_channel.a_getMute());
+ solo->setStatus(m_channel.a_getSolo());
}
void geChannel::cb_arm()
{
- c::channel::setArm(channelId, arm->value());
+ c::events::toggleArmChannel(m_channel.id, Thread::MAIN);
}
void geChannel::cb_mute()
{
- c::channel::toggleMute(channelId);
+ c::events::toggleMuteChannel(m_channel.id, Thread::MAIN);
}
void geChannel::cb_solo()
{
- c::channel::toggleSolo(channelId);
+ c::events::toggleSoloChannel(m_channel.id, Thread::MAIN);
}
void geChannel::cb_changeVol()
{
- c::channel::setVolume(channelId, vol->value());
+ c::events::setChannelVolume(m_channel.id, vol->value(), Thread::MAIN);
}
#ifdef WITH_VST
void geChannel::cb_openFxWindow()
{
- u::gui::openSubWindow(G_MainWin, new v::gdPluginList(channelId), WID_FX_LIST);
+ u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST);
}
#endif
bool geChannel::handleKey(int e)
{
- m::model::ChannelsLock l(m::model::channels);
- const m::Channel& ch = m::model::get(m::model::channels, channelId);
-
- if (Fl::event_key() != ch.key)
+ if (Fl::event_key() != m_channel.key)
return false;
if (e == FL_KEYDOWN && !playButton->value()) { // Key not already pressed
playButton->value(0);
return true;
}
-
+
return false;
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+const c::channel::Data& geChannel::getData() const
+{
+ return m_channel;
+}
}} // giada::v::
#include <FL/Fl_Group.H>
+#include "glue/channel.h"
#include "core/types.h"
namespace giada {
-namespace m
-{
-class Channel;
-}
namespace v
{
class geChannelButton;
-
class geChannel : public Fl_Group
{
public:
- geChannel(int x, int y, int w, int h, ID channelId);
+ geChannel(int x, int y, int w, int h, c::channel::Data d);
void draw() override;
bool handleKey(int e);
- ID channelId;
+ /* getData
+ Returns a reference to the internal data. Read-only. */
+
+ const c::channel::Data& getData() const;
geStatusButton* playButton;
geButton* arm;
void cb_solo();
void cb_changeVol();
#ifdef WITH_VST
- void cb_openFxWindow();
+ void cb_openFxWindow();
#endif
/* blink
Spread widgets across available space. */
void packWidgets();
+
+ /* m_channel
+ Channel's data. */
+
+ c::channel::Data m_channel;
};
}} // giada::v::
#include "core/model/model.h"
#include "core/const.h"
#include "core/recorder.h"
+#include "glue/channel.h"
#include "utils/string.h"
#include "channelButton.h"
namespace giada {
namespace v
{
-geChannelButton::geChannelButton(int x, int y, int w, int h, ID channelId)
-: geButton (x, y, w, h),
- m_channelId(channelId),
- m_key ("")
+geChannelButton::geChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geButton (x, y, w, h)
+, m_channel(d)
{
}
void geChannelButton::refresh()
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- switch (c.playStatus) {
- case ChannelStatus::OFF:
- case ChannelStatus::EMPTY:
- setDefaultMode(); break;
- case ChannelStatus::PLAY:
- setPlayMode(); break;
- case ChannelStatus::ENDING:
- setEndingMode(); break;
- default: break;
- }
-
- switch (c.recStatus) {
- case ChannelStatus::ENDING:
- setEndingMode(); break;
- default: break;
- }
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geChannelButton::setKey(int k)
-{
- m_key = k == 0 ? "" : std::string(1, k);
- redraw();
+ switch (m_channel.a_getPlayStatus()) {
+ case ChannelStatus::OFF:
+ case ChannelStatus::EMPTY:
+ setDefaultMode(); break;
+ case ChannelStatus::PLAY:
+ setPlayMode(); break;
+ case ChannelStatus::ENDING:
+ setEndingMode(); break;
+ default: break;
+ }
+ switch (m_channel.a_getRecStatus()) {
+ case ChannelStatus::ENDING:
+ setEndingMode(); break;
+ default: break;
+ }
}
{
geButton::draw();
- if (m_key == "")
+ if (m_channel.key == 0)
return;
/* draw background */
fl_color(G_COLOR_LIGHT_2);
fl_font(FL_HELVETICA, 11);
- fl_draw(m_key.c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER);
+ fl_draw(std::string(1, static_cast<wchar_t>(m_channel.key)).c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER);
}
{
bgColor0 = G_COLOR_GREY_4;
}
-
}} // giada::v::
namespace giada {
-namespace m
-{
-class Channel;
-}
+namespace c {
+namespace channel
+{
+struct Data;
+}}
namespace v
{
class geChannelButton : public geButton
{
public:
- geChannelButton(int x, int y, int w, int h, ID channelId);
+ geChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
virtual void refresh();
void draw() override;
- void setKey(int k);
void setPlayMode();
void setEndingMode();
void setDefaultMode(const char* l=0);
protected:
- ID m_channelId;
- std::string m_key;
+ const c::channel::Data& m_channel;
};
}} // giada::v::
#include <cassert>
#include <FL/fl_draw.H>
#include "utils/gui.h"
-#include "core/channels/sampleChannel.h"
+#include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
#include "core/model/model.h"
#include "core/graphics.h"
#include "core/const.h"
namespace giada {
namespace v
{
-geChannelMode::geChannelMode(int x, int y, int w, int h, ID channelId)
-: Fl_Menu_Button(x, y, w, h),
- m_channelId (channelId)
+geChannelMode::geChannelMode(int x, int y, int w, int h, c::channel::Data& d)
+: Fl_Menu_Button(x, y, w, h)
+, m_channel (d)
{
box(G_CUSTOM_BORDER_BOX);
textsize(G_GUI_FONT_SIZE_BASE);
textcolor(G_COLOR_LIGHT_2);
color(G_COLOR_GREY_2);
- add("Loop . basic", 0, cb_changeMode, (void*) ChannelMode::LOOP_BASIC);
- add("Loop . once", 0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE);
- add("Loop . once . bar", 0, cb_changeMode, (void*) ChannelMode::LOOP_ONCE_BAR);
- add("Loop . repeat", 0, cb_changeMode, (void*) ChannelMode::LOOP_REPEAT);
- add("Oneshot . basic", 0, cb_changeMode, (void*) ChannelMode::SINGLE_BASIC);
- add("Oneshot . press", 0, cb_changeMode, (void*) ChannelMode::SINGLE_PRESS);
- add("Oneshot . retrig", 0, cb_changeMode, (void*) ChannelMode::SINGLE_RETRIG);
- add("Oneshot . endless", 0, cb_changeMode, (void*) ChannelMode::SINGLE_ENDLESS);
+ add("Loop . basic", 0, cb_changeMode, (void*) SamplePlayerMode::LOOP_BASIC);
+ add("Loop . once", 0, cb_changeMode, (void*) SamplePlayerMode::LOOP_ONCE);
+ add("Loop . once . bar", 0, cb_changeMode, (void*) SamplePlayerMode::LOOP_ONCE_BAR);
+ add("Loop . repeat", 0, cb_changeMode, (void*) SamplePlayerMode::LOOP_REPEAT);
+ add("Oneshot . basic", 0, cb_changeMode, (void*) SamplePlayerMode::SINGLE_BASIC);
+ add("Oneshot . press", 0, cb_changeMode, (void*) SamplePlayerMode::SINGLE_PRESS);
+ add("Oneshot . retrig", 0, cb_changeMode, (void*) SamplePlayerMode::SINGLE_RETRIG);
+ add("Oneshot . endless", 0, cb_changeMode, (void*) SamplePlayerMode::SINGLE_ENDLESS);
+
+ value(static_cast<int>(m_channel.sample->mode));
}
{
fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border
- m::model::ChannelsLock l(m::model::channels);
- const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, m_channelId));
-
- switch (ch.mode) {
- case ChannelMode::LOOP_BASIC:
+ switch (m_channel.sample->mode) {
+ case SamplePlayerMode::LOOP_BASIC:
fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1);
break;
- case ChannelMode::LOOP_ONCE:
+ case SamplePlayerMode::LOOP_ONCE:
fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1);
break;
- case ChannelMode::LOOP_ONCE_BAR:
+ case SamplePlayerMode::LOOP_ONCE_BAR:
fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1);
break;
- case ChannelMode::LOOP_REPEAT:
+ case SamplePlayerMode::LOOP_REPEAT:
fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1);
break;
- case ChannelMode::SINGLE_BASIC:
+ case SamplePlayerMode::SINGLE_BASIC:
fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1);
break;
- case ChannelMode::SINGLE_PRESS:
+ case SamplePlayerMode::SINGLE_PRESS:
fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1);
break;
- case ChannelMode::SINGLE_RETRIG:
+ case SamplePlayerMode::SINGLE_RETRIG:
fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1);
break;
- case ChannelMode::SINGLE_ENDLESS:
+ case SamplePlayerMode::SINGLE_ENDLESS:
fl_draw_pixmap(oneshotEndless_xpm, x()+1, y()+1);
break;
}
void geChannelMode::cb_changeMode(int mode)
{
- c::channel::setSampleMode(m_channelId, static_cast<ChannelMode>(mode));
+ c::channel::setSamplePlayerMode(m_channel.id, static_cast<SamplePlayerMode>(mode));
}
}} // giada::v::
namespace giada {
-namespace m
-{
-class SampleChannel;
-}
namespace v
{
class geChannelMode : public Fl_Menu_Button
{
public:
- geChannelMode(int x, int y, int w, int h, ID channelId);
+ geChannelMode(int x, int y, int w, int h, c::channel::Data& d);
void draw() override;
static void cb_changeMode(Fl_Widget* v, void* p);
void cb_changeMode(int mode);
- ID m_channelId;
+ c::channel::Data& m_channel;
};
}} // giada::v::
#include <FL/fl_draw.H>
-#include "core/channels/sampleChannel.h"
-#include "core/model/model.h"
-#include "core/mixer.h"
-#include "core/clock.h"
-#include "core/recorder.h"
-#include "core/recManager.h"
#include "core/const.h"
+#include "glue/channel.h"
#include "channelStatus.h"
namespace giada {
namespace v
{
-geChannelStatus::geChannelStatus(int x, int y, int w, int h, ID channelId)
-: Fl_Box(x, y, w, h), channelId(channelId)
+geChannelStatus::geChannelStatus(int x, int y, int w, int h, c::channel::Data& d)
+: Fl_Box (x, y, w, h)
+, m_channel(d)
{
}
fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // reset border
fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // reset background
- m::model::ChannelsLock l(m::model::channels);
- const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, channelId));
-
- if (ch.playStatus == ChannelStatus::WAIT ||
- ch.playStatus == ChannelStatus::ENDING ||
- ch.recStatus == ChannelStatus::WAIT ||
- ch.recStatus == ChannelStatus::ENDING)
+ ChannelStatus playStatus = m_channel.a_getPlayStatus();
+ ChannelStatus recStatus = m_channel.a_getRecStatus();
+ Pixel pos = 0;
+
+ if (playStatus == ChannelStatus::WAIT ||
+ playStatus == ChannelStatus::ENDING ||
+ recStatus == ChannelStatus::WAIT ||
+ recStatus == ChannelStatus::ENDING)
{
fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
}
else
- if (ch.playStatus == ChannelStatus::PLAY)
+ if (playStatus == ChannelStatus::PLAY) {
+ /* Equation for the progress bar:
+ ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
+ Frame tracker = m_channel.sample->a_getTracker();
+ Frame begin = m_channel.sample->a_getBegin();
+ Frame end = m_channel.sample->a_getEnd();
+ pos = ((tracker - begin) * (w() - 1)) / ((end - begin));
fl_rect(x(), y(), w(), h(), G_COLOR_LIGHT_1);
+ }
else
fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // status empty
-
- if (m::recManager::isRecordingInput() && ch.armed)
- fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_RED); // take in progress
- else
- if (m::recManager::isRecordingAction())
- fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_BLUE); // action recording
-
- /* Equation for the progress bar:
- ((chanTracker - chanStart) * w()) / (chanEnd - chanStart). */
-
- int pos = ch.getPosition();
- if (pos == -1)
- pos = 0;
- else
- pos = (pos * (w()-1)) / ((ch.getEnd() - ch.getBegin()));
- fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1);
+ if (pos != 0)
+ fl_rectf(x()+1, y()+1, pos, h()-2, G_COLOR_LIGHT_1);
}
}} // giada::v::
namespace giada {
-namespace m
-{
-class SampleChannel;
-}
+namespace c {
+namespace channel
+{
+struct Data;
+}}
namespace v
{
class geChannelStatus : public Fl_Box
{
public:
- geChannelStatus(int x, int y, int w, int h, ID channelId);
+ geChannelStatus(int x, int y, int w, int h, c::channel::Data& d);
void draw() override;
- ID channelId;
+private:
+
+ c::channel::Data& m_channel;
};
}} // giada::v::
#include <cassert>
#include <FL/fl_draw.H>
#include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
+#include "core/channels/state.h"
#include "core/model/model.h"
#include "glue/channel.h"
#include "utils/log.h"
/* -------------------------------------------------------------------------- */
-geChannel* geColumn::addChannel(ID channelId, ChannelType t, int height)
+geChannel* geColumn::addChannel(c::channel::Data d)
{
geChannel* gch = nullptr;
Fl_Widget* last = m_channels.size() == 0 ? static_cast<Fl_Widget*>(m_addChannelBtn) : m_channels.back();
- if (t == ChannelType::SAMPLE)
- gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId);
+ if (d.type == ChannelType::SAMPLE)
+ gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d);
else
- gch = new geMidiChannel (x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId);
+ gch = new geMidiChannel (x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d);
geResizerBar* bar = new geResizerBar(x(), gch->y() + gch->h(), w(),
G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::VERTICAL, gch);
/* Update the column height while dragging the resizer bar. */
- bar->onDrag = [=](const Fl_Widget* w)
+ bar->onDrag = [this](const Fl_Widget* w)
{
resizable(nullptr);
size(this->w(), (child(children() - 1)->y() - y()) + G_GUI_INNER_MARGIN);
+
};
/* Store the channel height in model when the resizer bar is released. */
- bar->onRelease = [=](const Fl_Widget* w)
+ bar->onRelease = [channelId = d.id, this](const Fl_Widget* w)
{
- storeChannelHeight(w, channelId);
+ resizable(this);
+ c::channel::setHeight(channelId, w->h());
};
m_channels.push_back(gch);
geChannel* geColumn::getChannel(ID channelId) const
{
for (geChannel* c : m_channels)
- if (c->channelId == channelId)
+ if (c->getData().id == channelId)
return c;
return nullptr;
}
out += c->h() + G_GUI_INNER_MARGIN;
return out + m_addChannelBtn->h() + G_GUI_INNER_MARGIN;
}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geColumn::storeChannelHeight(const Fl_Widget* w, ID channelId) const
-{
- m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c)
- {
- c.height = w->h();
- });
-}
-
}} // giada::v::
#include <functional>
#include <vector>
#include <FL/Fl_Group.H>
+#include "glue/channel.h"
#include "core/types.h"
/* addChannel
Adds a new channel in this column. */
- geChannel* addChannel(ID channelId, ChannelType t, int size);
+ geChannel* addChannel(c::channel::Data d);
/* refreshChannels
Updates channels' graphical statues. Called on each GUI cycle. */
int countChannels() const;
int computeHeight() const;
- void storeChannelHeight(const Fl_Widget* c, ID channelId) const;
std::vector<geChannel*> m_channels;
#include <cassert>
#include <FL/fl_draw.H>
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
#include "glue/io.h"
#include "glue/channel.h"
#include "utils/fs.h"
namespace v
{
geKeyboard::geKeyboard(int X, int Y, int W, int H)
-: Fl_Scroll (X, Y, W, H),
- m_addColumnBtn(nullptr)
+: geScroll (X, Y, W, H, Fl_Scroll::BOTH_ALWAYS)
+, m_addColumnBtn(nullptr)
{
end();
-
- color(G_COLOR_GREY_1);
- type(Fl_Scroll::BOTH_ALWAYS);
- scrollbar.color(G_COLOR_GREY_2);
- scrollbar.selection_color(G_COLOR_GREY_4);
- scrollbar.labelcolor(G_COLOR_LIGHT_1);
- scrollbar.slider(G_CUSTOM_BORDER_BOX);
- hscrollbar.color(G_COLOR_GREY_2);
- hscrollbar.selection_color(G_COLOR_GREY_4);
- hscrollbar.labelcolor(G_COLOR_LIGHT_1);
- hscrollbar.slider(G_CUSTOM_BORDER_BOX);
-
init();
}
for (ColumnLayout c : layout)
addColumn(c.width, c.id);
- /* Parse the model and assign each channel to its column. */
-
- m::model::ChannelsLock lock(m::model::channels);
-
- for (const m::Channel* ch : m::model::channels)
- if (!ch->isInternal())
- getColumn(ch->columnId)->addChannel(ch->id, ch->type, ch->height);
+ for (const c::channel::Data& ch : c::channel::getChannels())
+ getColumn(ch.columnId)->addChannel(ch);
redraw();
}
#include <vector>
-#include <FL/Fl_Scroll.H>
-#include "core/channels/channel.h"
+#include <functional>
+#include "gui/elems/basics/scroll.h"
#include "core/idManager.h"
#include "core/const.h"
{
class geColumn;
class geChannel;
-class geSampleChannel;
-
-class geKeyboard : public Fl_Scroll
+class geKeyboard : public geScroll
{
public:
#include <FL/Fl_Menu_Button.H>
#include "core/const.h"
#include "core/graphics.h"
-#include "core/channels/midiChannel.h"
#include "core/model/model.h"
#include "core/recorder.h"
#include "utils/gui.h"
void menuCallback(Fl_Widget* w, void* v)
{
- geMidiChannel* gch = static_cast<geMidiChannel*>(w);
+ const geMidiChannel* gch = static_cast<geMidiChannel*>(w);
+ const c::channel::Data& data = gch->getData();
- Menu selectedItem = (Menu) (intptr_t) v;
-
- switch (selectedItem)
+ switch ((Menu) (intptr_t) v)
{
case Menu::CLEAR_ACTIONS:
case Menu::__END_CLEAR_ACTION_SUBMENU__:
break;
case Menu::EDIT_ACTIONS:
- u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(gch->channelId), WID_ACTION_EDITOR);
+ u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR);
break;
case Menu::CLEAR_ACTIONS_ALL:
- c::recorder::clearAllActions(gch->channelId);
+ c::recorder::clearAllActions(data.id);
break;
case Menu::SETUP_KEYBOARD_INPUT:
- u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId), WID_KEY_GRABBER);
+ u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER);
break;
case Menu::SETUP_MIDI_INPUT:
- u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId), WID_MIDI_INPUT);
+ u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT);
break;
case Menu::SETUP_MIDI_OUTPUT:
- u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(gch->channelId), WID_MIDI_OUTPUT);
+ u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT);
break;
case Menu::CLONE_CHANNEL:
- c::channel::cloneChannel(gch->channelId);
+ c::channel::cloneChannel(data.id);
break;
case Menu::RENAME_CHANNEL:
- u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId), WID_SAMPLE_NAME);
+ u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME);
break;
case Menu::DELETE_CHANNEL:
- c::channel::deleteChannel(gch->channelId);
+ c::channel::deleteChannel(data.id);
break;
}
}
/* -------------------------------------------------------------------------- */
-geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId)
-: geChannel(X, Y, W, H, channelId)
+geMidiChannel::geMidiChannel(int X, int Y, int W, int H, c::channel::Data d)
+: geChannel(X, Y, W, H, d)
+, m_data (d)
{
#if defined(WITH_VST)
constexpr int delta = 6 * (G_GUI_UNIT + G_GUI_INNER_MARGIN);
playButton = new geStatusButton (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm);
arm = new geButton (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm);
- mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId);
+ mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel);
mute = new geStatusButton (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm);
solo = new geStatusButton (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm);
#if defined(WITH_VST)
resizable(mainButton);
- m::model::ChannelsLock l(m::model::channels);
- const m::Channel& ch = m::model::get(m::model::channels, channelId);
-
#ifdef WITH_VST
- fx->setStatus(ch.pluginIds.size() > 0);
+ fx->setStatus(m_channel.pluginIds.size() > 0);
#endif
playButton->callback(cb_playButton, (void*)this);
playButton->when(FL_WHEN_CHANGED); // On keypress && on keyrelease
arm->type(FL_TOGGLE_BUTTON);
- arm->value(ch.armed);
+ arm->value(m_channel.a_isArmed());
arm->callback(cb_arm, (void*)this);
#ifdef WITH_VST
solo->type(FL_TOGGLE_BUTTON);
solo->callback(cb_solo, (void*)this);
- mainButton->setKey(ch.key);
mainButton->callback(cb_openMenu, (void*)this);
- vol->value(ch.volume);
+ vol->value(m_channel.volume);
vol->callback(cb_changeVol, (void*)this);
size(w(), h()); // Force responsiveness
void geMidiChannel::cb_playButton()
{
- v::dispatcher::dispatchTouch(this, playButton->value());
+ v::dispatcher::dispatchTouch(*this, playButton->value());
}
Fl_Menu_Item rclick_menu[] = {
{"Edit actions...", 0, menuCallback, (void*) Menu::EDIT_ACTIONS},
{"Clear actions", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS, FL_SUBMENU},
- {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL},
+ {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL},
{0},
{"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT},
{"Setup MIDI input...", 0, menuCallback, (void*) Menu::SETUP_MIDI_INPUT},
/* No 'clear actions' if there are no actions. */
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- if (!c.hasActions)
- rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate();
- });
+ if (!m_data.hasActions)
+ rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate();
Fl_Menu_Button b(0, 0, 100, 50);
b.box(G_CUSTOM_BORDER_BOX);
namespace giada {
-namespace m
-{
-class MidiChannel;
-}
namespace v
{
class geMidiChannel : public geChannel
{
public:
- geMidiChannel(int x, int y, int w, int h, ID channelId);
+ geMidiChannel(int x, int y, int w, int h, c::channel::Data d);
void resize(int x, int y, int w, int h) override;
static void cb_openMenu(Fl_Widget* v, void* p);
void cb_playButton();
void cb_openMenu();
+
+ c::channel::Data m_data;
};
}} // giada::v::
#include "utils/string.h"
-#include "core/channels/midiChannel.h"
-#include "core/model/model.h"
-#include "core/recManager.h"
+#include "glue/channel.h"
#include "midiChannelButton.h"
namespace giada {
namespace v
{
-geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, ID channelId)
-: geChannelButton(x, y, w, h, channelId)
+geMidiChannelButton::geMidiChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geChannelButton(x, y, w, h, d)
{
- std::string l;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- const m::MidiChannel& mc = static_cast<m::MidiChannel&>(c);
- if (mc.name.empty())
- l = "-- MIDI --";
- else
- l = mc.name.c_str();
-
- if (mc.midiOut)
- l += " (ch " + u::string::iToString(mc.midiOutChan + 1) + " out)";
- });
-
- label(l.c_str());
}
{
geChannelButton::refresh();
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- if (m::recManager::isRecordingAction() && c.armed)
- setActionRecordMode();
- });
+ refreshLabel();
+
+ if (m_channel.a_isRecordingAction() && m_channel.a_isArmed())
+ setActionRecordMode();
redraw();
}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiChannelButton::refreshLabel()
+{
+ std::string l = m_channel.name.empty() ? "-- MIDI --" : m_channel.name;
+
+ if (m_channel.midi->a_isOutputEnabled())
+ l += " (ch " + std::to_string(m_channel.midi->a_getFilter() + 1) + " out)";
+
+ label(l.c_str());
+}
}} // giada::v::
namespace giada {
-namespace m
-{
-class MidiChannel;
-}
namespace v
{
class geMidiChannelButton : public geChannelButton
{
public:
- geMidiChannelButton(int x, int y, int w, int h, ID channelId);
+ geMidiChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
void refresh() override;
+
+private:
+
+ void refreshLabel();
};
}} // giada::v::
#include <cassert>
-#include "core/channels/sampleChannel.h"
+#include "core/channels/channel.h"
+#include "core/channels/samplePlayer.h"
#include "core/model/model.h"
#include "core/mixer.h"
#include "core/conf.h"
#include "core/recManager.h"
#include "glue/io.h"
#include "glue/channel.h"
+#include "glue/events.h"
#include "glue/recorder.h"
#include "glue/storage.h"
#include "utils/gui.h"
void menuCallback(Fl_Widget* w, void* v)
{
- geSampleChannel* gch = static_cast<geSampleChannel*>(w);
+ const geSampleChannel* gch = static_cast<geSampleChannel*>(w);
+ const c::channel::Data& data = gch->getData();
- ID waveId;
- bool inputMonitor;
- m::model::onGet(m::model::channels, gch->channelId, [&](m::Channel& c)
- {
- waveId = static_cast<m::SampleChannel&>(c).waveId;
- inputMonitor = static_cast<m::SampleChannel&>(c).inputMonitor;
- });
-
- Menu selectedItem = (Menu) (intptr_t) v;
-
- switch (selectedItem) {
+ switch ((Menu) (intptr_t) v) {
case Menu::INPUT_MONITOR: {
- c::channel::setInputMonitor(gch->channelId, !inputMonitor);
+ c::channel::setInputMonitor(data.id, !data.sample->a_getInputMonitor());
break;
}
case Menu::LOAD_SAMPLE: {
gdWindow* w = new gdBrowserLoad("Browse sample",
- m::conf::conf.samplePath.c_str(), c::storage::loadSample, gch->channelId);
+ m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id);
u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
break;
}
case Menu::EXPORT_SAMPLE: {
gdWindow* w = new gdBrowserSave("Save sample",
- m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, gch->channelId);
+ m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id);
u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER);
break;
}
case Menu::SETUP_KEYBOARD_INPUT: {
- u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId),
+ u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data),
WID_KEY_GRABBER);
break;
}
case Menu::SETUP_MIDI_INPUT: {
- u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId),
+ u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id),
WID_MIDI_INPUT);
break;
}
case Menu::SETUP_MIDI_OUTPUT: {
- u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(gch->channelId),
+ u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id),
WID_MIDI_OUTPUT);
break;
}
case Menu::EDIT_SAMPLE: {
- u::gui::openSubWindow(G_MainWin, new gdSampleEditor(gch->channelId, waveId),
+ u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id),
WID_SAMPLE_EDITOR);
break;
}
case Menu::EDIT_ACTIONS: {
- u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(gch->channelId),
+ u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id),
WID_ACTION_EDITOR);
break;
}
case Menu::__END_CLEAR_ACTIONS_SUBMENU__:
break;
case Menu::CLEAR_ACTIONS_ALL: {
- c::recorder::clearAllActions(gch->channelId);
+ c::recorder::clearAllActions(data.id);
break;
}
case Menu::CLEAR_ACTIONS_VOLUME: {
- c::recorder::clearVolumeActions(gch->channelId);
+ c::recorder::clearVolumeActions(data.id);
break;
}
case Menu::CLEAR_ACTIONS_START_STOP: {
- c::recorder::clearStartStopActions(gch->channelId);
+ c::recorder::clearStartStopActions(data.id);
break;
}
case Menu::CLONE_CHANNEL: {
- c::channel::cloneChannel(gch->channelId);
+ c::channel::cloneChannel(data.id);
break;
}
case Menu::RENAME_CHANNEL: {
- u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId),
+ u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data),
WID_SAMPLE_NAME);
break;
}
case Menu::FREE_CHANNEL: {
- c::channel::freeChannel(gch->channelId);
+ c::channel::freeChannel(data.id);
break;
}
case Menu::DELETE_CHANNEL: {
- c::channel::deleteChannel(gch->channelId);
+ c::channel::deleteChannel(data.id);
break;
}
}
/* -------------------------------------------------------------------------- */
-geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId)
-: geChannel(X, Y, W, H, channelId)
+geSampleChannel::geSampleChannel(int X, int Y, int W, int H, c::channel::Data d)
+: geChannel(X, Y, W, H, d)
{
#if defined(WITH_VST)
constexpr int delta = 9 * (G_GUI_UNIT + G_GUI_INNER_MARGIN);
playButton = new geStatusButton (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm);
arm = new geButton (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm);
- status = new geChannelStatus (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, channelId);
- mainButton = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId);
+ status = new geChannelStatus (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, m_channel);
+ mainButton = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel);
readActions = new geStatusButton (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, readActionOff_xpm, readActionOn_xpm, readActionDisabled_xpm);
- modeBox = new geChannelMode (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, channelId);
+ modeBox = new geChannelMode (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, m_channel);
mute = new geStatusButton (modeBox->x() + modeBox->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm);
solo = new geStatusButton (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm);
#if defined(WITH_VST)
resizable(mainButton);
- m::model::ChannelsLock l(m::model::channels);
- const m::SampleChannel& ch = static_cast<m::SampleChannel&>(m::model::get(m::model::channels, channelId));
-
- modeBox->value(static_cast<int>(ch.mode));
- modeBox->redraw();
-
#ifdef WITH_VST
- fx->setStatus(ch.pluginIds.size() > 0);
+ fx->setStatus(m_channel.pluginIds.size() > 0);
#endif
playButton->callback(cb_playButton, (void*)this);
playButton->when(FL_WHEN_CHANGED); // On keypress && on keyrelease
arm->type(FL_TOGGLE_BUTTON);
- arm->value(ch.armed);
+ arm->value(m_channel.a_isArmed());
arm->callback(cb_arm, (void*)this);
#ifdef WITH_VST
solo->type(FL_TOGGLE_BUTTON);
solo->callback(cb_solo, (void*)this);
- mainButton->setKey(ch.key);
mainButton->callback(cb_openMenu, (void*)this);
- readActions->setStatus(ch.readActions);
readActions->callback(cb_readActions, (void*)this);
- vol->value(ch.volume);
+ vol->value(m_channel.volume);
vol->callback(cb_changeVol, (void*)this);
size(w(), h()); // Force responsiveness
void geSampleChannel::cb_playButton()
{
- v::dispatcher::dispatchTouch(this, playButton->value());
+ v::dispatcher::dispatchTouch(*this, playButton->value());
}
void geSampleChannel::cb_openMenu()
{
- bool inputMonitor;
- bool isEmptyOrMissing;
- bool hasActions;
- bool isAnyLoopMode;
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
- inputMonitor = sc.inputMonitor;
- isEmptyOrMissing = sc.playStatus == ChannelStatus::EMPTY || sc.playStatus == ChannelStatus::MISSING;
- hasActions = sc.hasActions;
- isAnyLoopMode = sc.isAnyLoopMode();
- });
-
/* If you're recording (input or actions) no menu is allowed; you can't do
anything, especially deallocate the channel. */
Fl_Menu_Item rclick_menu[] = {
{"Input monitor", 0, menuCallback, (void*) Menu::INPUT_MONITOR,
- FL_MENU_TOGGLE | FL_MENU_DIVIDER | (inputMonitor ? FL_MENU_VALUE : 0)},
+ FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->a_getInputMonitor() ? FL_MENU_VALUE : 0)},
{"Load new sample...", 0, menuCallback, (void*) Menu::LOAD_SAMPLE},
{"Export sample to file...", 0, menuCallback, (void*) Menu::EXPORT_SAMPLE},
{"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT},
{0}
};
- if (isEmptyOrMissing) {
+ if (m_channel.sample->waveId == 0) {
rclick_menu[(int) Menu::EXPORT_SAMPLE].deactivate();
rclick_menu[(int) Menu::EDIT_SAMPLE].deactivate();
rclick_menu[(int) Menu::FREE_CHANNEL].deactivate();
rclick_menu[(int) Menu::RENAME_CHANNEL].deactivate();
}
- if (!hasActions)
+ if (!m_channel.hasActions)
rclick_menu[(int) Menu::CLEAR_ACTIONS].deactivate();
-
/* No 'clear start/stop actions' for those channels in loop mode: they cannot
have start/stop actions. */
- if (isAnyLoopMode)
+ if (m_channel.sample->isLoop)
rclick_menu[(int) Menu::CLEAR_ACTIONS_START_STOP].deactivate();
Fl_Menu_Button b(0, 0, 100, 50);
void geSampleChannel::cb_readActions()
{
- c::channel::toggleReadingActions(channelId);
+ c::events::toggleReadActionsChannel(m_channel.id, Thread::MAIN);
}
{
geChannel::refresh();
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- if (c.hasData())
- status->redraw();
- if (c.hasActions) {
- readActions->activate();
- readActions->setStatus(c.readActions);
- }
- else
- readActions->deactivate();
- });
+ if (m_channel.sample->waveId != 0)
+ status->redraw();
+ if (m_channel.hasActions) {
+ readActions->activate();
+ readActions->setStatus(m_channel.a_getReadActions());
+ }
+ else
+ readActions->deactivate();
}
#define GE_SAMPLE_CHANNEL_H
+#include "glue/channel.h"
#include "channel.h"
namespace giada {
-namespace m
-{
-class SampleChannel;
-}
namespace v
{
class geChannelMode;
-
class geSampleChannel : public geChannel
{
public:
- geSampleChannel(int x, int y, int w, int h, ID channelId);
+ geSampleChannel(int x, int y, int w, int h, c::channel::Data d);
void resize(int x, int y, int w, int h) override;
void draw() override;
+
void refresh() override;
geChannelMode* modeBox;
#include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
-#include "core/const.h"
-#include "core/model/model.h"
-#include "core/wave.h"
-#include "core/mixer.h"
-#include "core/recorder.h"
-#include "core/recManager.h"
#include "utils/string.h"
#include "utils/fs.h"
#include "glue/channel.h"
namespace giada {
namespace v
{
-geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, ID channelId)
-: geChannelButton(x, y, w, h, channelId)
+geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d)
+: geChannelButton(x, y, w, h, d)
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- const m::SampleChannel& sc = static_cast<m::SampleChannel&>(c);
-
- switch (sc.playStatus) {
- case ChannelStatus::EMPTY:
- label("-- no sample --");
- break;
- case ChannelStatus::MISSING:
- case ChannelStatus::WRONG:
- label("* file not found! *");
- break;
- default:
- if (sc.name.empty()) {
- m::model::onGet(m::model::waves, sc.waveId, [&](m::Wave& w)
- {
- label(w.getBasename(false).c_str());
- });
- }
- else
- label(sc.name.c_str());
- break;
- }
- });
+ switch (m_channel.a_getPlayStatus()) {
+ case ChannelStatus::MISSING:
+ case ChannelStatus::WRONG:
+ label("* file not found! *");
+ break;
+ default:
+ label(m_channel.sample->waveId == 0 ? "-- no sample --" : m_channel.name.c_str());
+ break;
+ }
}
void geSampleChannelButton::refresh()
{
geChannelButton::refresh();
-
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- if (m::recManager::isRecordingInput() && c.armed)
- setInputRecordMode();
- else
- if (m::recManager::isRecordingAction() && c.hasData())
- setActionRecordMode();
- });
+
+ if (m_channel.a_isRecordingInput() && m_channel.a_isArmed())
+ setInputRecordMode();
+ else
+ if (m_channel.a_isRecordingAction() && m_channel.sample->waveId != 0 && !m_channel.sample->isLoop)
+ setActionRecordMode();
redraw();
}
break;
}
case FL_PASTE: {
- c::channel::loadChannel(m_channelId, u::string::trim(u::fs::stripFileUrl(Fl::event_text())));
+ c::channel::loadChannel(m_channel.id, u::string::trim(u::fs::stripFileUrl(Fl::event_text())));
ret = 1;
break;
}
namespace giada {
-namespace m
-{
-class SampleChannel;
-}
namespace v
{
class geSampleChannelButton : public geChannelButton
{
public:
- geSampleChannelButton(int x, int y, int w, int h, ID channelId);
+ geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d);
int handle(int e) override;
#include "core/const.h"
-#include "core/model/model.h"
#include "core/graphics.h"
-#include "core/mixer.h"
-#include "core/mixerHandler.h"
-#include "core/pluginHost.h"
+#include "glue/events.h"
#include "glue/main.h"
+#include "glue/channel.h"
#include "utils/gui.h"
#include "gui/elems/soundMeter.h"
#include "gui/elems/basics/statusButton.h"
namespace v
{
geMainIO::geMainIO(int x, int y)
-: Fl_Pack(x, y, 396, G_GUI_UNIT)
+: gePack(x, y, Direction::HORIZONTAL)
{
- type(Fl_Pack::HORIZONTAL);
- spacing(G_GUI_INNER_MARGIN);
-
- begin();
-
#if defined(WITH_VST)
masterFxIn = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm);
outMeter = new geSoundMeter (0, 0, 140, G_GUI_UNIT);
outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT);
masterFxOut = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm);
+ add(masterFxIn);
+ add(inVol);
+ add(inMeter);
+ add(inToOut);
+ add(outMeter);
+ add(outVol);
+ add(masterFxOut);
#else
- inVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT);
- inMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
- outMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
- outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+ inVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+ inMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
+ outMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT);
+ outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT);
+ add(inVol);
+ add(inMeter);
+ add(outMeter);
+ add(outVol);
#endif
- end();
-
resizable(nullptr); // don't resize any widget
outVol->callback(cb_outVol, (void*)this);
inVol->callback(cb_inVol, (void*)this);
- outVol->value(m::mh::getOutVol());
- inVol->value(m::mh::getInVol());
-
#ifdef WITH_VST
masterFxOut->callback(cb_masterFxOut, (void*)this);
void geMainIO::cb_outVol()
{
- c::main::setOutVol(outVol->value());
+ c::events::setMasterOutVolume(outVol->value(), Thread::MAIN);
}
void geMainIO::cb_inVol()
{
- c::main::setInVol(inVol->value());
+ c::events::setMasterInVolume(inVol->value(), Thread::MAIN);
}
void geMainIO::cb_inToOut()
{
- m::mh::setInToOut(inToOut->value());
+ c::main::setInToOut(inToOut->value());
}
#endif
void geMainIO::setOutVol(float v)
{
- outVol->value(v);
+ outVol->value(v);
}
void geMainIO::setInVol(float v)
{
- inVol->value(v);
+ inVol->value(v);
}
void geMainIO::setMasterFxOutFull(bool v)
{
- masterFxOut->setStatus(v);
+ masterFxOut->setStatus(v);
}
void geMainIO::setMasterFxInFull(bool v)
{
- masterFxIn->setStatus(v);
+ masterFxIn->setStatus(v);
}
#endif
void geMainIO::refresh()
{
- outMeter->mixerPeak = m::mixer::peakOut.load();
- inMeter->mixerPeak = m::mixer::peakIn.load();
+ outMeter->mixerPeak = m_io.a_getMasterOutPeak();
+ inMeter->mixerPeak = m_io.a_getMasterInPeak();
outMeter->redraw();
inMeter->redraw();
}
void geMainIO::rebuild()
{
- m::model::onGet(m::model::channels, m::mixer::MASTER_OUT_CHANNEL_ID, [&](m::Channel& c)
- {
- outVol->value(c.volume);
-#ifdef WITH_VST
- masterFxOut->setStatus(c.pluginIds.size() > 0);
-#endif
- });
+ m_io = c::main::getIO();
- m::model::onGet(m::model::channels, m::mixer::MASTER_IN_CHANNEL_ID, [&](m::Channel& c)
- {
- inVol->value(c.volume);
+ outVol->value(m_io.masterOutVol);
+ inVol->value(m_io.masterInVol);
#ifdef WITH_VST
- masterFxIn->setStatus(c.pluginIds.size() > 0);
+ masterFxOut->setStatus(m_io.masterOutHasPlugins);
+ masterFxIn->setStatus(m_io.masterInHasPlugins);
+ inToOut->value(m_io.inToOut);
#endif
- });
}
}} // giada::v::
#define GE_MAIN_IO_H
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
+#include "glue/main.h"
class geSoundMeter;
namespace giada {
namespace v
{
-class geMainIO : public Fl_Pack
+class geMainIO : public gePack
{
public:
void cb_inToOut();
#endif
+ c::main::IO m_io;
+
geSoundMeter* outMeter;
geSoundMeter* inMeter;
geDial* outVol;
#include <cassert>
#include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
-#include "core/channels/channel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "core/mixer.h"
namespace v
{
geMainMenu::geMainMenu(int x, int y)
-: Fl_Pack(x, y, 300, 20)
+: gePack(x, y, Direction::HORIZONTAL)
{
- type(Fl_Pack::HORIZONTAL);
- spacing(G_GUI_INNER_MARGIN);
-
- begin();
-
file = new geButton(0, 0, 70, 21, "file");
edit = new geButton(0, 0, 70, 21, "edit");
config = new geButton(0, 0, 70, 21, "config");
about = new geButton(0, 0, 70, 21, "about");
-
- end();
+ add(file);
+ add(edit);
+ add(config);
+ add(about);
resizable(nullptr); // don't resize any widget
}
else
if (strcmp(m->label(), "Close project") == 0) {
- c::main::resetToInitState(/*createColumns=*/true);
+ c::main::closeProject(/*createColumns=*/true);
}
#ifndef NDEBUG
else
void geMainMenu::cb_edit()
{
Fl_Menu_Item menu[] = {
- {"Clear all samples"},
+ {"Free all Sample channels"},
{"Clear all actions"},
{"Setup global MIDI input..."},
{0}
const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b);
if (!m) return;
- if (strcmp(m->label(), "Clear all samples") == 0)
+ if (strcmp(m->label(), "Free all Sample channels") == 0)
c::main::clearAllSamples();
else
if (strcmp(m->label(), "Clear all actions") == 0)
#define GE_MAIN_MENU_H
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
class geButton;
namespace giada {
namespace v
{
-class geMainMenu : public Fl_Pack
+class geMainMenu : public gePack
{
public:
#include "core/const.h"
-#include "core/model/model.h"
-#include "core/kernelAudio.h"
-#include "core/mixer.h"
-#include "core/recManager.h"
#include "core/graphics.h"
#include "core/clock.h"
#include "glue/main.h"
+#include "glue/events.h"
#include "utils/gui.h"
#include "utils/string.h"
#include "gui/elems/basics/button.h"
namespace v
{
geMainTimer::geMainTimer(int x, int y)
- : Fl_Group(x, y, 210, 20)
+: geGroup(x, y)
{
- begin();
-
- quantizer = new geChoice(x, y, 50, 20, "", false);
- bpm = new geButton(quantizer->x()+quantizer->w()+4, y, 50, 20);
- meter = new geButton(bpm->x()+bpm->w()+8, y, 50, 20);
- multiplier = new geButton(meter->x()+meter->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm);
- divider = new geButton(multiplier->x()+multiplier->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_xpm);
-
- end();
+ quantizer = new geChoice(0, 0, 50, 20, "", false);
+ bpm = new geButton(quantizer->x() + quantizer->w() + G_GUI_INNER_MARGIN, 0, 50, G_GUI_UNIT);
+ meter = new geButton(bpm->x() + bpm->w() + G_GUI_OUTER_MARGIN, 0, 50, G_GUI_UNIT);
+ multiplier = new geButton(meter->x() + meter->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", multiplyOff_xpm, multiplyOn_xpm);
+ divider = new geButton(multiplier->x() + multiplier->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", divideOff_xpm, divideOn_xpm);
+ add(quantizer);
+ add(bpm);
+ add(meter);
+ add(multiplier);
+ add(divider);
resizable(nullptr); // don't resize any widget
- bpm->copy_label(u::string::fToString(m::clock::getBpm(), 1).c_str());
bpm->callback(cb_bpm, (void*)this);
-
meter->callback(cb_meter, (void*)this);
-
- multiplier->callback(cb_multiplier, (void*)this);
-
+ multiplier->callback(cb_multiplier, (void*)this);
divider->callback(cb_divider, (void*)this);
quantizer->add("off", 0, cb_quantizer, (void*)this);
quantizer->add("1\\/6", 0, cb_quantizer, (void*)this);
quantizer->add("1\\/8", 0, cb_quantizer, (void*)this);
quantizer->value(0); // "off" by default
-
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
-
- /* Can't change bpm from within Giada when using JACK. */
-
- if (m::kernelAudio::getAPI() == G_SYS_API_JACK)
- bpm->deactivate();
-
-#endif
}
void geMainTimer::cb_multiplier()
{
- c::main::beatsMultiply();
+ c::events::multiplyBeats();
}
void geMainTimer::cb_divider()
{
- c::main::beatsDivide();
+ c::events::divideBeats();
}
void geMainTimer::refresh()
{
- if (m::recManager::isRecordingInput()) {
+ m_timer = c::main::getTimer();
+
+ if (m_timer.isRecordingInput) {
bpm->deactivate();
meter->deactivate();
multiplier->deactivate();
/* Don't reactivate bpm when using JACK. It must stay disabled. */
#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
- if (m::kernelAudio::getAPI() != G_SYS_API_JACK)
- bpm->activate();
+ if (m_timer.isUsingJack)
+ bpm->deactivate();
#else
bpm->activate();
#endif
void geMainTimer::rebuild()
{
- m::model::onGet(m::model::clock, [&](m::model::Clock& c)
- {
- setBpm(c.bpm);
- setMeter(c.beats, c.bars);
- setQuantizer(c.quantize);
- });
+ m_timer = c::main::getTimer();
+
+ setBpm(m_timer.bpm);
+ setMeter(m_timer.beats, m_timer.bars);
+ setQuantizer(m_timer.quantize);
+
+#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+
+ /* Can't change bpm from within Giada when using JACK. */
+
+ if (m_timer.isUsingJack)
+ bpm->deactivate();
+
+#endif
}
#define GE_MAIN_TIMER_H
-#include <FL/Fl_Group.H>
+#include "glue/main.h"
+#include "gui/elems/basics/group.h"
class geButton;
namespace giada {
namespace v
{
-class geMainTimer : public Fl_Group
+class geMainTimer : public geGroup
{
public:
void setQuantizer(int q);
/* setLock
- Locks bpm, beter and multipliers. Used during audio recordings. */
+ Locks bpm, meter and multipliers. Used during audio recordings. */
void setLock(bool v);
void cb_multiplier();
void cb_divider();
+ c::main::Timer m_timer;
+
geButton* bpm;
geButton* meter;
geChoice* quantizer;
#include "core/graphics.h"
#include "core/conf.h"
#include "core/clock.h"
+#include "core/sequencer.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/recManager.h"
#include "core/conf.h"
#include "core/const.h"
#include "glue/main.h"
+#include "glue/events.h"
#include "gui/elems/basics/button.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/statusButton.h"
namespace v
{
geMainTransport::geMainTransport(int x, int y)
-: Fl_Pack(x, y, 200, 25)
+: gePack(x, y, Direction::HORIZONTAL)
{
- type(Fl_Pack::HORIZONTAL);
- spacing(G_GUI_INNER_MARGIN);
-
rewind = new geButton (0, 0, 25, 25, "", rewindOff_xpm, rewindOn_xpm);
play = new geStatusButton(0, 0, 25, 25, play_xpm, pause_xpm);
- new geBox (0, 0, 10, 25);
+ spacer1 = new geBox (0, 0, 10, 25);
recTriggerMode = new geButton (0, 0, 15, 25, "", recTriggerModeOff_xpm, recTriggerModeOn_xpm);
recAction = new geStatusButton(0, 0, 25, 25, recOff_xpm, recOn_xpm);
recInput = new geStatusButton(0, 0, 25, 25, inputRecOff_xpm, inputRecOn_xpm);
- new geBox (0, 0, 10, 25);
+ spacer2 = new geBox (0, 0, 10, 25);
metronome = new geStatusButton(0, 0, 15, 25, metronomeOff_xpm, metronomeOn_xpm);
-
+ add(rewind);
+ add(play);
+ add(spacer1);
+ add(recTriggerMode);
+ add(recAction);
+ add(recInput);
+ add(spacer2);
+ add(metronome);
+
rewind->callback([](Fl_Widget* w, void* v) {
- m::mh::rewindSequencer();
+ c::events::rewindSequencer(Thread::MAIN);
});
play->callback([](Fl_Widget* w, void* v) {
- m::mh::toggleSequencer();
+ c::events::toggleSequencer(Thread::MAIN);
});
recAction->callback([](Fl_Widget* w, void* v) {
- c::main::toggleActionRec();
+ c::events::toggleActionRecording();
});
recInput->callback([](Fl_Widget* w, void* v) {
- c::main::toggleInputRec();
+ c::events::toggleInputRecording();
});
recTriggerMode->value(static_cast<int>(m::conf::conf.recTriggerMode));
metronome->type(FL_TOGGLE_BUTTON);
metronome->callback([](Fl_Widget* w, void* v) {
- m::mixer::toggleMetronome();
+ c::events::toggleMetronome();
});
}
play->setStatus(m::clock::isRunning());
recAction->setStatus(m::recManager::isRecordingAction());
recInput->setStatus(m::recManager::isRecordingInput());
- metronome->setStatus(m::mixer::isMetronomeOn());
+ metronome->setStatus(m::sequencer::isMetronomeOn());
}
-
}} // giada::v::
#define GE_MAIN_TRANSPORT_H
-#include <FL/Fl_Pack.H>
+#include "gui/elems/basics/pack.h"
class geButton;
+class geBox;
class geStatusButton;
namespace giada {
namespace v
{
-class geMainTransport : public Fl_Pack
+class geMainTransport : public gePack
{
public:
geButton* rewind;
geStatusButton* play;
-
+ geBox* spacer1;
geButton* recTriggerMode;
geStatusButton* recAction;
geStatusButton* recInput;
-
+ geBox* spacer2;
geStatusButton* metronome;
};
}} // giada::v::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "utils/string.h"
+#include "gui/elems/basics/boxtypes.h"
+#include "gui/elems/basics/box.h"
+#include "gui/elems/basics/button.h"
+#include "midiLearner.h"
+
+
+namespace giada {
+namespace v
+{
+geMidiLearner::geMidiLearner(int X, int Y, int W, std::string l, int param)
+: Fl_Group (X, Y, W, 20)
+, onStartLearn(nullptr)
+, onStopLearn (nullptr)
+, onClearLearn(nullptr)
+, m_param (param)
+{
+ begin();
+ m_text = new geBox(x(), y(), 156, 20, l.c_str());
+ m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20);
+ m_button = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn");
+ end();
+
+ m_text->box(G_CUSTOM_BORDER_BOX);
+ m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
+
+ m_valueBtn->box(G_CUSTOM_BORDER_BOX);
+ m_valueBtn->callback(cb_value, (void*)this);
+ m_valueBtn->when(FL_WHEN_RELEASE);
+
+ m_button->type(FL_TOGGLE_BUTTON);
+ m_button->callback(cb_button, (void*)this);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::cb_button(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->onLearn(); }
+void geMidiLearner::cb_value(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->onReset(); }
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::update(uint32_t value)
+{
+ std::string tmp = "(not set)";
+
+ if (value != 0x0) {
+ tmp = "0x" + u::string::iToString(value, /*hex=*/true);
+ tmp.pop_back(); // Remove last two digits, useless in MIDI messages
+ tmp.pop_back(); // Remove last two digits, useless in MIDI messages
+ }
+
+ m_valueBtn->copy_label(tmp.c_str());
+ m_button->value(0);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::activate()
+{
+ Fl_Group::activate();
+ m_valueBtn->activate();
+ m_button->activate();
+}
+
+
+void geMidiLearner::deactivate()
+{
+ Fl_Group::deactivate();
+ m_valueBtn->deactivate();
+ m_button->deactivate();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::onLearn() const
+{
+ assert(onStartLearn != nullptr);
+ assert(onStopLearn != nullptr);
+
+ if (m_button->value() == 1)
+ onStartLearn(m_param);
+ else
+ onStopLearn();
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearner::onReset() const
+{
+ assert(onClearLearn != nullptr);
+
+ if (Fl::event_button() == FL_RIGHT_MOUSE)
+ onClearLearn(m_param);
+}
+}} // giada::v::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_MIDI_LEARNER_H
+#define GE_MIDI_LEARNER_H
+
+
+#include <functional>
+#include <string>
+#include <FL/Fl_Group.H>
+
+
+class geBox;
+class geButton;
+
+
+namespace giada {
+namespace v
+{
+class geMidiLearner : public Fl_Group
+{
+public:
+
+ geMidiLearner(int x, int y, int w, std::string l, int param);
+
+ /* update
+ Updates and repaints the label widget with value 'value'. */
+
+ void update(uint32_t value);
+
+ void activate();
+ void deactivate();
+
+ std::function<void(uint32_t)> onStartLearn;
+ std::function<void()> onStopLearn;
+ std::function<void(uint32_t)> onClearLearn;
+
+protected:
+
+ /* m_param
+ Parameter index to be learnt. */
+
+ int m_param;
+
+ geBox* m_text;
+ geButton* m_valueBtn;
+ geButton* m_button;
+
+private:
+
+ static void cb_button(Fl_Widget* v, void* p);
+ static void cb_value (Fl_Widget* v, void* p);
+
+ void onLearn() const;
+ void onReset() const;
+
+};
+}} // giada::v::
+
+
+#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <string>
-#include "utils/string.h"
-#include "gui/elems/basics/boxtypes.h"
-#include "gui/elems/basics/box.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v
-{
-geMidiLearnerBase::geMidiLearnerBase(int X, int Y, int W, std::string l, int param, uint32_t value)
-: Fl_Group (X, Y, W, 20),
- m_param (param)
-{
- begin();
- m_text = new geBox(x(), y(), 156, 20, l.c_str());
- m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20);
- m_button = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn");
- end();
-
- m_text->box(G_CUSTOM_BORDER_BOX);
- m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
-
- m_valueBtn->box(G_CUSTOM_BORDER_BOX);
- m_valueBtn->callback(cb_value, (void*)this);
- m_valueBtn->when(FL_WHEN_RELEASE);
-
- m_button->type(FL_TOGGLE_BUTTON);
- m_button->callback(cb_button, (void*)this);
-
- update(value);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::cb_button(Fl_Widget* v, void* p) { ((geMidiLearnerBase*)p)->onLearn(); }
-void geMidiLearnerBase::cb_value(Fl_Widget* v, void* p) { ((geMidiLearnerBase*)p)->onReset(); }
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::update(uint32_t value)
-{
- std::string tmp = "(not set)";
-
- if (value != 0x0) {
- tmp = "0x" + u::string::iToString(value, /*hex=*/true);
- tmp.pop_back(); // Remove last two digits, useless in MIDI messages
- tmp.pop_back(); // Remove last two digits, useless in MIDI messages
- }
-
- m_valueBtn->copy_label(tmp.c_str());
- m_button->value(0);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerBase::activate()
-{
- Fl_Group::activate();
- m_valueBtn->activate();
- m_button->activate();
-}
-
-
-void geMidiLearnerBase::deactivate()
-{
- Fl_Group::deactivate();
- m_valueBtn->deactivate();
- m_button->deactivate();
-}
-}} // giada::v::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef GE_MIDI_LEARNER_BASE_H
-#define GE_MIDI_LEARNER_BASE_H
-
-
-#include <string>
-#include <FL/Fl_Group.H>
-
-
-class geBox;
-class geButton;
-
-
-namespace giada {
-namespace v
-{
-class geMidiLearnerBase : public Fl_Group
-{
-public:
-
- virtual ~geMidiLearnerBase() = default;
-
- virtual void refresh() = 0;
- virtual void onLearn() = 0;
- virtual void onReset() = 0;
-
- void activate();
- void deactivate();
-
-protected:
-
- geMidiLearnerBase(int x, int y, int w, std::string l, int param, uint32_t value);
-
- /* update
- Updates and repaints the label widget with value 'value'. */
-
- void update(uint32_t value);
-
- /* m_param
- Parameter index to be learnt. */
-
- int m_param;
-
- geBox* m_text;
- geButton* m_valueBtn;
- geButton* m_button;
-
-private:
-
- static void cb_button(Fl_Widget* v, void* p);
- static void cb_value (Fl_Widget* v, void* p);
-};
-}} // giada::v::
-
-
-#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "core/channels/sampleChannel.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerChannel.h"
-
-
-namespace giada {
-namespace v
-{
-geMidiLearnerChannel::geMidiLearnerChannel(int x, int y, int w, std::string l, int param, uint32_t value, ID channelId)
-: geMidiLearnerBase(x, y, w, l, param, value),
- m_channelId (channelId)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerChannel::refresh()
-{
- m::model::onGet(m::model::channels, m_channelId, [&](const m::Channel& c)
- {
- switch (m_param) {
- case G_MIDI_IN_KEYPRESS : update(c.midiInKeyPress); break;
- case G_MIDI_IN_KEYREL : update(c.midiInKeyRel); break;
- case G_MIDI_IN_KILL : update(c.midiInKill); break;
- case G_MIDI_IN_ARM : update(c.midiInArm); break;
- case G_MIDI_IN_MUTE : update(c.midiInVolume); break;
- case G_MIDI_IN_SOLO : update(c.midiInMute); break;
- case G_MIDI_IN_VOLUME : update(c.midiInSolo); break;
- case G_MIDI_IN_PITCH : update(static_cast<const m::SampleChannel&>(c).midiInPitch); break;
- case G_MIDI_IN_READ_ACTIONS : update(static_cast<const m::SampleChannel&>(c).midiInReadActions); break;
- case G_MIDI_OUT_L_PLAYING : update(static_cast<const m::SampleChannel&>(c).midiOutLplaying); break;
- case G_MIDI_OUT_L_MUTE : update(static_cast<const m::SampleChannel&>(c).midiOutLmute); break;
- case G_MIDI_OUT_L_SOLO : update(static_cast<const m::SampleChannel&>(c).midiOutLsolo); break;
- }
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerChannel::onLearn()
-{
- if (m_button->value() == 1)
- c::io::startChannelMidiLearn(m_param, m_channelId);
- else
- c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerChannel::onReset()
-{
- if (Fl::event_button() == FL_RIGHT_MOUSE)
- c::io::clearChannelMidiLearn(m_param, m_channelId);
-}
-}} // giada::v::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef GE_MIDI_LEARNER_CHANNEL_H
-#define GE_MIDI_LEARNER_CHANNEL_H
-
-
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v
-{
-class geMidiLearnerChannel : public geMidiLearnerBase
-{
-public:
-
- geMidiLearnerChannel(int x, int y, int w, std::string l, int param, uint32_t value, ID channelId);
-
- void refresh() override;
- void onLearn() override;
- void onReset() override;
-
-private:
-
- ID m_channelId;
-};
-}} // giada::v::
-
-
-#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerMaster.h"
-
-
-namespace giada {
-namespace v
-{
-geMidiLearnerMaster::geMidiLearnerMaster(int X, int Y, int W, std::string l, int param, uint32_t value)
-: geMidiLearnerBase(X, Y, W, l, param, value)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerMaster::refresh()
-{
- m::model::onGet(m::model::midiIn, [&](const m::model::MidiIn& m)
- {
- switch (m_param) {
- case G_MIDI_IN_REWIND : update(m.rewind); break;
- case G_MIDI_IN_START_STOP : update(m.startStop); break;
- case G_MIDI_IN_ACTION_REC : update(m.actionRec); break;
- case G_MIDI_IN_INPUT_REC : update(m.inputRec); break;
- case G_MIDI_IN_METRONOME : update(m.volumeIn); break;
- case G_MIDI_IN_VOLUME_IN : update(m.volumeOut); break;
- case G_MIDI_IN_VOLUME_OUT : update(m.beatDouble); break;
- case G_MIDI_IN_BEAT_DOUBLE : update(m.beatHalf); break;
- case G_MIDI_IN_BEAT_HALF : update(m.metronome); break;
- }
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerMaster::onLearn()
-{
- if (m_button->value() == 1)
- c::io::startMasterMidiLearn(m_param);
- else
- c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerMaster::onReset()
-{
- if (Fl::event_button() == FL_RIGHT_MOUSE)
- c::io::clearMasterMidiLearn(m_param);
-}
-}} // giada::v::
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifndef GE_MIDI_LEARNER_MASTER_H
-#define GE_MIDI_LEARNER_MASTER_H
-
-
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v
-{
-class geMidiLearnerMaster : public geMidiLearnerBase
-{
-public:
-
- geMidiLearnerMaster(int x, int y, int w, std::string l, int param, uint32_t value);
-
- void refresh() override;
- void onLearn() override;
- void onReset() override;
-};
-}} // giada::v::
-
-
-#endif
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include <cassert>
+#include "core/const.h"
+#include "glue/io.h"
+#include "gui/elems/basics/box.h"
+#include "midiLearnerPack.h"
+
+
+namespace giada {
+namespace v
+{
+constexpr int LEARNER_WIDTH = 284;
+
+
+/* -------------------------------------------------------------------------- */
+
+
+geMidiLearnerPack::geMidiLearnerPack(int X, int Y, std::string title)
+: gePack(X, Y, Direction::VERTICAL)
+{
+ end();
+
+ if (title != "") {
+ geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, title.c_str());
+ header->box(FL_BORDER_BOX);
+ add(header);
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::setCallbacks(std::function<void(uint32_t)> s, std::function<void(uint32_t)> c)
+{
+ m_onStartLearn = s;
+ m_onClearLearn = c;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::addMidiLearner(std::string label, int param, bool visible)
+{
+ geMidiLearner* l = new geMidiLearner(0, 0, LEARNER_WIDTH, label, param);
+
+ l->onStartLearn = m_onStartLearn;
+ l->onClearLearn = m_onClearLearn;
+ l->onStopLearn = [] () { c::io::stopMidiLearn(); };
+
+ add(l);
+ if (!visible) l->hide();
+ learners.push_back(l);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geMidiLearnerPack::setEnabled(bool v)
+{
+ if (v) for (auto* l : learners) l->activate();
+ else for (auto* l : learners) l->deactivate();
+}
+}} // giada::v::
--- /dev/null
+/* -----------------------------------------------------------------------------
+ *
+ * Giada - Your Hardcore Loopmachine
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
+ *
+ * This file is part of Giada - Your Hardcore Loopmachine.
+ *
+ * Giada - Your Hardcore Loopmachine is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Giada - Your Hardcore Loopmachine is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Giada - Your Hardcore Loopmachine. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef GE_LEARNER_PACK_H
+#define GE_LEARNER_PACK_H
+
+
+#include <string>
+#include <vector>
+#include "gui/elems/basics/pack.h"
+#include "gui/elems/midiIO/midiLearner.h"
+
+
+namespace giada {
+namespace v
+{
+class geMidiLearnerPack : public gePack
+{
+public:
+
+ geMidiLearnerPack(int x, int y, std::string title="");
+
+ void setCallbacks(std::function<void(uint32_t)>, std::function<void(uint32_t)>);
+ void addMidiLearner(std::string label, int param, bool visible=true);
+ void setEnabled(bool v);
+
+ std::vector<geMidiLearner*> learners;
+
+private:
+
+ std::function<void(uint32_t)> m_onStartLearn;
+ std::function<void(uint32_t)> m_onClearLearn;
+};
+}} // giada::v::
+
+
+#endif
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifdef WITH_VST
-
-
-#include <FL/Fl.H>
-#include "core/model/model.h"
-#include "glue/io.h"
-#include "gui/elems/basics/button.h"
-#include "midiLearnerPlugin.h"
-
-
-namespace giada {
-namespace v
-{
-geMidiLearnerPlugin::geMidiLearnerPlugin(int x, int y, int w, std::string l, int param, uint32_t value, ID pluginId)
-: geMidiLearnerBase(x, y, w, l, param, value),
- m_pluginId (pluginId)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerPlugin::refresh()
-{
- m::model::onGet(m::model::plugins, m_pluginId, [&](const m::Plugin& p)
- {
- assert(static_cast<size_t>(m_param) < p.midiInParams.size());
- update(p.midiInParams[m_param]);
- });
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void geMidiLearnerPlugin::onLearn()
-{
- if (m_button->value() == 1)
- c::io::startPluginMidiLearn(m_param, m_pluginId);
- else
- c::io::stopMidiLearn();
-}
-
-
-void geMidiLearnerPlugin::onReset()
-{
- if (Fl::event_button() == FL_RIGHT_MOUSE)
- c::io::clearPluginMidiLearn(m_param, m_pluginId);
-}
-}} // giada::v::
-
-
-#endif
\ No newline at end of file
+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * Giada - Your Hardcore Loopmachine
- *
- * -----------------------------------------------------------------------------
- *
- * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual
- *
- * This file is part of Giada - Your Hardcore Loopmachine.
- *
- * Giada - Your Hardcore Loopmachine is free software: you can
- * redistribute it and/or modify it under the terms of the GNU General
- * Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * Giada - Your Hardcore Loopmachine is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Giada - Your Hardcore Loopmachine. If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * -------------------------------------------------------------------------- */
-
-
-#ifdef WITH_VST
-
-
-#ifndef GE_MIDI_LEARNER_PLUGIN_H
-#define GE_MIDI_LEARNER_PLUGIN_H
-
-
-#include "core/types.h"
-#include "midiLearnerBase.h"
-
-
-namespace giada {
-namespace v
-{
-class geMidiLearnerPlugin : public geMidiLearnerBase
-{
-public:
-
- geMidiLearnerPlugin(int x, int y, int w, std::string l, int param, uint32_t value, ID pluginId);
-
- void refresh() override;
- void onLearn() override;
- void onReset() override;
-
-private:
-
- ID m_pluginId;
-};
-}} // giada::v::
-
-
-#endif
-#endif
#include <cassert>
#include <string>
-#include "core/channels/channel.h"
-#include "core/model/model.h"
#include "core/graphics.h"
#include "core/pluginHost.h"
#include "core/plugin.h"
namespace giada {
namespace v
{
-gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W)
-: Fl_Pack (X, Y, W, G_GUI_UNIT),
- m_channelId(channelId),
- m_pluginId (pluginId)
+gePluginElement::gePluginElement(int X, int Y, int W, c::plugin::Plugin data)
+: Fl_Pack(X, Y, W, G_GUI_UNIT)
+, m_plugin (data)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
remove = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", fxRemoveOff_xpm, fxRemoveOn_xpm);
end();
- m::model::PluginsLock l(m::model::plugins);
-
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
remove->callback(cb_removePlugin, (void*)this);
- if (!p.valid) {
- button->copy_label(p.getUniqueId().c_str());
+ if (!m_plugin.valid) {
+ button->copy_label(m_plugin.uniqueId.c_str());
button->deactivate();
bypass->deactivate();
shiftUp->deactivate();
shiftDown->deactivate();
return;
}
- button->copy_label(p.getName().c_str());
+ button->copy_label(m_plugin.name.c_str());
button->callback(cb_openPluginWindow, (void*)this);
program->callback(cb_setProgram, (void*)this);
- for (int i=0; i<p.getNumPrograms(); i++)
- program->add(u::gui::removeFltkChars(p.getProgramName(i)).c_str());
+ for (const auto& p : m_plugin.programs)
+ program->add(u::gui::removeFltkChars(p.name).c_str());
if (program->size() == 0) {
program->add("-- no programs --\0");
program->deactivate();
}
else
- program->value(p.getCurrentProgram());
+ program->value(m_plugin.currentProgram);
bypass->callback(cb_setBypass, (void*)this);
bypass->type(FL_TOGGLE_BUTTON);
- bypass->value(p.isBypassed() ? 0 : 1);
+ bypass->value(m_plugin.isBypassed ? 0 : 1);
shiftUp->callback(cb_shiftUp, (void*)this);
shiftDown->callback(cb_shiftDown, (void*)this);
ID gePluginElement::getPluginId() const
{
- return m_pluginId;
+ return m_plugin.id;
}
{
const gdPluginList* parent = static_cast<const gdPluginList*>(window());
- c::plugin::swapPlugins(m_pluginId, parent->getPrevElement(*this).getPluginId(), m_channelId);
+ c::plugin::swapPlugins(m_plugin.id, parent->getPrevElement(*this).getPluginId(), m_plugin.channelId);
}
{
const gdPluginList* parent = static_cast<const gdPluginList*>(window());
- c::plugin::swapPlugins(m_pluginId, parent->getNextElement(*this).getPluginId(), m_channelId);
+ c::plugin::swapPlugins(m_plugin.id, parent->getNextElement(*this).getPluginId(), m_plugin.channelId);
}
pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent
window 'add plugin'.*/
- static_cast<gdWindow*>(window())->delSubWindow(m_pluginId + 1);
- c::plugin::freePlugin(m_pluginId, m_channelId);
+ static_cast<gdWindow*>(window())->delSubWindow(m_plugin.id + 1);
+ c::plugin::freePlugin(m_plugin.id, m_plugin.channelId);
}
void gePluginElement::cb_openPluginWindow()
{
- m::model::PluginsLock l(m::model::plugins);
-
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
/* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for
the parent window 'add plugin'. */
- int pwid = m_pluginId + 1;
+ int pwid = m_plugin.id + 1;
gdWindow* parent = static_cast<gdWindow*>(window());
gdWindow* child = parent->getChild(pwid);
child->show(); // Raise it to top
}
else {
- if (p.hasEditor())
- child = new gdPluginWindowGUI(m_pluginId);
+ if (m_plugin.hasEditor)
+ child = new gdPluginWindowGUI(m_plugin);
else
- child = new gdPluginWindow(m_pluginId);
+ child = new gdPluginWindow(m_plugin);
child->setId(pwid);
parent->addSubWindow(child);
}
void gePluginElement::cb_setBypass()
{
- c::plugin::toggleBypass(m_pluginId);
+ c::plugin::toggleBypass(m_plugin.id);
}
void gePluginElement::cb_setProgram()
{
- c::plugin::setProgram(m_pluginId, program->value());
+ c::plugin::setProgram(m_plugin.id, program->value());
}
}} // giada::v::
#include <FL/Fl_Pack.H>
+#include "glue/plugin.h"
class geChoice;
{
public:
- gePluginElement(ID pluginId, ID channelId, int x, int y, int w);
+ gePluginElement(int x, int y, int w, c::plugin::Plugin);
ID getPluginId() const;
void cb_shiftDown();
void cb_setProgram();
- ID m_channelId;
- ID m_pluginId;
+ c::plugin::Plugin m_plugin;
};
}} // giada::v::
#ifdef WITH_VST
-#include "core/model/model.h"
-#include "core/plugin.h"
#include "core/const.h"
#include "glue/plugin.h"
+#include "glue/events.h"
#include "gui/elems/basics/boxtypes.h"
#include "gui/elems/basics/box.h"
#include "gui/elems/basics/slider.h"
namespace giada {
namespace v
{
-gePluginParameter::gePluginParameter(int paramIndex, ID pluginId,
- int X, int Y, int W, int labelWidth)
-: Fl_Group (X, Y, W, G_GUI_UNIT),
- m_pluginId (pluginId),
- m_paramIndex(paramIndex)
+gePluginParameter::gePluginParameter(int X, int Y, int W, int labelWidth, const c::plugin::Param p)
+: Fl_Group (X, Y, W, G_GUI_UNIT)
+, m_param (p)
{
- m::model::PluginsLock l(m::model::plugins);
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
begin();
const int VALUE_WIDTH = 100;
m_label = new geBox(x(), y(), labelWidth, G_GUI_UNIT);
- m_label->copy_label(p.getParameterName(m_paramIndex).c_str());
+ m_label->copy_label(m_param.name.c_str());
m_label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
m_slider = new geSlider(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN, y(),
w()-(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN)-VALUE_WIDTH, G_GUI_UNIT);
- m_slider->value(p.getParameter(m_paramIndex));
+ m_slider->value(m_param.value);
m_slider->callback(cb_setValue, (void*)this);
m_value = new geBox(m_slider->x()+m_slider->w()+G_GUI_OUTER_MARGIN, y(), VALUE_WIDTH, G_GUI_UNIT);
m_value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
m_value->box(G_CUSTOM_BORDER_BOX);
-
+
end();
+
resizable(m_slider);
- update(false);
+ update(m_param, false);
}
/* -------------------------------------------------------------------------- */
-void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); }
+void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); }
/* -------------------------------------------------------------------------- */
void gePluginParameter::cb_setValue()
{
- c::plugin::setParameter(m_pluginId, m_paramIndex, m_slider->value(),
+ c::events::setPluginParameter(m_param.pluginId, m_param.index, m_slider->value(),
/*gui=*/true);
}
/* -------------------------------------------------------------------------- */
-void gePluginParameter::update(bool changeSlider)
+void gePluginParameter::update(const c::plugin::Param& p, bool changeSlider)
{
- m::model::PluginsLock l(m::model::plugins);
- const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId);
-
- std::string v = p.getParameterText(m_paramIndex) + " " +
- p.getParameterLabel(m_paramIndex);
-
- m_value->copy_label(v.c_str());
-
+ m_value->copy_label(std::string(p.text + " " + p.label).c_str());
if (changeSlider)
- m_slider->value(p.getParameter(m_paramIndex));
+ m_slider->value(p.value);
}
}} // giada::v::
namespace giada {
+namespace c {
+namespace plugin
+{
+struct Param;
+}}
namespace v
{
class gePluginParameter : public Fl_Group
{
public:
- gePluginParameter(int paramIndex, ID pluginId, int x, int y, int w,
- int labelWidth);
+ gePluginParameter(int x, int y, int w, int labelWidth, const c::plugin::Param);
- void update(bool changeSlider);
+ void update(const c::plugin::Param& p, bool changeSlider);
private:
static void cb_setValue(Fl_Widget* v, void* p);
void cb_setValue();
- ID m_pluginId;
- int m_paramIndex;
+ const c::plugin::Param m_param;
geBox* m_label;
geSlider* m_slider;
#include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
#include "core/const.h"
#include "core/waveFx.h"
#include "glue/channel.h"
#include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "core/waveFx.h"
-#include "glue/channel.h"
+#include "glue/events.h"
#include "utils/gui.h"
#include "utils/math.h"
#include "utils/string.h"
namespace giada {
namespace v
{
-gePanTool::gePanTool(ID channelId, int x, int y)
-: Fl_Pack (x, y, 200, G_GUI_UNIT),
- m_channelId(channelId)
+gePanTool::gePanTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 200, G_GUI_UNIT)
+, m_data (nullptr)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
reset = new geButton(0, 0, 70, G_GUI_UNIT, "Reset");
end();
- dial->range(0.0f, 1.0f);
+ dial->range(0.0f, G_MAX_PAN);
dial->callback(cb_panning, (void*)this);
input->align(FL_ALIGN_RIGHT);
input->cursor_color(FL_WHITE);
reset->callback(cb_panReset, (void*)this);
+
+ rebuild(d);
}
/* -------------------------------------------------------------------------- */
-void gePanTool::rebuild()
+void gePanTool::rebuild(const c::sampleEditor::Data& d)
{
- float p;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- p = static_cast<m::SampleChannel&>(c).getPan();
- });
+ m_data = &d;
+ update(m_data->pan);
+
+}
- dial->value(p);
- if (p < 0.5f) {
- std::string tmp = u::string::iToString((int) ((-p * 200.0f) + 100.0f)) + " L";
+/* -------------------------------------------------------------------------- */
+
+
+void gePanTool::update(float v)
+{
+ dial->value(v);
+
+ if (v < 0.5f) {
+ std::string tmp = u::string::iToString((int) ((-v * 200.0f) + 100.0f)) + " L";
input->value(tmp.c_str());
}
else
- if (p == 0.5)
+ if (v == 0.5)
input->value("C");
else {
- std::string tmp = u::string::iToString((int) ((p * 200.0f) - 100.0f)) + " R";
+ std::string tmp = u::string::iToString((int) ((v * 200.0f) - 100.0f)) + " R";
input->value(tmp.c_str());
}
}
void gePanTool::cb_panning()
{
- c::channel::setPan(m_channelId, dial->value());
+ c::events::sendChannelPan(m_data->channelId, dial->value());
}
void gePanTool::cb_panReset()
{
- c::channel::setPan(m_channelId, 0.5f);
+ c::events::sendChannelPan(m_data->channelId, 0.5f);
}
-
}} // giada::v::
{
public:
- gePanTool(ID channelId, int x, int y);
+ gePanTool(const c::sampleEditor::Data& d, int x, int y);
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
+ void update(float v);
private:
void cb_panning();
void cb_panReset();
- ID m_channelId;
+ const c::sampleEditor::Data* m_data;
geBox* label;
geDial* dial;
#include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "core/graphics.h"
#include "core/clock.h"
-#include "glue/channel.h"
+#include "glue/events.h"
#include "utils/gui.h"
#include "utils/string.h"
#include "gui/dialogs/sampleEditor.h"
namespace giada {
namespace v
{
-gePitchTool::gePitchTool(ID channelId, int x, int y)
-: Fl_Pack (x, y, 600, G_GUI_UNIT),
- m_channelId(channelId)
+gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 600, G_GUI_UNIT)
+, m_data (nullptr)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
pitchHalf->callback(cb_setPitchHalf, (void*)this);
pitchDouble->callback(cb_setPitchDouble, (void*)this);
pitchReset->callback(cb_resetPitch, (void*)this);
+
+ rebuild(d);
}
/* -------------------------------------------------------------------------- */
-void gePitchTool::rebuild()
+void gePitchTool::rebuild(const c::sampleEditor::Data& d)
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- float p = static_cast<m::SampleChannel&>(c).getPitch();
-
- dial->value(p);
- input->value(u::string::fToString(p, 4).c_str()); // 4 digits
- });
+ m_data = &d;
+ update(m_data->pitch, /*isDial=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void gePitchTool::update(float v, bool isDial)
+{
+ input->value(u::string::fToString(v, 4).c_str()); // 4 digits
+ if (!isDial)
+ dial->value(v);
}
void gePitchTool::cb_setPitch()
{
- c::channel::setPitch(m_channelId, dial->value());
+ c::events::setChannelPitch(m_data->channelId, dial->value(), Thread::MAIN);
}
void gePitchTool::cb_setPitchNum()
{
- c::channel::setPitch(m_channelId, atof(input->value()));
+ c::events::setChannelPitch(m_data->channelId, atof(input->value()), Thread::MAIN);
}
void gePitchTool::cb_setPitchHalf()
{
- c::channel::setPitch(m_channelId, dial->value()/2);
+ c::events::setChannelPitch(m_data->channelId, dial->value() / 2, Thread::MAIN);
}
void gePitchTool::cb_setPitchDouble()
{
- c::channel::setPitch(m_channelId, dial->value()*2);
+ c::events::setChannelPitch(m_data->channelId, dial->value() * 2, Thread::MAIN);
}
void gePitchTool::cb_setPitchToBar()
{
- Frame end;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- end = static_cast<m::SampleChannel&>(c).getEnd();
- });
-
- c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInBar());
+ c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInBar(),
+ Thread::MAIN);
}
void gePitchTool::cb_setPitchToSong()
{
- Frame end;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- end = static_cast<m::SampleChannel&>(c).getEnd();
- });
-
- c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInLoop());
+ c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInLoop(),
+ Thread::MAIN);
}
void gePitchTool::cb_resetPitch()
{
- c::channel::setPitch(m_channelId, G_DEFAULT_PITCH);
+ c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN);
}
-
}} // giada::v::
{
public:
- gePitchTool(ID channelId, int x, int y);
+ gePitchTool(const c::sampleEditor::Data& d, int x, int y);
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
+ void update(float v, bool isDial=false);
private:
void cb_resetPitch();
void cb_setPitchNum();
- ID m_channelId;
+ const c::sampleEditor::Data* m_data;
geBox* label;
geDial* dial;
#include <cassert>
#include <FL/Fl.H>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/wave.h"
#include "glue/channel.h"
namespace giada {
namespace v
{
-geRangeTool::geRangeTool(ID channelId, ID waveId, int x, int y)
-: Fl_Pack (x, y, 280, G_GUI_UNIT),
- m_channelId(channelId),
- m_waveId (waveId)
+geRangeTool::geRangeTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 280, G_GUI_UNIT)
+, m_data (nullptr)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
m_end->callback(cb_setChanPos, this);
m_reset->callback(cb_resetStartEnd, this);
+
+ rebuild(d);
}
/* -------------------------------------------------------------------------- */
-void geRangeTool::rebuild()
+void geRangeTool::rebuild(const c::sampleEditor::Data& d)
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- m_begin->value(std::to_string(static_cast<m::SampleChannel&>(c).getBegin()).c_str());
- m_end->value(std::to_string(static_cast<m::SampleChannel&>(c).getEnd()).c_str());
- });
+ m_data = &d;
+ update(m_data->begin, m_data->end);
}
/* -------------------------------------------------------------------------- */
+void geRangeTool::update(Frame begin, Frame end)
+{
+ m_begin->value(std::to_string(begin).c_str());
+ m_end->value(std::to_string(end).c_str());
+}
+
+/* -------------------------------------------------------------------------- */
+
+
void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_setChanPos(); }
void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_resetStartEnd(); }
void geRangeTool::cb_setChanPos()
{
- c::sampleEditor::setBeginEnd(m_channelId, atoi(m_begin->value()), atoi(m_end->value()));
+ c::sampleEditor::setBeginEnd(m_data->channelId, atoi(m_begin->value()), atoi(m_end->value()));
}
void geRangeTool::cb_resetStartEnd()
{
- Frame waveSize;
- m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w)
- {
- waveSize = w.getSize();
- });
-
- c::sampleEditor::setBeginEnd(m_channelId, 0, waveSize - 1);
+ c::sampleEditor::setBeginEnd(m_data->channelId, 0, m_data->waveSize - 1);
}
}} // giada::v::
{
public:
- geRangeTool(ID channelId, ID waveId, int x, int y);
+ geRangeTool(const c::sampleEditor::Data& d, int x, int y);
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
+ void update(Frame begin, Frame end);
private:
void cb_setChanPos();
void cb_resetStartEnd();
- ID m_channelId;
- ID m_waveId;
+ const c::sampleEditor::Data* m_data;
geBox* m_label;
geInput* m_begin;
#include <cassert>
#include <cstdlib>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/const.h"
#include "utils/gui.h"
namespace giada {
namespace v
{
-geShiftTool::geShiftTool(ID channelId, ID waveId, int x, int y)
-: Fl_Pack (x, y, 300, G_GUI_UNIT),
- m_channelId(channelId),
- m_waveId (waveId)
+geShiftTool::geShiftTool(const c::sampleEditor::Data& d, int x, int y)
+: Fl_Pack(x, y, 300, G_GUI_UNIT)
+, m_data (nullptr)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
m_shift->callback(cb_setShift, (void*)this);
m_reset->callback(cb_reset, (void*)this);
+
+ rebuild(d);
}
/* -------------------------------------------------------------------------- */
-void geShiftTool::rebuild()
+void geShiftTool::rebuild(const c::sampleEditor::Data& d)
+{
+ m_data = &d;
+ update(m_data->shift);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void geShiftTool::update(Frame shift)
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- m_shift->value(std::to_string(static_cast<m::SampleChannel&>(c).shift).c_str());
- });
+ m_shift->value(std::to_string(shift).c_str());
}
void geShiftTool::shift(int f)
{
- c::sampleEditor::shift(m_channelId, m_waveId, f);
+ c::sampleEditor::shift(m_data->channelId, m_data->waveId, f);
}
}} // giada::v::
{
public:
- geShiftTool(ID channelId, ID waveId, int x, int y);
+ geShiftTool(const c::sampleEditor::Data& d, int x, int y);
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
+ void update(Frame shift);
private:
void shift(int f);
- ID m_channelId;
- ID m_waveId;
+ const c::sampleEditor::Data* m_data;
geBox* m_label;
geInput* m_shift;
#include <cmath>
#include <cstdlib>
#include <FL/Fl_Pack.H>
-#include "core/channels/sampleChannel.h"
#include "core/const.h"
-#include "core/model/model.h"
-#include "glue/channel.h"
+#include "glue/events.h"
#include "utils/gui.h"
#include "utils/math.h"
#include "utils/string.h"
namespace giada {
namespace v
{
-geVolumeTool::geVolumeTool(ID channelId, int X, int Y)
-: Fl_Pack (X, Y, 150, G_GUI_UNIT),
- m_channelId(channelId)
+geVolumeTool::geVolumeTool(const c::sampleEditor::Data& d, int X, int Y)
+: Fl_Pack(X, Y, 150, G_GUI_UNIT)
+, m_data (nullptr)
{
type(Fl_Pack::HORIZONTAL);
spacing(G_GUI_INNER_MARGIN);
dial->callback(cb_setVolume, (void*)this);
input->callback(cb_setVolumeNum, (void*)this);
+
+ rebuild(d);
}
/* -------------------------------------------------------------------------- */
-void geVolumeTool::rebuild()
+void geVolumeTool::rebuild(const c::sampleEditor::Data& d)
{
- float volume;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) { volume = c.volume; });
+ m_data = &d;
+ update(m_data->volume, /*isDial=*/false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void geVolumeTool::update(float v, bool isDial)
+{
std::string tmp = "-inf";
- float dB = u::math::linearToDB(volume);
+ float dB = u::math::linearToDB(v);
if (dB > -INFINITY)
tmp = u::string::fToString(dB, 2); // 2 digits
input->value(tmp.c_str());
- dial->value(volume);
+ if (!isDial)
+ dial->value(v);
}
void geVolumeTool::cb_setVolume()
{
- c::channel::setVolume(m_channelId, dial->value(), false, true);
+ c::events::setChannelVolume(m_data->channelId, dial->value(), Thread::MAIN);
}
void geVolumeTool::cb_setVolumeNum()
{
- float value = pow(10, (atof(input->value()) / 20)); // linear = 10^(dB/20)
- c::channel::setVolume(m_channelId, value, false, true);
+ c::events::setChannelVolume(m_data->channelId, u::math::dBtoLinear(atof(input->value())),
+ Thread::MAIN);
}
-
}} // giada::v::
{
public:
- geVolumeTool(ID channelId, int x, int y);
+ geVolumeTool(const c::sampleEditor::Data& d, int x, int y);
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
+ void update(float v, bool isDial=false);
private:
- ID m_channelId;
+ static void cb_setVolume(Fl_Widget* w, void* p);
+ static void cb_setVolumeNum(Fl_Widget* w, void* p);
+ void cb_setVolume();
+ void cb_setVolumeNum();
+
+ const c::sampleEditor::Data* m_data;
geBox* label;
geDial* dial;
geInput* input;
- static void cb_setVolume (Fl_Widget* w, void* p);
- static void cb_setVolumeNum(Fl_Widget* w, void* p);
- void cb_setVolume ();
- void cb_setVolumeNum();
};
}} // giada::v::
#include <cstdint>
#include <FL/Fl_Menu_Item.H>
#include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/waveFx.h"
#include "core/const.h"
{
const geWaveTools* wt = static_cast<geWaveTools*>(w);
- ID channelId = wt->channelId;
- size_t waveId = wt->waveId;
- Menu selectedItem = (Menu) (intptr_t) v;
+ ID channelId = wt->getChannelData().channelId;
+ ID waveId = wt->getChannelData().waveId;
+ Menu selectedItem = (Menu) (intptr_t) v;
int a = wt->waveform->getSelectionA();
int b = wt->waveform->getSelectionB();
c::sampleEditor::trim(channelId, waveId, a, b);
break;
case Menu::SILENCE:
- c::sampleEditor::silence(waveId, a, b);
+ c::sampleEditor::silence(channelId, waveId, a, b);
break;
case Menu::REVERSE:
- c::sampleEditor::reverse(waveId, a, b);
+ c::sampleEditor::reverse(channelId, waveId, a, b);
break;
case Menu::NORMALIZE:
- c::sampleEditor::normalizeHard(waveId, a, b);
+ c::sampleEditor::normalize(channelId, waveId, a, b);
break;
case Menu::FADE_IN:
- c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_IN);
+ c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::IN);
break;
case Menu::FADE_OUT:
- c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_OUT);
+ c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::OUT);
break;
case Menu::SMOOTH_EDGES:
- c::sampleEditor::smoothEdges(waveId, a, b);
+ c::sampleEditor::smoothEdges(channelId, waveId, a, b);
break;
case Menu::SET_BEGIN_END:
- c::sampleEditor::setBeginEnd(waveId, a, b);
+ c::sampleEditor::setBeginEnd(channelId, a, b);
break;
case Menu::TO_NEW_CHANNEL:
- c::sampleEditor::toNewChannel(waveId, a, b);
+ c::sampleEditor::toNewChannel(channelId, waveId, a, b);
break;
}
}
/* -------------------------------------------------------------------------- */
-geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h)
-: Fl_Scroll(x, y, w, h, nullptr),
- channelId(channelId),
- waveId(waveId)
+geWaveTools::geWaveTools(int x, int y, int w, int h)
+: Fl_Scroll(x, y, w, h, nullptr)
+, m_data (nullptr)
{
type(Fl_Scroll::HORIZONTAL_ALWAYS);
hscrollbar.color(G_COLOR_GREY_2);
hscrollbar.labelcolor(G_COLOR_LIGHT_1);
hscrollbar.slider(G_CUSTOM_BORDER_BOX);
- waveform = new v::geWaveform(channelId, waveId, x, y, w, h-24);
+ waveform = new v::geWaveform(x, y, w, h-24);
}
/* -------------------------------------------------------------------------- */
-void geWaveTools::rebuild()
+void geWaveTools::rebuild(const c::sampleEditor::Data& d)
{
- waveform->rebuild();
+ m_data = &d;
+ waveform->rebuild(d);
}
void geWaveTools::refresh()
{
- m::model::onGet(m::model::channels, channelId, [&](m::Channel& c)
- {
- if (c.isPreview())
- waveform->redraw();
- });
+ if (m_data->a_getPreviewStatus() == ChannelStatus::PLAY)
+ waveform->redraw();
}
if (this->w() == w || (this->w() != w && this->h() != h)) { // vertical or both resize
waveform->resize(x, y, waveform->w(), h-24);
- waveform->rebuild();
+ waveform->rebuild(*m_data);
}
if (this->w() > waveform->w())
{
public:
- geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h);
+ geWaveTools(int x, int y, int w, int h);
void resize(int x, int y, int w, int h) override;
int handle(int e) override;
Updates the waveform by realloc-ing new data (i.e. when the waveform has
changed). */
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
/* refresh
Redraws the waveform, called by the video thread. This is meant to be called
method is smart enough to skip painting if the channel is stopped. */
void refresh();
+
+ const c::sampleEditor::Data& getChannelData() const { return *m_data; }
v::geWaveform* waveform;
- ID channelId;
- ID waveId;
-
private:
void openMenu();
+
+ const c::sampleEditor::Data* m_data;
};
}} // giada::v::
#include <cmath>
#include <FL/fl_draw.H>
#include <FL/Fl_Menu_Button.H>
-#include "core/channels/sampleChannel.h"
#include "core/model/model.h"
#include "core/wave.h"
#include "core/conf.h"
namespace giada {
namespace v
{
-geWaveform::geWaveform(ID channelId, ID waveId, int x, int y, int w, int h)
-: Fl_Widget (x, y, w, h, nullptr),
- m_selection {},
- m_channelId (channelId),
- m_waveId (waveId),
- m_chanStart (0),
- m_chanStartLit(false),
- m_chanEnd (0),
- m_chanEndLit (false),
- m_pushed (false),
- m_dragged (false),
- m_resizedA (false),
- m_resizedB (false),
- m_ratio (0.0f)
+geWaveform::geWaveform(int x, int y, int w, int h)
+: Fl_Widget (x, y, w, h, nullptr)
+, m_selection {}
+, m_data (nullptr)
+, m_chanStart (0)
+, m_chanStartLit(false)
+, m_chanEnd (0)
+, m_chanEndLit (false)
+, m_pushed (false)
+, m_dragged (false)
+, m_resizedA (false)
+, m_resizedB (false)
+, m_ratio (0.0f)
{
- m_data.size = w;
+ m_waveform.size = w;
m_grid.snap = m::conf::conf.sampleEditorGridOn;
m_grid.level = m::conf::conf.sampleEditorGridVal;
void geWaveform::clearData()
{
- m_data.sup.clear();
- m_data.inf.clear();
- m_data.size = 0;
+ m_waveform.sup.clear();
+ m_waveform.inf.clear();
+ m_waveform.size = 0;
m_grid.points.clear();
}
int geWaveform::alloc(int datasize, bool force)
{
- m::model::WavesLock lock(m::model::waves);
+ /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */
- const m::Wave& wave = m::model::get(m::model::waves, m_waveId);
+ m::model::WavesLock l(m::model::waves);
+ const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId);
m_ratio = wave.getSize() / (float) datasize;
m_ratio = 1;
}
- if (datasize == m_data.size && !force)
+ if (datasize == m_waveform.size && !force)
return 0;
clearData();
- m_data.size = datasize;
- m_data.sup.resize(m_data.size);
- m_data.inf.resize(m_data.size);
+ m_waveform.size = datasize;
+ m_waveform.sup.resize(m_waveform.size);
+ m_waveform.inf.resize(m_waveform.size);
- u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio);
+ u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_waveform.size, m_ratio);
int offset = h() / 2;
int zero = y() + offset; // center, zero amplitude (-inf dB)
/* Resampling the waveform, hardcore way. Many thanks to
http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */
- for (int i = 0; i < m_data.size; i++) {
+ for (int i = 0; i < m_waveform.size; i++) {
/* Scan the original waveform in chunks [pc, pn]. */
m_grid.points.push_back(k);
}
- m_data.sup[i] = zero - (peaksup * offset);
- m_data.inf[i] = zero - (peakinf * offset);
+ m_waveform.sup[i] = zero - (peaksup * offset);
+ m_waveform.inf[i] = zero - (peakinf * offset);
// avoid window overflow
- if (m_data.sup[i] < y()) m_data.sup[i] = y();
- if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1;
+ if (m_waveform.sup[i] < y()) m_waveform.sup[i] = y();
+ if (m_waveform.inf[i] > y()+h()-1) m_waveform.inf[i] = y()+h()-1;
}
recalcPoints();
void geWaveform::recalcPoints()
{
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- m_chanStart = static_cast<m::SampleChannel&>(c).getBegin();
- m_chanEnd = static_cast<m::SampleChannel&>(c).getEnd();
- });
+ m_chanStart = m_data->begin;
+ m_chanEnd = m_data->end;
}
fl_color(G_COLOR_BLACK);
for (int i=from; i<to; i++) {
- if (i >= m_data.size)
+ if (i >= m_waveform.size)
break;
- fl_line(i+x(), zero, i+x(), m_data.sup[i]);
- fl_line(i+x(), zero, i+x(), m_data.inf[i]);
+ fl_line(i+x(), zero, i+x(), m_waveform.sup[i]);
+ fl_line(i+x(), zero, i+x(), m_waveform.inf[i]);
}
}
void geWaveform::drawPlayHead()
{
- float tp;
- m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c)
- {
- tp = static_cast<m::SampleChannel&>(c).trackerPreview;
- });
-
- int p = frameToPixel(tp) + x();
+ int p = frameToPixel(m_data->a_getPreviewTracker()) + x();
fl_color(G_COLOR_LIGHT_2);
fl_line(p, y() + 1, p, y() + h() - 2);
}
void geWaveform::draw()
{
- assert(m_data.sup.size() > 0);
- assert(m_data.inf.size() > 0);
+ assert(m_waveform.sup.size() > 0);
+ assert(m_waveform.inf.size() > 0);
fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas
int geWaveform::handle(int e)
{
- m::model::WavesLock lock(m::model::waves);
-
- const m::Wave& wave = m::model::get(m::model::waves, m_waveId);
+ /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */
+
+ m::model::WavesLock l(m::model::waves);
+ const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId);
m_mouseX = pixelToFrame(Fl::event_x() - x());
m_mouseY = pixelToFrame(Fl::event_y() - y());
static_cast<v::gdSampleEditor*>(window())->cb_togglePreview();
else
if (Fl::event_key() == FL_BackSpace)
- c::sampleEditor::rewindPreview(m_channelId);
+ c::sampleEditor::setPreviewTracker(m_data->begin);
return 1;
}
case FL_RELEASE: {
- c::sampleEditor::setPlayHead(m_channelId, m_mouseX);
+ c::sampleEditor::setPreviewTracker(m_mouseX);
/* If selection has been done (m_dragged or resized), make sure that point A
is always lower than B. */
/* Handle begin/end markers interaction. */
if (m_chanStartLit || m_chanEndLit)
- c::sampleEditor::setBeginEnd(m_channelId, m_chanStart, m_chanEnd);
+ c::sampleEditor::setBeginEnd(m_data->channelId, m_chanStart, m_chanEnd);
m_pushed = false;
m_dragged = false;
int geWaveform::pixelToFrame(int p) const
{
- Frame waveSize;
- m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); });
-
if (p <= 0)
return 0;
- if (p > m_data.size)
- return waveSize - 1;
+ if (p > m_waveform.size)
+ return m_data->waveSize - 1;
return p * m_ratio;
}
if (m_selection.a > m_selection.b) // inverted m_selection
std::swap(m_selection.a, m_selection.b);
- c::sampleEditor::setPlayHead(m_channelId, m_selection.a);
+ c::sampleEditor::setPreviewTracker(m_selection.a);
}
void geWaveform::setZoom(Zoom z)
{
- if (!alloc(z == Zoom::IN ? m_data.size * G_GUI_ZOOM_FACTOR : m_data.size / G_GUI_ZOOM_FACTOR))
+ if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR))
return;
- size(m_data.size, h());
+ size(m_waveform.size, h());
/* Zoom to cursor. */
/* -------------------------------------------------------------------------- */
-void geWaveform::rebuild()
+void geWaveform::rebuild(const c::sampleEditor::Data& d)
{
+ m_data = &d;
clearSelection();
- alloc(m_data.size, /*force=*/true);
+ alloc(m_waveform.size, /*force=*/true);
redraw();
}
{
m_grid.points.clear();
m_grid.level = l;
- alloc(m_data.size, true); // force alloc
+ alloc(m_waveform.size, true); // force alloc
redraw();
}
void geWaveform::setSnap(bool v) { m_grid.snap = v; }
bool geWaveform::getSnap() const { return m_grid.snap; }
-int geWaveform::getSize() const { return m_data.size; }
+int geWaveform::getSize() const { return m_waveform.size; }
/* -------------------------------------------------------------------------- */
void geWaveform::selectAll()
{
- Frame waveSize;
- m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); });
-
m_selection.a = 0;
- m_selection.b = waveSize - 1;
+ m_selection.b = m_data->waveSize - 1;
redraw();
}
}} // giada::v::
#endif
enum class Zoom { IN, OUT };
- geWaveform(ID channelId, ID waveId, int x, int y, int w, int h);
+ geWaveform(int x, int y, int w, int h);
void draw() override;
int handle(int e) override;
/* rebuild
Redraws the waveform. */
- void rebuild();
+ void rebuild(const c::sampleEditor::Data& d);
/* setGridLevel
Sets a new frequency level for the grid. 0 means disabled. */
/* setWaveId
Call this when the Wave ID has changed (e.g. after a reload). */
- void setWaveId(ID id) { m_waveId = id; };
+ void setWaveId(ID id) { /* TODO m_waveId = id;*/};
private:
std::vector<int> sup; // upper part of the waveform
std::vector<int> inf; // lower part of the waveform
int size; // width of the waveform to draw (in pixel)
- } m_data;
+ } m_waveform;
struct
{
int alloc(int datasize, bool force=false);
- ID m_channelId;
- ID m_waveId;
+ const c::sampleEditor::Data* m_data;
int m_chanStart;
bool m_chanStartLit;
{
if (m::model::waves.changed.load() == true ||
m::model::actions.changed.load() == true ||
- m::model::channels.changed.load() == true)
+ m::model::channels.changed.load() == true)
{
u::gui::rebuild();
m::model::waves.changed.store(false);
#elif defined(__linux__) || defined(__FreeBSD__)
#include <X11/xpm.h>
#endif
-#include "core/channels/channel.h"
#include "core/mixer.h"
#include "core/mixerHandler.h"
#include "core/clock.h"
G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars());
G_MainWin->mainTimer->setBpm(clock::getBpm());
- G_MainWin->mainTimer->setQuantizer(clock::getQuantize());
+ G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue());
}
{
return (x / (double) b) * z;
}
-
-
-/* -------------------------------------------------------------------------- */
-
-/* bound (1)
-Returns 'def' if 'x' is outside the range ('min', 'max'). */
-
-template <typename T>
-T bound(T x, T min, T max, T def)
-{
- return x < min || x > max ? def : x;
-}
-
-
-/* bound (2)
-Clamps 'x' in the range ('min', 'max'). */
-
-template <typename T>
-T bound(T x, T min, T max)
-{
- if (x < min) return min;
- if (x > max) return max;
- return x;
-}
}}} // giada::u::math::
std::string replace(std::string in, const std::string& search, const std::string& replace)
{
- size_t pos = 0;
+ std::size_t pos = 0;
while ((pos = in.find(search, pos)) != std::string::npos) {
in.replace(pos, search.length(), replace);
pos += replace.length();
into account). */
va_start(args, format);
- size_t size = vsnprintf(nullptr, 0, format, args) + 1;
+ std::size_t size = vsnprintf(nullptr, 0, format, args) + 1;
va_end(args);
/* Create a new temporary char array to hold the new expanded std::string. */
std::vector<std::string> out;
std::string full = in;
std::string token = "";
- size_t curr = 0;
- size_t next = -1;
+ std::size_t curr = 0;
+ std::size_t next = -1;
do {
curr = next + 1;
next = full.find_first_of(sep, curr);
namespace vector
{
template <typename T, typename P>
-size_t indexOf(T& v, const P& p)
+std::size_t indexOf(T& v, const P& p)
{
return std::distance(v.begin(), std::find(v.begin(), v.end(), p));
}
-template <typename T, typename P>
-size_t indexOfIf(T& v, P p)
-{
- return std::distance(v.begin(), std::find_if(v.begin(), v.end(), p));
-}
-
-
/* -------------------------------------------------------------------------- */
REQUIRE(buffer[1024][0] == 2048.0f);
}
- SECTION("test copy frame")
- {
- buffer.copyFrame(16, &data[32]);
- REQUIRE(buffer[16][0] == 32.0f);
- REQUIRE(buffer[16][1] == 33.0f);
- }
-
delete[] data;
}
}
+++ /dev/null
-#include "../src/core/channels/sampleChannel.h"
-#include "../src/core/model/model.h"
-#include <catch.hpp>
-
-
-TEST_CASE("sampleChannel")
-{
- using namespace giada;
- using namespace giada::m;
-
- const int BUFFER_SIZE = 1024;
- const int WAVE_SIZE = 50000;
-
- std::vector<ChannelMode> modes = { ChannelMode::LOOP_BASIC,
- ChannelMode::LOOP_ONCE, ChannelMode::LOOP_REPEAT,
- ChannelMode::LOOP_ONCE_BAR, ChannelMode::SINGLE_BASIC,
- ChannelMode::SINGLE_PRESS, ChannelMode::SINGLE_RETRIG,
- ChannelMode::SINGLE_ENDLESS };
-
- model::channels.clear();
- model::channels.push(std::make_unique<SampleChannel>(false, BUFFER_SIZE, 1, 1));
-
- model::onSwap(model::channels, 1, [&](Channel& c)
- {
- static_cast<SampleChannel&>(c).pushWave(1, WAVE_SIZE);
- });
-
- model::channels.lock();
- SampleChannel& ch = static_cast<SampleChannel&>(*model::channels.get(0));
- model::channels.unlock();
-
- SECTION("push wave")
- {
- REQUIRE(ch.playStatus == ChannelStatus::OFF);
- REQUIRE(ch.begin == 0);
- REQUIRE(ch.end == WAVE_SIZE);
- REQUIRE(ch.name == "");
- }
-
- SECTION("begin/end")
- {
- /* TODO - This section requires model::waves interaction. Let's wait for
- the non-virtual channel refactoring... */
- /*
- ch.setBegin(-100);
-
- REQUIRE(ch.getBegin() == 0);
- REQUIRE(ch.tracker == 0);
- REQUIRE(ch.trackerPreview == 0);
-
- ch.setBegin(100000);
-
- REQUIRE(ch.getBegin() == WAVE_SIZE);
- REQUIRE(ch.tracker == WAVE_SIZE);
- REQUIRE(ch.trackerPreview == WAVE_SIZE);
-
- ch.setBegin(16);
-
- REQUIRE(ch.getBegin() == 16);
- REQUIRE(ch.tracker == 16);
- REQUIRE(ch.trackerPreview == 16);
-
- ch.setEnd(0);
-
- REQUIRE(ch.getEnd() == 17);
-
- ch.setEnd(100000);
-
- REQUIRE(ch.getEnd() == WAVE_SIZE - 1);
-
- ch.setEnd(32);
-
- REQUIRE(ch.getEnd() == 32);
-
- ch.setBegin(64);
-
- REQUIRE(ch.getBegin() == 31);
- */
- }
-
- SECTION("pitch")
- {
- ch.setPitch(40.0f);
-
- REQUIRE(ch.getPitch() == G_MAX_PITCH);
-
- ch.setPitch(-2.0f);
-
- REQUIRE(ch.getPitch() == G_MIN_PITCH);
-
- ch.setPitch(0.8f);
-
- REQUIRE(ch.getPitch() == 0.8f);
- }
-
- SECTION("position")
- {
- REQUIRE(ch.getPosition() == -1); // Initially OFF
-
- ch.playStatus = ChannelStatus::PLAY;
- ch.tracker = 1000;
-
- REQUIRE(ch.getPosition() == 1000);
-
- ch.begin = 700;
-
- REQUIRE(ch.getPosition() == 300);
- }
-
- SECTION("empty")
- {
- ch.empty();
-
- REQUIRE(ch.playStatus == ChannelStatus::EMPTY);
- REQUIRE(ch.begin == 0);
- REQUIRE(ch.end == 0);
- REQUIRE(ch.tracker == 0);
- REQUIRE(ch.volume == G_DEFAULT_VOL);
- REQUIRE(ch.hasActions == false);
- REQUIRE(ch.hasWave == false);
- REQUIRE(ch.waveId == 0);
- }
-
- SECTION("can record audio")
- {
- REQUIRE(ch.canInputRec() == false); // Can't record if not armed
-
- ch.armed = true;
-
- REQUIRE(ch.canInputRec() == false); // Can't record with Wave in it
-
- ch.empty();
-
- REQUIRE(ch.canInputRec() == true);
- }
-
- /* TODO - fillBuffer, isAnyLoopMode, isAnySingleMode, isOnLastFrame */
-}
int a = 47;
int b = 500;
- wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_IN);
- wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_OUT);
+ wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::IN);
+ wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::OUT);
REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[0] == 0.0f);
REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[1] == 0.0f);
SECTION("test fade (mono)")
{
- wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_IN);
- wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_OUT);
+ wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::IN);
+ wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::OUT);
REQUIRE(getWave(WAVE_MONO_ID).getFrame(a)[0] == 0.0f);
REQUIRE(getWave(WAVE_MONO_ID).getFrame(b)[0] == 0.0f);