From: Dennis Braun Date: Mon, 24 Aug 2020 17:04:31 +0000 (+0200) Subject: New upstream version 0.16.3.1+ds1 X-Git-Tag: archive/raspbian/0.20.1+ds1-1+rpi1~1^2~79^2~1 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=f201bc8a29bb71349e92dedeb6b7d205b4f17898;p=giada.git New upstream version 0.16.3.1+ds1 --- diff --git a/ChangeLog b/ChangeLog index 655967f..76ea8d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,21 @@ -------------------------------------------------------------------------------- +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 diff --git a/Makefile.am b/Makefile.am index 9bd86b9..ee58416 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,7 +6,7 @@ AUTOMAKE_OPTIONS = foreign cppFlags = -I$(top_srcdir)/src -cxxFlags = -std=c++14 -Wall +cxxFlags = -std=c++17 -Wall ldAdd = ldFlags = sourcesExtra = @@ -14,6 +14,7 @@ sourcesMain = src/main.cpp sourcesCore = \ src/core/const.h \ src/core/queue.h \ + src/core/ringBuffer.h \ src/core/types.h \ src/core/range.h \ src/core/action.h \ @@ -25,6 +26,8 @@ sourcesCore = \ src/core/midiEvent.cpp \ src/core/audioBuffer.h \ src/core/audioBuffer.cpp \ + src/core/quantizer.h \ + src/core/quantizer.cpp \ src/core/conf.h \ src/core/conf.cpp \ src/core/kernelAudio.h \ @@ -35,6 +38,8 @@ sourcesCore = \ src/core/pluginManager.cpp \ src/core/mixerHandler.h \ src/core/mixerHandler.cpp \ + src/core/sequencer.h \ + src/core/sequencer.cpp \ src/core/init.h \ src/core/init.cpp \ src/core/plugin.h \ @@ -61,28 +66,42 @@ sourcesCore = \ src/core/waveManager.cpp \ src/core/recManager.h \ src/core/recManager.cpp \ + src/core/channels/state.h \ + src/core/channels/state.cpp \ + src/core/channels/sampleActionRecorder.h \ + src/core/channels/sampleActionRecorder.cpp \ + src/core/channels/midiActionRecorder.h \ + src/core/channels/midiActionRecorder.cpp \ + src/core/channels/waveReader.h \ + src/core/channels/waveReader.cpp \ + src/core/channels/midiController.h \ + src/core/channels/midiController.cpp \ + src/core/channels/sampleController.h \ + src/core/channels/sampleController.cpp \ + src/core/channels/samplePlayer.h \ + src/core/channels/samplePlayer.cpp \ + src/core/channels/audioReceiver.h \ + src/core/channels/audioReceiver.cpp \ + src/core/channels/midiLighter.h \ + src/core/channels/midiLighter.cpp \ + src/core/channels/midiLearner.h \ + src/core/channels/midiLearner.cpp \ + src/core/channels/midiSender.h \ + src/core/channels/midiSender.cpp \ + src/core/channels/midiReceiver.h \ + src/core/channels/midiReceiver.cpp \ src/core/channels/channel.h \ src/core/channels/channel.cpp \ - src/core/channels/midiChannel.h \ - src/core/channels/midiChannel.cpp \ - src/core/channels/masterChannel.h \ - src/core/channels/masterChannel.cpp \ - src/core/channels/sampleChannel.h \ - src/core/channels/sampleChannel.cpp \ src/core/channels/channelManager.h \ src/core/channels/channelManager.cpp \ - src/core/channels/sampleChannelProc.h \ - src/core/channels/sampleChannelProc.cpp \ - src/core/channels/sampleChannelRec.h \ - src/core/channels/sampleChannelRec.cpp \ - src/core/channels/midiChannelProc.h \ - src/core/channels/midiChannelProc.cpp \ src/core/model/model.h \ src/core/model/model.cpp \ src/core/model/storage.h \ src/core/model/storage.cpp \ src/core/idManager.h \ src/core/idManager.cpp \ + src/glue/events.h \ + src/glue/events.cpp \ src/glue/main.h \ src/glue/main.cpp \ src/glue/io.h \ @@ -161,6 +180,10 @@ sourcesCore = \ src/gui/dialogs/midiIO/midiInputChannel.cpp \ src/gui/dialogs/midiIO/midiInputMaster.h \ src/gui/dialogs/midiIO/midiInputMaster.cpp \ + src/gui/elems/midiIO/midiLearner.h \ + src/gui/elems/midiIO/midiLearner.cpp \ + src/gui/elems/midiIO/midiLearnerPack.h \ + src/gui/elems/midiIO/midiLearnerPack.cpp \ src/gui/elems/browser.h \ src/gui/elems/browser.cpp \ src/gui/elems/soundMeter.h \ @@ -199,7 +222,7 @@ sourcesCore = \ src/gui/elems/actionEditor/envelopePoint.cpp \ src/gui/elems/actionEditor/pianoRoll.h \ src/gui/elems/actionEditor/pianoRoll.cpp \ - src/gui/elems/actionEditor/noteEditor.h \ + src/gui/elems/actionEditor/noteEditor.h \ src/gui/elems/actionEditor/noteEditor.cpp \ src/gui/elems/actionEditor/pianoItem.h \ src/gui/elems/actionEditor/pianoItem.cpp \ @@ -249,16 +272,14 @@ sourcesCore = \ src/gui/elems/config/tabBehaviors.cpp \ src/gui/elems/config/tabPlugins.h \ src/gui/elems/config/tabPlugins.cpp \ - src/gui/elems/midiIO/midiLearnerBase.h \ - src/gui/elems/midiIO/midiLearnerBase.cpp \ - src/gui/elems/midiIO/midiLearnerMaster.h \ - src/gui/elems/midiIO/midiLearnerMaster.cpp \ - src/gui/elems/midiIO/midiLearnerChannel.h \ - src/gui/elems/midiIO/midiLearnerChannel.cpp \ - src/gui/elems/midiIO/midiLearnerPlugin.h \ - src/gui/elems/midiIO/midiLearnerPlugin.cpp \ src/gui/elems/basics/scroll.h \ src/gui/elems/basics/scroll.cpp \ + src/gui/elems/basics/pack.h \ + src/gui/elems/basics/pack.cpp \ + src/gui/elems/basics/group.h \ + src/gui/elems/basics/group.cpp \ + src/gui/elems/basics/scrollPack.h \ + src/gui/elems/basics/scrollPack.cpp \ src/gui/elems/basics/boxtypes.h \ src/gui/elems/basics/boxtypes.cpp \ src/gui/elems/basics/baseButton.h \ @@ -303,7 +324,7 @@ sourcesCore = \ src/utils/ver.cpp \ src/utils/string.h \ src/utils/string.cpp \ - src/deps/rtaudio/RtAudio.h \ + src/deps/rtaudio/RtAudio.h \ src/deps/rtaudio/RtAudio.cpp sourcesTests = \ tests/main.cpp \ @@ -313,9 +334,7 @@ sourcesTests = \ tests/utils.cpp \ tests/recorder.cpp \ tests/waveFx.cpp \ - tests/audioBuffer.cpp \ - tests/sampleChannel.cpp - + tests/audioBuffer.cpp if WITH_VST sourcesExtra += \ @@ -423,7 +442,7 @@ endif bin_PROGRAMS = giada -giada_SOURCES = $(sourcesCore) $(sourcesMain) $(sourcesExtra) +giada_SOURCES = $(sourcesCore) $(sourcesExtra) $(sourcesMain) giada_CPPFLAGS = $(cppFlags) giada_CXXFLAGS = $(cxxFlags) giada_LDADD = $(ldAdd) diff --git a/README.md b/README.md index 10a1809..5982ae8 100644 --- a/README.md +++ b/README.md @@ -8,31 +8,35 @@ ## What is Giada? -Giada is a free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. How does it work? Just pick up your channel, fill it with samples or MIDI events and start the show by using this tiny piece of software as a loop machine, drum machine, sequencer, live sampler or yet as a plugin/effect host. Giada aims to be a compact and portable virtual device for Linux, Mac OS X and Windows for production use and live sets. +Giada is an open source, minimalistic and hardcore music production tool. Designed for DJs, live performers and electronic musicians.

✦✦✦ See Giada in action! ✦✦✦

-![Giada Loop Machine screenshot](http://giadamusic.com/public/img/screenshots/giada-loop-machine-screenshot-14-carousel.jpg) +![Giada Loop Machine screenshot](https://giadamusic.com/images/giada-canvas.png) ## Main features +* Your sample player! Load samples from your crates and play them with a computer keyboard or a MIDI controller; +* Your loop machine! Build your performance in real time by layering audio tracks or MIDI events, driven by the main sequencer; +* Your song editor! Write songs from scratch or edit existing live recordings with the powerful Action Editor, for a fine-tuned control; +* Your live recorder! Record sounds from the real world and MIDI events coming from external devices or other apps; +* Your FX processor! Process samples or audio/MIDI input signals with VST instruments from your plug-ins collection; +* Your MIDI controller! Control other software or synchronize physical MIDI devices by using Giada as a MIDI master sequencer. + +### And more: + * Ultra-lightweight internal design; * multi-thread/multi-core support; * 32-bit floating point audio engine; * ALSA, JACK + Transport, CoreAudio, ASIO and DirectSound full support; -* high quality internal resampler; -* unlimited number of channels (controllable via computer keyboard); -* several playback modes and combinations; +* unlimited number of channels (optionally controllable via computer keyboard); * BPM and beat sync with sample-accurate loop engine; -* VST and VSTi (instrument) plug-in support; -* MIDI input and output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps); -* super-sleek, built-in wave editor; -* live sampler from external inputs; -* live action recorder with automatic quantizer; -* piano Roll editor; -* portable patch storage system, based on super-hackable JSON files; +* MIDI output support, featuring custom [MIDI lightning messages](https://github.com/monocasual/giada-midimaps); +* super-sleek, built-in Wave Editor for audio samples and Piano Roll editor for MIDI messages; +* automatic quantizer; +* portable project storage system, based on super-hackable JSON files; * support for all major uncompressed file formats; * test-driven development style supported by [Travis CI](https://travis-ci.org/monocasual/giada) and [Catch](https://github.com/philsquared/Catch) * under a constant stage of development; diff --git a/configure.ac b/configure.ac index d7121b3..f9c7337 100644 --- a/configure.ac +++ b/configure.ac @@ -58,7 +58,7 @@ AM_CONDITIONAL(FREEBSD, test "x$os" = "xfreebsd") AC_ARG_ENABLE( [vst], AS_HELP_STRING([--enable-vst], [enable vst support]), - [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)], + [AC_DEFINE(WITH_VST) AM_CONDITIONAL(WITH_VST, true)], [AM_CONDITIONAL(WITH_VST, false)] ) @@ -70,7 +70,7 @@ AC_ARG_ENABLE( AC_ARG_ENABLE( [system-catch], AS_HELP_STRING([--enable-system-catch], [use system-provided Catch library]), - [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)], + [AC_DEFINE(WITH_SYSTEM_CATCH) AM_CONDITIONAL(WITH_SYSTEM_CATCH, true)], [AM_CONDITIONAL(WITH_SYSTEM_CATCH, false)] ) @@ -81,7 +81,7 @@ AC_ARG_ENABLE( AC_ARG_ENABLE( [debug], AS_HELP_STRING([--enable-debug], [enable debug mode (asserts, ...)]), - [], + [], [AC_DEFINE(NDEBUG)] ) diff --git a/create_source_tarball.sh b/create_source_tarball.sh new file mode 100755 index 0000000..7be10d0 --- /dev/null +++ b/create_source_tarball.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# +# Creates source tarballs for giada in the form of +# 'giada-x.x.x-src.tar.gz' and optionally detached PGP signatures +# for the created file of the form 'giada-x.x.x-src.tar.gz.asc'. +# If the environment variable BUILD_DIR is provided, the files will be moved to +# $BUILD_DIR/, else to the location of this script (the repository folder). +# +# Requirements: +# - git +# - tar +# - a writable (user) /tmp folder for mktemp +# - gnupg >= 2.0.0 (if source tarball signing is requested) +# - a valid PGP signing key in the keyring (if source tarball signing is +# requested) + +set -euo pipefail + +get_absolute_path() { + cd "$(dirname "$1")" && pwd -P +} + +validate_project_tag() { + if ! git ls-remote -t "${upstream}"| grep -e "${version}$" > /dev/null; then + echo "The tag '$version' could not be found in upstream repository (${upstream})." + exit 1 + fi +} + +checkout_project() { + echo "Cloning project below working directory ${working_dir}" + cd "$working_dir" + git clone "$upstream" --branch "$version" \ + --single-branch \ + --depth=1 \ + --recurse-submodules \ + "${output_name}" +} + +clean_sources() { + cd "${working_dir}/${output_name}" + echo "Removing unneeded files and folders..." + rm -rfv .git* \ + .travis* \ + create_source_tarball.sh +} + +compress_sources() { + cd "${working_dir}" + tar cvfz "${output_name}.tar.gz" "${output_name}" +} + +move_sources() { + cd "${working_dir}" + mv -v "${output_name}.tar.gz" "${output_dir}/" +} + +sign_sources() { + cd "${output_dir}" + gpg --detach-sign \ + -u "${signer}" \ + -o "${output_name}.tar.gz.asc" \ + "${output_name}.tar.gz" +} + +cleanup_working_dir() { + echo "Removing working directory: ${working_dir}" + rm -rf "${working_dir}" +} + +print_help() { + echo "Usage: $0 -v -s " + 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: diff --git a/src/core/audioBuffer.cpp b/src/core/audioBuffer.cpp index ddb9024..01b7f8d 100644 --- a/src/core/audioBuffer.cpp +++ b/src/core/audioBuffer.cpp @@ -1,6 +1,32 @@ -#include +/* ----------------------------------------------------------------------------- + * + * 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 + * . + * + * -------------------------------------------------------------------------- */ + + #include -#include +#include #include "audioBuffer.h" @@ -8,13 +34,32 @@ namespace giada { namespace m { AudioBuffer::AudioBuffer() - : m_data (nullptr), - m_size (0), - m_channels(0) +: m_data (nullptr) +, m_size (0) +, m_channels(0) { } +AudioBuffer::AudioBuffer(Frame size, int channels) +: AudioBuffer() +{ + alloc(size, channels); +} + + +/* -------------------------------------------------------------------------- */ + + +AudioBuffer::AudioBuffer(const AudioBuffer& o) +: m_data (new float[o.m_size * o.m_channels]) +, m_size (o.m_size) +, m_channels(o.m_channels) +{ + std::copy(o.m_data, o.m_data + (o.m_size * o.m_channels), m_data); +} + + /* -------------------------------------------------------------------------- */ @@ -27,7 +72,7 @@ AudioBuffer::~AudioBuffer() /* -------------------------------------------------------------------------- */ -float* AudioBuffer::operator [](int offset) const +float* AudioBuffer::operator [](Frame offset) const { assert(m_data != nullptr); assert(offset < m_size); @@ -38,35 +83,47 @@ float* AudioBuffer::operator [](int offset) const /* -------------------------------------------------------------------------- */ -void AudioBuffer::clear(int a, int b) +void AudioBuffer::clear(Frame a, Frame b) { if (m_data == nullptr) return; if (b == -1) b = m_size; - memset(m_data + (a * m_channels), 0, (b - a) * m_channels * sizeof(float)); + std::fill_n(m_data + (a * m_channels), (b - a) * m_channels, 0.0); } /* -------------------------------------------------------------------------- */ -int AudioBuffer::countFrames() const { return m_size; } -int AudioBuffer::countSamples() const { return m_size * m_channels; } -int AudioBuffer::countChannels() const { return m_channels; } -bool AudioBuffer::isAllocd() const { return m_data != nullptr; } +Frame AudioBuffer::countFrames() const { return m_size; } +int AudioBuffer::countSamples() const { return m_size * m_channels; } +int AudioBuffer::countChannels() const { return m_channels; } +bool AudioBuffer::isAllocd() const { return m_data != nullptr; } + + + +/* -------------------------------------------------------------------------- */ + +float AudioBuffer::getPeak() const +{ + float peak = 0.0f; + for (int i = 0; i < countSamples(); i++) + peak = std::max(peak, m_data[i]); + return peak; +} /* -------------------------------------------------------------------------- */ -void AudioBuffer::alloc(int size, int channels) +void AudioBuffer::alloc(Frame size, int channels) { free(); m_size = size; m_channels = channels; m_data = new float[m_size * m_channels]; - clear(); // does nothing if m_data == nullptr + clear(); } @@ -75,7 +132,7 @@ void AudioBuffer::alloc(int size, int channels) void AudioBuffer::free() { - delete[] m_data; // No check required, delete nullptr does nothing + delete[] m_data; setData(nullptr, 0, 0); } @@ -83,7 +140,7 @@ void AudioBuffer::free() /* -------------------------------------------------------------------------- */ -void AudioBuffer::setData(float* data, int size, int channels) +void AudioBuffer::setData(float* data, Frame size, int channels) { m_data = data; m_size = size; @@ -107,20 +164,43 @@ void AudioBuffer::moveData(AudioBuffer& b) /* -------------------------------------------------------------------------- */ -void AudioBuffer::copyFrame(int frame, float* values) +void AudioBuffer::copyData(const float* data, Frame frames, int offset) { assert(m_data != nullptr); - memcpy(m_data + (frame * m_channels), values, m_channels * sizeof(float)); + assert(frames <= m_size - offset); + std::copy_n(data, frames * m_channels, m_data + (offset * m_channels)); +} + + +void AudioBuffer::copyData(const AudioBuffer& b, float gain) +{ + copyData(b[0], b.countFrames()); + if (gain != 1.0f) + applyGain(gain); } /* -------------------------------------------------------------------------- */ -void AudioBuffer::copyData(const float* data, int frames, int offset) + +void AudioBuffer::addData(const AudioBuffer& b, float gain, Pan pan) { assert(m_data != nullptr); - assert(frames <= m_size - offset); - memcpy(m_data + (offset * m_channels), data, frames * m_channels * sizeof(float)); + assert(countFrames() <= b.countFrames()); + assert(countChannels() == pan.size()); + + for (int i = 0; i < countFrames(); i++) + for (int j = 0; j < countChannels(); j++) + (*this)[i][j] += b[i][j] * gain * pan[j]; } + +/* -------------------------------------------------------------------------- */ + + +void AudioBuffer::applyGain(float g) +{ + for (int i = 0; i < countSamples(); i++) + m_data[i] *= g; +} }} // giada::m:: \ No newline at end of file diff --git a/src/core/audioBuffer.h b/src/core/audioBuffer.h index ab6fcd3..6c0128b 100644 --- a/src/core/audioBuffer.h +++ b/src/core/audioBuffer.h @@ -1,16 +1,59 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + #ifndef G_AUDIO_BUFFER_H #define G_AUDIO_BUFFER_H +#include +#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; + + /* AudioBuffer (1) + Creates an empty (and invalid) audio buffer. */ + AudioBuffer(); + + /* AudioBuffer (2) + Creates an audio buffer and allocates memory for size * channels frames. */ + + AudioBuffer(Frame size, int channels); + + AudioBuffer(const AudioBuffer& o); ~AudioBuffer(); /* operator [] @@ -26,32 +69,34 @@ public: float* operator [](int offset) const; - int countFrames() const; + Frame countFrames() const; int countSamples() const; int countChannels() const; bool isAllocd() const; - void alloc(int size, int channels); + /* getPeak + Returns the highest value from any channel. */ + + float getPeak() const; + + void alloc(Frame size, int channels); void free(); /* copyData Copies 'frames' frames from the new 'data' into m_data, and fills m_data - starting from frame 'offset'. It takes for granted that the new data contains - the same number of channels than m_channels. */ - - void copyData(const float* data, int frames, int offset=0); + starting from frame 'offset'. The new data MUST contain the same number of + channels than m_channels. */ - /* copyFrame - Copies data pointed by 'values' into m_data[frame]. It takes for granted that - 'values' contains the same number of channels than m_channels. */ + void copyData(const float* data, Frame frames, int offset=0); - void copyFrame(int frame, float* values); + void copyData(const AudioBuffer& b, float gain=1.0f); + void addData(const AudioBuffer& b, float gain=1.0f, Pan pan={1.0f, 1.0f}); /* setData Borrow 'data' as new m_data. Makes sure not to delete the data 'data' points to while using it. Set it back to nullptr when done. */ - void setData(float* data, int size, int channels); + void setData(float* data, Frame size, int channels); /* moveData Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer. */ @@ -62,12 +107,14 @@ public: Clears the internal data by setting all bytes to 0.0f. Optional parameters 'a' and 'b' set the range. */ - void clear(int a=0, int b=-1); + void clear(Frame a=0, Frame b=-1); + + void applyGain(float g); private: float* m_data; - int m_size; // in frames + Frame m_size; int m_channels; }; diff --git a/src/core/channels/audioReceiver.cpp b/src/core/channels/audioReceiver.cpp new file mode 100644 index 0000000..7d8a118 --- /dev/null +++ b/src/core/channels/audioReceiver.cpp @@ -0,0 +1,78 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "core/channels/state.h" +#include "audioReceiver.h" + + +namespace giada { +namespace m +{ +AudioReceiver::AudioReceiver(ChannelState* c) +: state (std::make_unique()) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +AudioReceiver::AudioReceiver(const patch::Channel& p, ChannelState* c) +: state (std::make_unique(p)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +AudioReceiver::AudioReceiver(const AudioReceiver& o, ChannelState* c) +: state (std::make_unique(*o.state)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void AudioReceiver::render(const AudioBuffer& in) const +{ + /* If armed and input monitor is on, copy input buffer to channel buffer: + this enables the input monitoring. The channel buffer will be overwritten + later on by pluginHost::processStack, so that you would record "clean" audio + (i.e. not plugin-processed). */ + + bool armed = m_channelState->armed.load(); + bool inputMonitor = state->inputMonitor.load(); + + if (armed && inputMonitor) + m_channelState->buffer.addData(in); // add, don't overwrite +} +}} // giada::m:: diff --git a/src/core/channels/audioReceiver.h b/src/core/channels/audioReceiver.h new file mode 100644 index 0000000..a285f92 --- /dev/null +++ b/src/core/channels/audioReceiver.h @@ -0,0 +1,71 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_AUDIO_RECEIVER_H +#define G_CHANNEL_AUDIO_RECEIVER_H + + +#include + + +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 state; + +private: + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/channel.cpp b/src/core/channels/channel.cpp index 2c98f8b..16b625b 100644 --- a/src/core/channels/channel.cpp +++ b/src/core/channels/channel.cpp @@ -26,68 +26,45 @@ #include -#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(id, bufferSize)) +, midiLighter (state.get()) +, m_type (type) +, m_columnId (columnId) { - buffer.alloc(bufferSize, G_MAX_IO_CHANS); + switch (m_type) { + case ChannelType::SAMPLE: + samplePlayer.emplace(state.get()); + audioReceiver.emplace(state.get()); + sampleActionRecorder.emplace(state.get(), samplePlayer->state.get()); + break; + + case ChannelType::PREVIEW: + samplePlayer.emplace(state.get()); + break; + + case ChannelType::MIDI: + midiController.emplace(state.get()); #ifdef WITH_VST - - midiBuffer.ensureSize(bufferSize); - + midiReceiver.emplace(state.get()); #endif + midiSender.emplace(state.get()); + midiActionRecorder.emplace(state.get()); + break; + + default: break; + } } @@ -95,152 +72,160 @@ Channel::Channel(ChannelType type, ChannelStatus playStatus, int bufferSize, Channel::Channel(const Channel& o) -: type (o.type), - playStatus (o.playStatus), - recStatus (o.recStatus), - columnId (o.columnId), - id (o.id), - height (o.height), - previewMode (o.previewMode), - pan (o.pan), - volume (o.volume), - armed (o.armed), - name (o.name), - key (o.key), - mute (o.mute), - solo (o.solo), - volume_i (o.volume_i), - volume_d (o.volume_d), - hasActions (o.hasActions), - readActions (o.readActions), - midiIn (o.midiIn), - midiInKeyPress (o.midiInKeyPress), - midiInKeyRel (o.midiInKeyRel), - midiInKill (o.midiInKill), - midiInArm (o.midiInArm), - midiInVolume (o.midiInVolume), - midiInMute (o.midiInMute), - midiInSolo (o.midiInSolo), - midiInFilter (o.midiInFilter), - midiOutL (o.midiOutL), - midiOutLplaying(o.midiOutLplaying), - midiOutLmute (o.midiOutLmute), - midiOutLsolo (o.midiOutLsolo) +: id (o.id) #ifdef WITH_VST - ,pluginIds (o.pluginIds) +, pluginIds (o.pluginIds) #endif +, state (std::make_unique(*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(p, bufferSize)) +, midiLearner (p) +, midiLighter (p, state.get()) +, m_type (p.type) +, m_columnId (p.columnId) { - buffer.alloc(bufferSize, G_MAX_IO_CHANS); + switch (m_type) { + + case ChannelType::SAMPLE: + samplePlayer.emplace(p, state.get()); + audioReceiver.emplace(p, state.get()); + sampleActionRecorder.emplace(state.get(), samplePlayer->state.get()); + break; + + case ChannelType::PREVIEW: + samplePlayer.emplace(p, state.get()); + break; + + case ChannelType::MIDI: + midiController.emplace(state.get()); +#ifdef WITH_VST + midiReceiver.emplace(p, state.get()); +#endif + midiSender.emplace(state.get()); + midiActionRecorder.emplace(state.get()); + break; + + default: break; + } } /* -------------------------------------------------------------------------- */ -bool Channel::isPlaying() const +void Channel::parse(const mixer::EventBuffer& events, bool audible) const { - return playStatus == ChannelStatus::PLAY || - playStatus == ChannelStatus::ENDING; + for (const mixer::Event& e : events) { + + if (e.action.channelId > 0 && e.action.channelId != id) + continue; + + parse(e); + midiLighter.parse(e, audible); + + if (midiController) midiController->parse(e); +#ifdef WITH_VST + if (midiReceiver) midiReceiver->parse(e); +#endif + if (midiSender) midiSender->parse(e); + if (samplePlayer) samplePlayer->parse(e); + if (midiActionRecorder) midiActionRecorder->parse(e); + if (sampleActionRecorder && samplePlayer && samplePlayer->hasWave()) + sampleActionRecorder->parse(e); + } } /* -------------------------------------------------------------------------- */ -void Channel::sendMidiLmute() +void Channel::advance(Frame bufferSize) const { - if (!midiOutL || midiOutLmute == 0x0) - return; - if (mute) - kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOn); - else - kernelMidi::sendMidiLightning(midiOutLmute, midimap::midimap.muteOff); + /* TODO - this is used only to advance samplePlayer for its quantizer. Use + this to render actions in the future. */ + + if (samplePlayer) samplePlayer->advance(bufferSize); } /* -------------------------------------------------------------------------- */ -void Channel::sendMidiLsolo() +void Channel::render(AudioBuffer* out, AudioBuffer* in, bool audible) const { - if (!midiOutL || midiOutLsolo == 0x0) - return; - if (solo) - kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOn); + if (id == mixer::MASTER_OUT_CHANNEL_ID) + renderMasterOut(*out); else - kernelMidi::sendMidiLightning(midiOutLsolo, midimap::midimap.soloOff); + if (id == mixer::MASTER_IN_CHANNEL_ID) + renderMasterIn(*in); + else + renderChannel(*out, *in, audible); } /* -------------------------------------------------------------------------- */ -void Channel::sendMidiLstatus() +void Channel::parse(const mixer::Event& e) const { - if (!midiOutL || midiOutLplaying == 0x0) - return; - switch (playStatus) { - case ChannelStatus::OFF: - kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopped); - break; - case ChannelStatus::WAIT: - kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.waiting); - break; - case ChannelStatus::ENDING: - kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.stopping); - break; - case ChannelStatus::PLAY: - if ((mixer::isChannelAudible(this) && !mute) || - !midimap::isDefined(midimap::midimap.playingInaudible)) - kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playing); - else - kernelMidi::sendMidiLightning(midiOutLplaying, midimap::midimap.playingInaudible); - break; - default: + switch (e.type) { + + case mixer::EventType::CHANNEL_VOLUME: + state->volume.store(e.action.event.getVelocityFloat()); break; + + case mixer::EventType::CHANNEL_PAN: + state->pan.store(e.action.event.getVelocityFloat()); break; + + case mixer::EventType::CHANNEL_MUTE: + state->mute.store(!state->mute.load()); break; + + case mixer::EventType::CHANNEL_TOGGLE_ARM: + state->armed.store(!state->armed.load()); break; + + case mixer::EventType::CHANNEL_SOLO: + state->solo.store(!state->solo.load()); + m::mh::updateSoloCount(); break; + + default: break; } } @@ -248,82 +233,98 @@ void Channel::sendMidiLstatus() /* -------------------------------------------------------------------------- */ -bool Channel::isMidiInAllowed(int c) const +void Channel::renderMasterOut(AudioBuffer& out) const { - return midiInFilter == -1 || midiInFilter == c; + state->buffer.copyData(out); +#ifdef WITH_VST + if (pluginIds.size() > 0) + pluginHost::processStack(state->buffer, pluginIds, nullptr); +#endif + out.copyData(state->buffer, state->volume.load()); } /* -------------------------------------------------------------------------- */ -void Channel::setPan(float v) +void Channel::renderMasterIn(AudioBuffer& in) const { - if (v > 1.0f) v = 1.0f; - else - if (v < 0.0f) v = 0.0f; - pan = v; +#ifdef WITH_VST + if (pluginIds.size() > 0) + pluginHost::processStack(in, pluginIds, nullptr); +#endif } -float Channel::getPan() const +/* -------------------------------------------------------------------------- */ + + +void Channel::renderChannel(AudioBuffer& out, AudioBuffer& in, bool audible) const { - return pan; -} + state->buffer.clear(); + if (samplePlayer) samplePlayer->render(out); + if (audioReceiver) audioReceiver->render(in); -/* -------------------------------------------------------------------------- */ + /* If MidiReceiver exists, let it process the plug-in stack, as it can + contain plug-ins that take MIDI events (i.e. synths). Otherwise process the + plug-in stack internally with no MIDI events. */ +#ifdef WITH_VST + if (midiReceiver) + midiReceiver->render(pluginIds); + else + if (pluginIds.size() > 0) + pluginHost::processStack(state->buffer, pluginIds, nullptr); +#endif -float Channel::calcPanning(int ch) const -{ - float p = pan; - if (p == 0.5f) // center: nothing to do - return 1.0; - if (ch == 0) - return 1.0 - p; - else // channel 1 - return p; + if (audible) + out.addData(state->buffer, state->volume.load() * state->volume_i, calcPanning()); } /* -------------------------------------------------------------------------- */ -void Channel::calcVolumeEnvelope() +AudioBuffer::Pan Channel::calcPanning() const { - volume_i = volume_i + volume_d; - if (volume_i < 0.0f) - volume_i = 0.0f; - else - if (volume_i > 1.0f) - volume_i = 1.0f; -} + /* TODO - precompute the AudioBuffer::Pan when pan value changes instead of + building it on the fly. */ + + float pan = state->pan.load(); + /* Center pan (0.5f)? Pass-through. */ -bool Channel::isPreview() const -{ - return previewMode != PreviewMode::NONE; + if (pan == 0.5f) return { 1.0f, 1.0f }; + return { 1.0f - pan, pan }; } /* -------------------------------------------------------------------------- */ +ID Channel::getColumnId() const { return m_columnId; }; +ChannelType Channel::getType() const { return m_type; }; + + +/* -------------------------------------------------------------------------- */ + + bool Channel::isInternal() const { - return id == mixer::MASTER_OUT_CHANNEL_ID || - id == mixer::MASTER_IN_CHANNEL_ID || - id == mixer::PREVIEW_CHANNEL_ID; + return m_type == ChannelType::MASTER || m_type == ChannelType::PREVIEW; } -/* -------------------------------------------------------------------------- */ +bool Channel::isMuted() const +{ + /* Internals can't be muted. */ + return !isInternal() && state->mute.load() == true; +} -bool Channel::isReadingActions() const +bool Channel::canInputRec() const { - return hasActions && readActions; + return samplePlayer && !samplePlayer->hasWave() && state->armed.load() == true; } - }} // giada::m:: diff --git a/src/core/channels/channel.h b/src/core/channels/channel.h index a6d4e5f..8450011 100644 --- a/src/core/channels/channel.h +++ b/src/core/channels/channel.h @@ -29,247 +29,96 @@ #define G_CHANNEL_H -#include -#include -#include "core/types.h" -#include "core/patch.h" +#include +#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 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 state; - bool midiOutL; - uint32_t midiOutLplaying; - uint32_t midiOutLmute; - uint32_t midiOutLsolo; + MidiLearner midiLearner; + MidiLighter midiLighter; + std::optional samplePlayer; + std::optional audioReceiver; + std::optional midiController; #ifdef WITH_VST + std::optional midiReceiver; +#endif + std::optional midiSender; + std::optional sampleActionRecorder; + std::optional midiActionRecorder; - std::vector 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 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:: diff --git a/src/core/channels/channelManager.cpp b/src/core/channels/channelManager.cpp index 8bc2b43..260fd5e 100644 --- a/src/core/channels/channelManager.cpp +++ b/src/core/channels/channelManager.cpp @@ -27,11 +27,10 @@ #include #include "utils/fs.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" -#include "core/channels/masterChannel.h" #include "core/channels/channel.h" +#include "core/channels/samplePlayer.h" #include "core/const.h" +#include "core/kernelAudio.h" #include "core/patch.h" #include "core/mixer.h" #include "core/idManager.h" @@ -69,25 +68,11 @@ void init() /* -------------------------------------------------------------------------- */ -std::unique_ptr create(ChannelType type, int bufferSize, +std::unique_ptr create(ChannelType type, int bufferSize, bool inputMonitorOn, ID columnId) { - std::unique_ptr ch = nullptr; - - if (type == ChannelType::SAMPLE) - ch = std::make_unique(inputMonitorOn, bufferSize, columnId, channelId_.get()); - else - if (type == ChannelType::MIDI) - ch = std::make_unique(bufferSize, columnId, channelId_.get()); - else - if (type == ChannelType::MASTER) - ch = std::make_unique(bufferSize, channelId_.get()); - else - if (type == ChannelType::PREVIEW) - ch = std::make_unique(bufferSize, channelId_.get()); // TODO - temporary placeholder - - assert(ch != nullptr); - return ch; + return std::make_unique(type, channelId_.get(), columnId, + kernelAudio::getRealBufSize()); } @@ -96,22 +81,8 @@ std::unique_ptr create(ChannelType type, int bufferSize, std::unique_ptr create(const Channel& o) { - std::unique_ptr ch = nullptr; - - if (o.type == ChannelType::SAMPLE) - ch = std::make_unique(static_cast(o)); - else - if (o.type == ChannelType::MIDI) - ch = std::make_unique(static_cast(o)); - else - if (o.type == ChannelType::MASTER) - ch = std::make_unique(static_cast(o)); - - assert(ch != nullptr); - - if (o.type != ChannelType::MASTER) - ch->id = channelId_.get(); - + std::unique_ptr ch = std::make_unique(o); + ch->id = channelId_.get(); return ch; } @@ -121,19 +92,8 @@ std::unique_ptr create(const Channel& o) std::unique_ptr deserializeChannel(const patch::Channel& pch, int bufferSize) { - std::unique_ptr ch = nullptr; - - if (pch.type == ChannelType::SAMPLE) - ch = std::make_unique(pch, bufferSize); - else - if (pch.type == ChannelType::MIDI) - ch = std::make_unique(pch, bufferSize); - - assert(ch != nullptr); - channelId_.set(pch.id); - - return ch; + return std::make_unique(pch, bufferSize); } @@ -144,58 +104,55 @@ const patch::Channel serializeChannel(const Channel& c) { patch::Channel pc; - pc.id = c.id; - pc.type = c.type; - #ifdef WITH_VST for (ID pid : c.pluginIds) pc.pluginIds.push_back(pid); -#endif - - if (c.type != ChannelType::MASTER) { - pc.height = c.height; - pc.name = c.name.c_str(); - pc.columnId = c.columnId; - pc.key = c.key; - pc.mute = c.mute; - pc.solo = c.solo; - pc.volume = c.volume; - pc.pan = c.pan; - pc.hasActions = c.hasActions; - pc.armed = c.armed; - pc.midiIn = c.midiIn; - pc.midiInKeyPress = c.midiInKeyRel; - pc.midiInKeyRel = c.midiInKeyPress; - pc.midiInKill = c.midiInKill; - pc.midiInArm = c.midiInArm; - pc.midiInVolume = c.midiInVolume; - pc.midiInMute = c.midiInMute; - pc.midiInSolo = c.midiInSolo; - pc.midiInFilter = c.midiInFilter; - pc.midiOutL = c.midiOutL; - pc.midiOutLplaying = c.midiOutLplaying; - pc.midiOutLmute = c.midiOutLmute; - pc.midiOutLsolo = c.midiOutLsolo; - } +#endif + + pc.id = c.id; + pc.type = c.getType(); + pc.columnId = c.getColumnId(); + pc.height = c.state->height; + pc.name = c.state->name; + pc.key = c.state->key.load(); + pc.mute = c.state->mute.load(); + pc.solo = c.state->solo.load(); + pc.volume = c.state->volume.load(); + pc.pan = c.state->pan.load(); + pc.hasActions = c.state->hasActions; + pc.readActions = c.state->readActions.load(); + pc.armed = c.state->armed.load(); + pc.midiIn = c.midiLearner.state->enabled.load(); + pc.midiInFilter = c.midiLearner.state->filter.load(); + pc.midiInKeyPress = c.midiLearner.state->keyPress.load(); + pc.midiInKeyRel = c.midiLearner.state->keyRelease.load(); + pc.midiInKill = c.midiLearner.state->kill.load(); + pc.midiInArm = c.midiLearner.state->arm.load(); + pc.midiInVolume = c.midiLearner.state->volume.load(); + pc.midiInMute = c.midiLearner.state->mute.load(); + pc.midiInSolo = c.midiLearner.state->solo.load(); + pc.midiInReadActions = c.midiLearner.state->readActions.load(); + pc.midiInPitch = c.midiLearner.state->pitch.load(); + pc.midiOutL = c.midiLighter.state->enabled.load(); + pc.midiOutLplaying = c.midiLighter.state->playing.load(); + pc.midiOutLmute = c.midiLighter.state->mute.load(); + pc.midiOutLsolo = c.midiLighter.state->solo.load(); + + if (c.getType() == ChannelType::SAMPLE) { + pc.waveId = c.samplePlayer->getWaveId(); + pc.mode = c.samplePlayer->state->mode.load(); + pc.begin = c.samplePlayer->state->begin.load(); + pc.end = c.samplePlayer->state->end.load(); + pc.pitch = c.samplePlayer->state->pitch.load(); + pc.shift = c.samplePlayer->state->shift.load(); + pc.midiInVeloAsVol = c.samplePlayer->state->velocityAsVol.load(); + pc.inputMonitor = c.audioReceiver->state->inputMonitor.load(); - if (c.type == ChannelType::SAMPLE) { - const SampleChannel& sc = static_cast(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(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; diff --git a/src/core/channels/channelManager.h b/src/core/channels/channelManager.h index 4229a04..e7667fe 100644 --- a/src/core/channels/channelManager.h +++ b/src/core/channels/channelManager.h @@ -40,9 +40,11 @@ namespace patch { struct Channel; } -class Channel; -class SampleChannel; -class MidiChannel; +class Channel; +struct ChannelState; +class Channel; +class SampleChannel; +class MidiChannel; namespace channelManager { /* init @@ -53,7 +55,7 @@ void init(); /* create (1) Creates a new Channel from scratch. */ -std::unique_ptr create(ChannelType type, int bufferSize, +std::unique_ptr create(ChannelType type, int bufferSize, bool inputMonitorOn, ID columnId); /* create (2) diff --git a/src/core/channels/masterChannel.cpp b/src/core/channels/masterChannel.cpp deleted file mode 100644 index 9ce1735..0000000 --- a/src/core/channels/masterChannel.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include "masterChannel.h" - - -namespace giada { -namespace m -{ -MasterChannel::MasterChannel(int bufferSize, ID id) -: Channel(ChannelType::MASTER, ChannelStatus::OFF, bufferSize, 0, id) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -MasterChannel::MasterChannel(const patch::Channel& p, int bufferSize) -: Channel(p, bufferSize) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -MasterChannel* MasterChannel::clone() const -{ - return new MasterChannel(*this); -} - - -/* -------------------------------------------------------------------------- */ - - -void MasterChannel::load(const patch::Channel& p) -{ - volume = p.volume; -#ifdef WITH_VST - pluginIds = p.pluginIds; -#endif -} - - -/* -------------------------------------------------------------------------- */ - - -void MasterChannel::render(AudioBuffer& out, const AudioBuffer& in, - AudioBuffer& inToOut, bool audible, bool running) -{ -#ifdef WITH_VST - if (pluginIds.size() == 0) - return; - if (id == mixer::MASTER_OUT_CHANNEL_ID) - pluginHost::processStack(out, pluginIds); - else - if (id == mixer::MASTER_IN_CHANNEL_ID) - pluginHost::processStack(inToOut, pluginIds); -#endif -} - -}} // giada::m:: diff --git a/src/core/channels/masterChannel.h b/src/core/channels/masterChannel.h deleted file mode 100644 index 1e336c6..0000000 --- a/src/core/channels/masterChannel.h +++ /dev/null @@ -1,64 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_MASTER_CHANNEL_H -#define G_MASTER_CHANNEL_H - - -#include "core/channels/channel.h" - - -namespace giada { -namespace m -{ -class MasterChannel : public Channel -{ -public: - - MasterChannel(int bufferSize, ID id); - MasterChannel(const patch::Channel& p, int bufferSize); - - MasterChannel* clone() const override; - void load(const patch::Channel& p) override; - void parseEvents(mixer::FrameEvents fe) override {}; - void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, - bool audible, bool running) override; - void start(int frame, bool doQuantize, int velocity) override {}; - void kill(int localFrame) override {}; - void empty() override {}; - void stopBySeq(bool chansStopOnSeqHalt) override {}; - void stop() override {}; - void rewindBySeq() override {}; - void setMute(bool value) override {}; - void setSolo(bool value) override {}; - void receiveMidi(const MidiEvent& midiEvent) override {}; -}; - -}} // giada::m:: - - -#endif diff --git a/src/core/channels/midiActionRecorder.cpp b/src/core/channels/midiActionRecorder.cpp new file mode 100644 index 0000000..dd1e038 --- /dev/null +++ b/src/core/channels/midiActionRecorder.cpp @@ -0,0 +1,91 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "core/action.h" +#include "core/clock.h" +#include "core/conf.h" +#include "core/mixer.h" +#include "core/recorderHandler.h" +#include "core/recManager.h" +#include "core/channels/state.h" +#include "midiActionRecorder.h" + + +namespace giada { +namespace m +{ +MidiActionRecorder::MidiActionRecorder(ChannelState* c) +: m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiActionRecorder::MidiActionRecorder(const MidiActionRecorder& o, ChannelState* c) +: MidiActionRecorder(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiActionRecorder::parse(const mixer::Event& e) const +{ + assert(m_channelState != nullptr); + + if (e.type == mixer::EventType::MIDI && canRecord()) + record(e.action.event); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiActionRecorder::record(const MidiEvent& e) const +{ + MidiEvent flat(e); + flat.setChannel(0); + recorderHandler::liveRec(m_channelState->id, flat, clock::quantize(clock::getCurrentFrame())); + m_channelState->hasActions = true; +} + + +/* -------------------------------------------------------------------------- */ + + +bool MidiActionRecorder::canRecord() const +{ + return recManager::isRecordingAction() && + clock::isRunning() && + !recManager::isRecordingInput(); +} +}} // giada::m:: + diff --git a/src/core/channels/midiActionRecorder.h b/src/core/channels/midiActionRecorder.h new file mode 100644 index 0000000..50e3109 --- /dev/null +++ b/src/core/channels/midiActionRecorder.h @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_MIDI_ACTION_RECORDER_H +#define G_CHANNEL_MIDI_ACTION_RECORDER_H + + +#include "core/types.h" + + +namespace giada { +namespace m +{ +namespace mixer +{ +struct Event; +} +struct ChannelState; +class MidiActionRecorder +{ +public: + + MidiActionRecorder(ChannelState*); + MidiActionRecorder(const MidiActionRecorder&, ChannelState* c=nullptr); + + void parse(const mixer::Event& e) const; + +private: + + bool canRecord() const; + void record(const MidiEvent& e) const; + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/midiChannel.cpp b/src/core/channels/midiChannel.cpp deleted file mode 100644 index 89dae2d..0000000 --- a/src/core/channels/midiChannel.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#include "utils/log.h" -#include "core/channels/midiChannelProc.h" -#include "core/channels/channelManager.h" -#include "core/channels/channel.h" -#include "core/recorder.h" -#include "core/recorderHandler.h" -#include "core/recManager.h" -#include "core/action.h" -#include "core/patch.h" -#include "core/const.h" -#include "core/conf.h" -#include "core/mixer.h" -#include "core/pluginHost.h" -#include "core/kernelMidi.h" -#include "midiChannel.h" - - -namespace giada { -namespace m -{ -MidiChannel::MidiChannel(int bufferSize, ID columnId, ID id) -: Channel (ChannelType::MIDI, ChannelStatus::OFF, bufferSize, columnId, id), - midiOut (false), - midiOutChan(G_MIDI_CHANS[0]) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -MidiChannel::MidiChannel(const MidiChannel& o) -: Channel (o), - midiOut (o.midiOut), - midiOutChan(o.midiOutChan) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -MidiChannel::MidiChannel(const patch::Channel& p, int bufferSize) -: Channel (p, bufferSize), - midiOut (p.midiOut), - midiOutChan(p.midiOutChan) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -MidiChannel* MidiChannel::clone() const -{ - return new MidiChannel(*this); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::parseEvents(mixer::FrameEvents fe) -{ - midiChannelProc::parseEvents(this, fe); -} - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::render(AudioBuffer& out, const AudioBuffer& in, - AudioBuffer& inToOut, bool audible, bool running) -{ - midiChannelProc::process(this, out, in, audible); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::stopBySeq(bool chansStopOnSeqHalt) -{ - midiChannelProc::stopBySeq(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::start(int frame, bool doQuantize, int velocity) -{ - midiChannelProc::start(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::kill(int localFrame) -{ - midiChannelProc::kill(this, localFrame); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::rewindBySeq() -{ - midiChannelProc::rewindBySeq(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::setMute(bool value) -{ - midiChannelProc::setMute(this, value); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::setSolo(bool value) -{ - midiChannelProc::setSolo(this, value); -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::empty() -{ - hasActions = false; -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::sendMidi(const MidiEvent& e, int localFrame) -{ - if (midiOut) { - MidiEvent e_ = e; - e_.setChannel(midiOutChan); - kernelMidi::send(e_.getRaw()); - } - -#ifdef WITH_VST - - /* Enqueue this MIDI event for plug-ins processing. Will be read and - rendered later on by the audio thread. */ - - MidiEvent e_ = e; - e_.setDelta(localFrame); - midiQueue.push(e_); - -#endif -} - - -/* -------------------------------------------------------------------------- */ - - -void MidiChannel::receiveMidi(const MidiEvent& midiEvent) -{ - namespace mrh = m::recorderHandler; - namespace mr = m::recorder; - - if (!armed) - return; - - /* Now all messages are turned into Channel-0 messages. Giada doesn't care - about holding MIDI channel information. Moreover, having all internal - messages on channel 0 is way easier. */ - - MidiEvent midiEventFlat(midiEvent); - midiEventFlat.setChannel(0); - -#ifdef WITH_VST - - /* Enqueue this MIDI event for plug-ins processing. Will be read and - rendered later on by the audio thread. */ - - midiQueue.push(midiEventFlat); - -#endif - - if (recManager::isRecordingAction()) { - mrh::liveRec(id, midiEventFlat); - hasActions = true; - } -} - -}} // giada::m:: diff --git a/src/core/channels/midiChannel.h b/src/core/channels/midiChannel.h deleted file mode 100644 index 7f2f550..0000000 --- a/src/core/channels/midiChannel.h +++ /dev/null @@ -1,75 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_MIDI_CHANNEL_H -#define G_MIDI_CHANNEL_H - - -#ifdef WITH_VST -#include "deps/juce-config.h" -#endif -#include "core/channels/channel.h" - - -namespace giada { -namespace m -{ -class MidiChannel : public Channel -{ -public: - - MidiChannel(int bufferSize, ID columnId, ID id); - MidiChannel(const MidiChannel& o); - MidiChannel(const patch::Channel& p, int bufferSize); - - MidiChannel* clone() const override; - void parseEvents(mixer::FrameEvents fe) override; - void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, - bool audible, bool running) override; - void start(int frame, bool doQuantize, int velocity) override; - void kill(int localFrame) override; - void empty() override; - void stopBySeq(bool chansStopOnSeqHalt) override; - void stop() override {}; - void rewindBySeq() override; - void setMute(bool value) override; - void setSolo(bool value) override; - void receiveMidi(const MidiEvent& midiEvent) override; - - /* sendMidi - Sends Midi event to the outside world. */ - - void sendMidi(const MidiEvent& e, int localFrame); - - bool midiOut; // enable midi output - int midiOutChan; // midi output channel -}; - -}} // giada::m:: - - -#endif diff --git a/src/core/channels/midiChannelProc.cpp b/src/core/channels/midiChannelProc.cpp deleted file mode 100644 index 204f5a6..0000000 --- a/src/core/channels/midiChannelProc.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#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; ibuffer[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& c : model::getLayout()->channels) - // c->sendMidiLstatus(); - - ch->sendMidiLsolo(); -} - - -/* -------------------------------------------------------------------------- */ - - -void stopBySeq(MidiChannel* ch) -{ - sendAllNotesOff_(ch); - kill(ch, 0); -} -}}}; diff --git a/src/core/channels/midiChannelProc.h b/src/core/channels/midiChannelProc.h deleted file mode 100644 index 724759f..0000000 --- a/src/core/channels/midiChannelProc.h +++ /dev/null @@ -1,78 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_MIDI_CHANNEL_PROC_H -#define G_MIDI_CHANNEL_PROC_H - - -#include "core/mixer.h" -#include "core/audioBuffer.h" - - -namespace giada { -namespace m -{ -class MidiChannel; -namespace midiChannelProc -{ -/* parseEvents -Parses events gathered by Mixer::masterPlay(). */ - -void parseEvents(MidiChannel* ch, mixer::FrameEvents ev); - -/**/ -void process(MidiChannel* ch, AudioBuffer& out, const AudioBuffer& in, bool audible); - -/* kill -Stops a channel abruptly. */ - -void kill(MidiChannel* ch, int localFrame); - -/* start -Starts a channel. */ - -void start(MidiChannel* ch); - -/* stopBySeq -Stops a channel when the stop button on main transport is pressed. */ - -void stopBySeq(MidiChannel* ch); - -/* rewind -Rewinds channel when rewind button on main transport is pressed. */ - -void rewindBySeq(MidiChannel* ch); - -/* mute|unmute -Mutes/unmutes a channel. */ - -void setMute(MidiChannel* ch, bool v); -void setSolo(MidiChannel* ch, bool v); -}}}; - - -#endif diff --git a/src/core/channels/midiController.cpp b/src/core/channels/midiController.cpp new file mode 100644 index 0000000..9b73df4 --- /dev/null +++ b/src/core/channels/midiController.cpp @@ -0,0 +1,126 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "core/conf.h" +#include "core/channels/state.h" +#include "midiController.h" + + +namespace giada { +namespace m +{ +MidiController::MidiController(ChannelState* c) +: m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiController::MidiController(const MidiController& o, ChannelState* c) +: m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiController::parse(const mixer::Event& e) const +{ + assert(m_channelState != nullptr); + + switch (e.type) { + + case mixer::EventType::KEY_PRESS: + press(); break; + + case mixer::EventType::KEY_KILL: + case mixer::EventType::SEQUENCER_STOP: + kill(); break; + + case mixer::EventType::SEQUENCER_FIRST_BEAT: + case mixer::EventType::SEQUENCER_REWIND: + onFirstBeat(); + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiController::press() const +{ + ChannelStatus playStatus = m_channelState->playStatus.load(); + + switch (playStatus) { + case ChannelStatus::PLAY: + playStatus = ChannelStatus::ENDING; break; + + case ChannelStatus::ENDING: + case ChannelStatus::WAIT: + playStatus = ChannelStatus::OFF; break; + + case ChannelStatus::OFF: + playStatus = ChannelStatus::WAIT; break; + + default: break; + } + + m_channelState->playStatus.store(playStatus); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiController::kill() const +{ + m_channelState->playStatus.store(ChannelStatus::OFF); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiController::onFirstBeat() const +{ + ChannelStatus playStatus = m_channelState->playStatus.load(); + + if (playStatus == ChannelStatus::ENDING) + playStatus = ChannelStatus::OFF; + else + if (playStatus == ChannelStatus::WAIT) + playStatus = ChannelStatus::PLAY; + + m_channelState->playStatus.store(playStatus); +} +}} // giada::m:: diff --git a/src/core/channels/midiController.h b/src/core/channels/midiController.h new file mode 100644 index 0000000..7a43778 --- /dev/null +++ b/src/core/channels/midiController.h @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_MIDI_CONTROLLER_H +#define G_CHANNEL_MIDI_CONTROLLER_H + + +#include "core/types.h" +#include "core/mixer.h" // TODO - forward declare + + +namespace giada { +namespace m +{ +/* MidiController +Manages events for a MIDI Channel. */ + +class MidiController +{ +public: + + MidiController(ChannelState*); + MidiController(const MidiController&, ChannelState* c=nullptr); + + void parse(const mixer::Event& e) const; + +private: + + void press() const; + void kill() const; + void onFirstBeat() const; + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/midiLearner.cpp b/src/core/channels/midiLearner.cpp new file mode 100644 index 0000000..0386546 --- /dev/null +++ b/src/core/channels/midiLearner.cpp @@ -0,0 +1,57 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "core/channels/state.h" +#include "midiLearner.h" + + +namespace giada { +namespace m +{ +MidiLearner::MidiLearner() +: state(std::make_unique()) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiLearner::MidiLearner(const patch::Channel& p) +: state(std::make_unique(p)) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiLearner::MidiLearner(const MidiLearner& o) +: state(std::make_unique(*o.state)) +{ +} +}} // giada::m:: diff --git a/src/core/channels/midiLearner.h b/src/core/channels/midiLearner.h new file mode 100644 index 0000000..5acb93d --- /dev/null +++ b/src/core/channels/midiLearner.h @@ -0,0 +1,55 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_MIDI_LEARNER_H +#define G_CHANNEL_MIDI_LEARNER_H + + +#include + + +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 state; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/midiLighter.cpp b/src/core/channels/midiLighter.cpp new file mode 100644 index 0000000..03b5b19 --- /dev/null +++ b/src/core/channels/midiLighter.cpp @@ -0,0 +1,149 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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()) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiLighter::MidiLighter(const patch::Channel& p, ChannelState* c) +: state (std::make_unique(p)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiLighter::MidiLighter(const MidiLighter& o, ChannelState* c) +: state (std::make_unique(*o.state)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiLighter::parse(const mixer::Event& e, bool audible) const +{ + if (state->enabled.load() == false) + return; + + uint32_t l_playing = state->playing.load(); + uint32_t l_mute = state->mute.load(); + uint32_t l_solo = state->solo.load(); + + switch (e.type) { + + case mixer::EventType::KEY_PRESS: + case mixer::EventType::KEY_RELEASE: + case mixer::EventType::KEY_KILL: + case mixer::EventType::SEQUENCER_STOP: + if (l_playing != 0x0) sendStatus(l_playing, audible); + break; + + case mixer::EventType::CHANNEL_MUTE: + if (l_mute != 0x0) sendMute(l_mute); + break; + + case mixer::EventType::CHANNEL_SOLO: + if (l_solo != 0x0) sendSolo(l_solo); + break; + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiLighter::sendMute(uint32_t l_mute) const +{ + if (m_channelState->mute.load() == true) + kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOn); + else + kernelMidi::sendMidiLightning(l_mute, midimap::midimap.muteOff); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiLighter::sendSolo(uint32_t l_solo) const +{ + if (m_channelState->solo.load() == true) + kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOn); + else + kernelMidi::sendMidiLightning(l_solo, midimap::midimap.soloOff); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiLighter::sendStatus(uint32_t l_playing, bool audible) const +{ + switch (m_channelState->playStatus.load()) { + + case ChannelStatus::OFF: + kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopped); + break; + + case ChannelStatus::WAIT: + kernelMidi::sendMidiLightning(l_playing, midimap::midimap.waiting); + break; + + case ChannelStatus::ENDING: + kernelMidi::sendMidiLightning(l_playing, midimap::midimap.stopping); + break; + + case ChannelStatus::PLAY: + kernelMidi::sendMidiLightning(l_playing, audible ? midimap::midimap.playing : midimap::midimap.playingInaudible); + break; + + default: break; + } +} +}} // giada::m:: diff --git a/src/core/channels/midiLighter.h b/src/core/channels/midiLighter.h new file mode 100644 index 0000000..8b77b29 --- /dev/null +++ b/src/core/channels/midiLighter.h @@ -0,0 +1,73 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_MIDI_LIGHTER_H +#define G_CHANNEL_MIDI_LIGHTER_H + + +#include + + +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 state; + +private: + + void sendMute(uint32_t l_mute) const; + void sendSolo(uint32_t l_solo) const; + void sendStatus(uint32_t l_playing, bool audible) const; + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/midiReceiver.cpp b/src/core/channels/midiReceiver.cpp new file mode 100644 index 0000000..0801b1e --- /dev/null +++ b/src/core/channels/midiReceiver.cpp @@ -0,0 +1,131 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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()) +, m_channelState (c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiReceiver::MidiReceiver(const patch::Channel& p, ChannelState* c) +: state (std::make_unique()) +, m_channelState (c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiReceiver::MidiReceiver(const MidiReceiver& o, ChannelState* c) +: state (std::make_unique()) +, 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& pluginIds) const +{ + pluginHost::processStack(m_channelState->buffer, pluginIds, &state->midiBuffer); + state->midiBuffer.clear(); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiReceiver::parseMidi(const MidiEvent& e) const +{ + /* Now all messages are turned into Channel-0 messages. Giada doesn't care + about holding MIDI channel information. Moreover, having all internal + messages on channel 0 is way easier. Then send it to plug-ins. */ + + MidiEvent flat(e); + flat.setChannel(0); + sendToPlugins(flat, /*delta=*/0); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiReceiver::sendToPlugins(const MidiEvent& e, Frame localFrame) const +{ + juce::MidiMessage message = juce::MidiMessage( + e.getStatus(), + e.getNote(), + e.getVelocity()); + state->midiBuffer.addEvent(message, localFrame); +} +}} // giada::m:: + + +#endif // WITH_VST \ No newline at end of file diff --git a/src/core/channels/midiReceiver.h b/src/core/channels/midiReceiver.h new file mode 100644 index 0000000..dc83bbe --- /dev/null +++ b/src/core/channels/midiReceiver.h @@ -0,0 +1,89 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_MIDI_RECEIVER_H +#define G_CHANNEL_MIDI_RECEIVER_H + + +#ifdef WITH_VST + + +#include + + +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& pluginIds) const; + + /* state + Pointer to mutable MidiReceiverState state. */ + + std::unique_ptr state; + +private: + + /* parseMidi + Takes a live message (e.g. from a MIDI keyboard), strips it and sends it + to plug-ins. */ + + void parseMidi(const MidiEvent& e) const; + + /* sendToPlugins + Enqueues the MIDI event for plug-ins processing. This will be read later on + by the PluginHost. */ + + void sendToPlugins(const MidiEvent& e, Frame localFrame) const; + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif // WITH_VST + + +#endif diff --git a/src/core/channels/midiSender.cpp b/src/core/channels/midiSender.cpp new file mode 100644 index 0000000..a5a616a --- /dev/null +++ b/src/core/channels/midiSender.cpp @@ -0,0 +1,92 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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()) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiSender::MidiSender(const patch::Channel& p, ChannelState* c) +: state(std::make_unique(p)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiSender::MidiSender(const MidiSender& o, ChannelState* c) +: state(std::make_unique(*o.state)) +, m_channelState(c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiSender::parse(const mixer::Event& e) const +{ + bool isPlaying = m_channelState->isPlaying(); + bool isEnabled = state->enabled.load(); + + if (!isPlaying || !isEnabled) + return; + + if (e.type == mixer::EventType::KEY_KILL || + e.type == mixer::EventType::SEQUENCER_STOP) + send(MidiEvent(G_MIDI_ALL_NOTES_OFF)); + else + if (e.type == mixer::EventType::ACTION) + send(e.action.event); +} + + +/* -------------------------------------------------------------------------- */ + + +void MidiSender::send(MidiEvent e) const +{ + e.setChannel(state->filter.load()); + kernelMidi::send(e.getRaw()); +} +}} // giada::m:: diff --git a/src/core/channels/midiSender.h b/src/core/channels/midiSender.h new file mode 100644 index 0000000..5f2d51f --- /dev/null +++ b/src/core/channels/midiSender.h @@ -0,0 +1,65 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 state; + +private: + + void send(MidiEvent e) const; + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/sampleActionRecorder.cpp b/src/core/channels/sampleActionRecorder.cpp new file mode 100644 index 0000000..10c61c6 --- /dev/null +++ b/src/core/channels/sampleActionRecorder.cpp @@ -0,0 +1,235 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "core/action.h" +#include "core/clock.h" +#include "core/conf.h" +#include "core/mixer.h" +#include "core/recorderHandler.h" +#include "core/recManager.h" +#include "core/channels/state.h" +#include "sampleActionRecorder.h" + + +namespace giada { +namespace m +{ +SampleActionRecorder::SampleActionRecorder(ChannelState* c, SamplePlayerState* sc) +: m_channelState(c) +, m_samplePlayerState(sc) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +SampleActionRecorder::SampleActionRecorder(const SampleActionRecorder& o, + ChannelState* c, SamplePlayerState* sc) +: SampleActionRecorder(c, sc) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::parse(const mixer::Event& e) const +{ + assert(m_channelState != nullptr); + + switch (e.type) { + + case mixer::EventType::KEY_PRESS: + onKeyPress(); break; + + /* Record a stop event only if channel is SINGLE_PRESS. For any other + mode the key release event is meaningless. */ + + case mixer::EventType::KEY_RELEASE: + if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) + record(MidiEvent::NOTE_OFF); + break; + + case mixer::EventType::KEY_KILL: + if (canRecord()) + record(MidiEvent::NOTE_KILL); + break; + + case mixer::EventType::SEQUENCER_FIRST_BEAT: + onFirstBeat(); break; + + case mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS: + toggleReadActions(); break; + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::record(int note) const +{ + recorderHandler::liveRec(m_channelState->id, MidiEvent(note, 0, 0), + clock::quantize(clock::getCurrentFrame())); + + m_channelState->hasActions = true; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::startReadActions() const +{ + if (conf::conf.treatRecsAsLoops) + m_channelState->recStatus.store(ChannelStatus::WAIT); + else { + m_channelState->recStatus.store(ChannelStatus::PLAY); + m_channelState->readActions.store(true); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::stopReadActions(ChannelStatus curRecStatus) const +{ + /* First of all, if the clock is not running or treatRecsAsLoops is off, + just stop and disable everything. Otherwise make sure a channel with actions + behave like a dynamic one. */ + + if (!clock::isRunning() || !conf::conf.treatRecsAsLoops) { + m_channelState->recStatus.store(ChannelStatus::OFF); + m_channelState->readActions.store(false); + } + else + if (curRecStatus == ChannelStatus::WAIT) + m_channelState->recStatus.store(ChannelStatus::OFF); + else + if (curRecStatus == ChannelStatus::ENDING) + m_channelState->recStatus.store(ChannelStatus::PLAY); + else + m_channelState->recStatus.store(ChannelStatus::ENDING); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::toggleReadActions() const +{ + /* When you start reading actions while conf::treatRecsAsLoops is true, the + value ch.state->readActions actually is not set to true immediately, because + the channel is in wait mode (REC_WAITING). readActions will become true on + the next first beat. So a 'stop rec' command should occur also when + readActions is false but the channel is in wait mode; this check will + handle the case of when you press 'R', the channel goes into REC_WAITING and + then you press 'R' again to undo the status. */ + + if (!m_channelState->hasActions) + return; + + bool readActions = m_channelState->readActions.load(); + ChannelStatus recStatus = m_channelState->recStatus.load(); + + if (readActions || (!readActions && recStatus == ChannelStatus::WAIT)) + stopReadActions(recStatus); + else + startReadActions(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::onKeyPress() const +{ + if (!canRecord()) + return; + record(MidiEvent::NOTE_ON); + + /* Skip reading actions when recording on ChannelMode::SINGLE_PRESS to + prevent existing actions to interfere with the keypress/keyrel combo. */ + + if (m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) + m_channelState->readActions = false; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::onKeyRelease() const +{ + if (canRecord() && m_samplePlayerState->mode.load() == SamplePlayerMode::SINGLE_PRESS) { + record(MidiEvent::NOTE_OFF); + m_channelState->readActions = true; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleActionRecorder::onFirstBeat() const +{ + ChannelStatus recStatus = m_channelState->recStatus.load(); + + switch (recStatus) { + + case ChannelStatus::ENDING: + m_channelState->recStatus.store(ChannelStatus::OFF); + m_channelState->readActions = false; + break; + + case ChannelStatus::WAIT: + m_channelState->recStatus.store(ChannelStatus::PLAY); + m_channelState->readActions = true; + break; + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +bool SampleActionRecorder::canRecord() const +{ + return recManager::isRecordingAction() && + clock::isRunning() && + !recManager::isRecordingInput() && + !m_samplePlayerState->isAnyLoopMode(); +} +}} // giada::m:: + diff --git a/src/core/channels/sampleActionRecorder.h b/src/core/channels/sampleActionRecorder.h new file mode 100644 index 0000000..0f81b17 --- /dev/null +++ b/src/core/channels/sampleActionRecorder.h @@ -0,0 +1,76 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_SAMPLE_ACTION_RECORDER_H +#define G_CHANNEL_SAMPLE_ACTION_RECORDER_H + + +#include "core/types.h" + + +namespace giada { +namespace m +{ +namespace mixer +{ +struct Event; +} +struct ChannelState; + +/* SampleActionRecorder +Records actions for channels and optionally manages the 'read action' state ('R' +button on Sample Channels). */ + +class SampleActionRecorder +{ +public: + + SampleActionRecorder(ChannelState*, SamplePlayerState*); + SampleActionRecorder(const SampleActionRecorder&, ChannelState* c=nullptr, + SamplePlayerState* sc=nullptr); + + void parse(const mixer::Event& e) const; + +private: + void record(int note) const; + void onKeyPress() const; + void onKeyRelease() const; + void onFirstBeat() const; + + void toggleReadActions() const; + void startReadActions() const; + void stopReadActions(ChannelStatus curRecStatus) const; + + bool canRecord() const; + + ChannelState* m_channelState; + SamplePlayerState* m_samplePlayerState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/sampleChannel.cpp b/src/core/channels/sampleChannel.cpp deleted file mode 100644 index 73eb4f0..0000000 --- a/src/core/channels/sampleChannel.cpp +++ /dev/null @@ -1,553 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#include "utils/log.h" -#include "core/const.h" -#include "core/wave.h" -#include "core/model/model.h" -#include "sampleChannelProc.h" -#include "sampleChannelRec.h" -#include "channelManager.h" -#include "sampleChannel.h" - - -namespace giada { -namespace m -{ -SampleChannel::SampleChannel(bool inputMonitor, int bufferSize, - ID columnId, ID id) -: Channel (ChannelType::SAMPLE, ChannelStatus::EMPTY, bufferSize, - columnId, id), - hasWave (false), - waveId (0), - shift (0), - mode (ChannelMode::SINGLE_BASIC), - quantizing (false), - inputMonitor (inputMonitor), - pitch (G_DEFAULT_PITCH), - tracker (0), - trackerPreview (0), - begin (0), - end (0), - midiInVeloAsVol (false), - midiInReadActions(0x0), - midiInPitch (0x0), - bufferOffset (0), - rewinding (false), - rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr)) -{ - if (rsmp_state == nullptr) { - u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n"); - throw std::bad_alloc(); - } - bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS); -} - - -/* -------------------------------------------------------------------------- */ - - -SampleChannel::SampleChannel(const SampleChannel& o) -: Channel (o), - hasWave (o.hasWave), - waveId (o.waveId), - shift (o.shift), - mode (o.mode), - quantizing (o.quantizing), - inputMonitor (o.inputMonitor), - pitch (o.pitch), - tracker (o.tracker), - trackerPreview (0), - begin (o.begin), - end (o.end), - midiInVeloAsVol (o.midiInVeloAsVol), - midiInReadActions(o.midiInReadActions), - midiInPitch (o.midiInPitch), - bufferOffset (o.bufferOffset), - rewinding (o.rewinding), - rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr)) -{ - if (rsmp_state == nullptr) { - u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n"); - throw std::bad_alloc(); - } - - bufferPreview.alloc(o.bufferPreview.countFrames(), G_MAX_IO_CHANS); -} - - -/* -------------------------------------------------------------------------- */ - - -SampleChannel::SampleChannel(const patch::Channel& p, int bufferSize) -: Channel (p, bufferSize), - hasWave (p.waveId != 0), - waveId (p.waveId), - shift (0), // TODO - mode (p.mode), - quantizing (false), - inputMonitor (p.inputMonitor), - pitch (p.pitch), - tracker (0), - trackerPreview (0), - begin (p.begin), - end (p.end), - midiInVeloAsVol (p.midiInVeloAsVol), - midiInReadActions(p.midiInReadActions), - midiInPitch (p.midiInPitch), - bufferOffset (0), - rewinding (0), - rsmp_state (src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr)) -{ - if (rsmp_state == nullptr) { - u::log::print("[SampleChannel] unable to alloc memory for SRC_STATE!\n"); - throw std::bad_alloc(); - } - - bufferPreview.alloc(bufferSize, G_MAX_IO_CHANS); -} - - -/* -------------------------------------------------------------------------- */ - - -SampleChannel* SampleChannel::clone() const -{ - return new SampleChannel(*this); -} - - -/* -------------------------------------------------------------------------- */ - - -SampleChannel::~SampleChannel() -{ - if (rsmp_state != nullptr) - src_delete(rsmp_state); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::parseEvents(mixer::FrameEvents fe) -{ - sampleChannelProc::parseEvents(this, fe); - sampleChannelRec::parseEvents(this, fe); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::render(AudioBuffer& out, const AudioBuffer& in, - AudioBuffer& inToOut, bool audible, bool running) -{ - sampleChannelProc::render(this, out, in, inToOut, audible, running); -} - - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::rewindBySeq() -{ - sampleChannelProc::rewindBySeq(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::start(int localFrame, bool doQuantize, int velocity) -{ - sampleChannelProc::start(this, localFrame, doQuantize, velocity); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::stop() -{ - sampleChannelProc::stop(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::stopBySeq(bool chansStopOnSeqHalt) -{ - sampleChannelProc::stopBySeq(this, chansStopOnSeqHalt); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::kill(int localFrame) -{ - sampleChannelProc::kill(this, localFrame); -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::recordStart(bool canQuantize) -{ - return sampleChannelRec::recordStart(this, canQuantize); -} - - -bool SampleChannel::recordKill() -{ - return sampleChannelRec::recordKill(this); -} - - -void SampleChannel::recordStop() -{ - sampleChannelRec::recordStop(this); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) -{ - sampleChannelRec::startReadingActions(this, treatRecsAsLoops, recsStopOnChanHalt); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::stopReadingActions(bool running, bool treatRecsAsLoops, - bool recsStopOnChanHalt) -{ - sampleChannelRec::stopReadingActions(this, running, treatRecsAsLoops, - recsStopOnChanHalt); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::stopInputRec(int globalFrame) -{ - sampleChannelProc::stopInputRec(this, globalFrame); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setMute(bool value) -{ - sampleChannelProc::setMute(this, value); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setSolo(bool value) -{ - sampleChannelProc::setSolo(this, value); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setReadActions(bool v, bool recsStopOnChanHalt) -{ - sampleChannelRec::setReadActions(this, v, recsStopOnChanHalt); -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::hasLogicalData() const -{ - if (!hasWave) - return false; - - model::WavesLock wl(model::waves); - return model::get(model::waves, waveId).isLogical(); -}; - - -bool SampleChannel::hasEditedData() const -{ - if (!hasWave) - return false; - - model::WavesLock wl(model::waves); - return model::get(model::waves, waveId).isEdited(); -}; - - -bool SampleChannel::hasData() const -{ - return hasWave; -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setBegin(int f) -{ - model::WavesLock lock(model::waves); - const Wave& wave = model::get(model::waves, waveId); - - if (f < 0) - f = 0; - else - if (f > wave.getSize()) - f = wave.getSize(); - else - if (f >= end) - f = end - 1; - - begin = f; - tracker = f; - trackerPreview = f; -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setEnd(int f) -{ - model::WavesLock lock(model::waves); - const Wave& wave = model::get(model::waves, waveId); - - if (f >= wave.getSize()) - f = wave.getSize() - 1; - else - if (f <= begin) - f = begin + 1; - - end = f;} - - -/* -------------------------------------------------------------------------- */ - - -int SampleChannel::getBegin() const { return begin; } -int SampleChannel::getEnd() const { return end; } - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::setPitch(float v) -{ - if (v > G_MAX_PITCH) - pitch = G_MAX_PITCH; - else - if (v < G_MIN_PITCH) - pitch = G_MIN_PITCH; - else - pitch = v; - -// ???? /* if status is off don't slide between frequencies */ -// ???? -// ???? if (status & (STATUS_OFF | STATUS_WAIT)) -// ???? src_set_ratio(rsmp_state, 1/pitch); -} - - -float SampleChannel::getPitch() const { return pitch; } - - -/* -------------------------------------------------------------------------- */ - - -int SampleChannel::getPosition() const -{ - if (playStatus != ChannelStatus::EMPTY && - playStatus != ChannelStatus::MISSING && - playStatus != ChannelStatus::OFF) - return tracker - begin; - else - return -1; -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::empty() -{ - playStatus = ChannelStatus::EMPTY; - begin = 0; - end = 0; - tracker = 0; - volume = G_DEFAULT_VOL; - hasActions = false; - hasWave = false; - waveId = 0; - sendMidiLstatus(); -} - - -/* -------------------------------------------------------------------------- */ - - -void SampleChannel::pushWave(ID wid, Frame size) -{ - playStatus = ChannelStatus::OFF; - waveId = wid; - begin = 0; - end = size; - tracker = 0; - hasWave = true; - sendMidiLstatus(); -} - - -void SampleChannel::popWave() -{ - playStatus = ChannelStatus::OFF; - waveId = 0; - begin = 0; - end = 0; - tracker = 0; - hasWave = false; - sendMidiLstatus(); -} - - -/* -------------------------------------------------------------------------- */ - - -std::string SampleChannel::getSamplePath() const -{ - if (!hasWave) - return ""; - - model::WavesLock wl(model::waves); - return model::get(model::waves, waveId).getPath(); -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::canInputRec() const -{ - return !hasWave && armed == true; -} - - -/* -------------------------------------------------------------------------- */ - - -int SampleChannel::fillBuffer(AudioBuffer& dest, int start, int offset) -{ - assert(offset < dest.countFrames()); - - if (pitch == 1.0) return fillBufferCopy(dest, start, offset); - else return fillBufferResampled(dest, start, offset); -} - - -/* -------------------------------------------------------------------------- */ - - -int SampleChannel::fillBufferResampled(AudioBuffer& dest, int start, int offset) -{ - model::WavesLock lock(model::waves); - const Wave& wave = model::get(model::waves, waveId); - - rsmp_data.data_in = wave.getFrame(start); // Source data - rsmp_data.input_frames = end - start; // How many readable frames - rsmp_data.data_out = dest[offset]; // Destination (processed data) - rsmp_data.output_frames = dest.countFrames() - offset; // How many frames to process - rsmp_data.end_of_input = false; - rsmp_data.src_ratio = 1 / pitch; - - src_process(rsmp_state, &rsmp_data); - - return rsmp_data.input_frames_used; // Returns used frames -} - -/* -------------------------------------------------------------------------- */ - - -int SampleChannel::fillBufferCopy(AudioBuffer& dest, int start, int offset) -{ - model::WavesLock lock(model::waves); - const Wave& wave = model::get(model::waves, waveId); - - int used = dest.countFrames() - offset; - if (used > wave.getSize() - start) - used = wave.getSize() - start; - - dest.copyData(wave.getFrame(start), used, offset); - - return used; -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::isAnyLoopMode() const -{ - return mode == ChannelMode::LOOP_BASIC || - mode == ChannelMode::LOOP_ONCE || - mode == ChannelMode::LOOP_REPEAT || - mode == ChannelMode::LOOP_ONCE_BAR; -} - - -bool SampleChannel::isAnySingleMode() const -{ - return !isAnyLoopMode(); -} - - -/* -------------------------------------------------------------------------- */ - - -bool SampleChannel::isOnLastFrame() const -{ - return tracker >= end; -} - -}} // giada::m:: diff --git a/src/core/channels/sampleChannel.h b/src/core/channels/sampleChannel.h deleted file mode 100644 index 21f8388..0000000 --- a/src/core/channels/sampleChannel.h +++ /dev/null @@ -1,175 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_SAMPLE_CHANNEL_H -#define G_SAMPLE_CHANNEL_H - - -#include -#include -#include -#include "core/types.h" -#include "core/channels/channel.h" - - -namespace giada { -namespace m -{ -class Wave; - -class SampleChannel : public Channel -{ -public: - - SampleChannel(bool inputMonitor, int bufferSize, ID columnId, ID id); - SampleChannel(const SampleChannel& o); - SampleChannel(const patch::Channel& p, int bufferSize); - ~SampleChannel(); - - SampleChannel* clone() const override; - void parseEvents(mixer::FrameEvents fe) override; - void render(AudioBuffer& out, const AudioBuffer& in, AudioBuffer& inToOut, - bool audible, bool running) override; - - void start(int frame, bool doQuantize, int velocity) override; - void stop() override; - void kill(int frame) override; - bool recordStart(bool canQuantize) override; - bool recordKill() override; - void recordStop() override; - void setMute(bool value) override; - void setSolo(bool value) override; - void startReadingActions(bool treatRecsAsLoops, bool recsStopOnChanHalt) override; - void stopReadingActions(bool running, bool treatRecsAsLoops, - bool recsStopOnChanHalt) override; - void empty() override; - void stopBySeq(bool chansStopOnSeqHalt) override; - void rewindBySeq() override; - void stopInputRec(int globalFrame) override; - bool canInputRec() const override; - bool hasLogicalData() const override; - bool hasEditedData() const override; - bool hasData() const override; - - int getBegin() const; - int getEnd() const; - float getPitch() const; - bool isAnyLoopMode() const; - bool isAnySingleMode() const; - bool isOnLastFrame() const; - std::string getSamplePath() const; - - /* getPosition - Returns the position of an active sample. If EMPTY o MISSING returns -1. */ - - int getPosition() const; - - /* fillBuffer - Fills 'dest' buffer at point 'offset' with Wave data taken from 'start'. - Returns how many frames have been used from the original Wave data. It also - resamples data if pitch != 1.0f. */ - - int fillBuffer(AudioBuffer& dest, int start, int offset); - - /* pushWave - Adds a new wave to this channel. */ - - void pushWave(ID waveId, Frame waveSize); - void popWave(); - - void setPitch(float v); - void setBegin(int f); - void setEnd(int f); - - void setReadActions(bool v, bool recsStopOnChanHalt); - - /* bufferPreview - Extra buffer for audio preview. */ - - AudioBuffer bufferPreview; - - /* hasWave - Tells if a wave is linked to this channel. */ - /* TODO - useless: check if waveId != 0 */ - - bool hasWave; - - /* waveId - ID of a Wave object. Might be useless if hasWave == false. */ - - ID waveId; - - int shift; - ChannelMode mode; - bool quantizing; // quantization in progress - bool inputMonitor; - float pitch; - - Frame tracker; // chan position - Frame trackerPreview; // chan position for audio preview - - /* begin, end - Begin/end point to read wave data from/to. */ - - Frame begin; - Frame end; - - /* midiIn* - MIDI input parameters. */ - - bool midiInVeloAsVol; - uint32_t midiInReadActions; - uint32_t midiInPitch; - - /* bufferOffset - Offset used while filling the internal buffer with audio data. Value is - greater than zero on start sample. */ - - Frame bufferOffset; - - /* rewinding - Tells whether a rewind event is taking place. Used to fill the audio - buffer twice. */ - - bool rewinding; - -private: - - /* rsmp_state, rsmp_data - Structs from libsamplerate. */ - - SRC_STATE* rsmp_state; - SRC_DATA rsmp_data; - - int fillBufferResampled(AudioBuffer& dest, int start, int offset); - int fillBufferCopy (AudioBuffer& dest, int start, int offset); -}; - -}} // giada::m:: - - -#endif diff --git a/src/core/channels/sampleChannelProc.cpp b/src/core/channels/sampleChannelProc.cpp deleted file mode 100644 index cd8b193..0000000 --- a/src/core/channels/sampleChannelProc.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#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; ibuffer.countFrames(); i++) - for (int j=0; jbuffer.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; icalcVolumeEnvelope(); - if (!ch->mute) - for (int j=0; jbuffer[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; ibufferPreview[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(velocity, 0, G_MAX_VELOCITY, 0.0, 1.0); - } - - switch (ch->playStatus) { - case ChannelStatus::OFF: - ch->bufferOffset = localFrame; - if (ch->isAnyLoopMode()) { - ch->playStatus = ChannelStatus::WAIT; - ch->sendMidiLstatus(); - } - else { - if (doQuantize) - ch->quantizing = true; - else { - ch->playStatus = ChannelStatus::PLAY; - ch->sendMidiLstatus(); - } - } - break; - - case ChannelStatus::PLAY: - if (ch->mode == ChannelMode::SINGLE_RETRIG) { - if (doQuantize) - ch->quantizing = true; - else - rewind_(ch, localFrame); - } - else - if (ch->isAnyLoopMode() || ch->mode == ChannelMode::SINGLE_ENDLESS) { - ch->playStatus = ChannelStatus::ENDING; - ch->sendMidiLstatus(); - } - else - if (ch->mode == ChannelMode::SINGLE_BASIC) { - rewind_(ch, localFrame); - ch->playStatus = ChannelStatus::OFF; - ch->sendMidiLstatus(); - } - break; - - case ChannelStatus::WAIT: - ch->playStatus = ChannelStatus::OFF; - ch->sendMidiLstatus(); - break; - - case ChannelStatus::ENDING: - ch->playStatus = ChannelStatus::PLAY; - ch->sendMidiLstatus(); - break; - - default: break; - } -} - - -/* -------------------------------------------------------------------------- */ - - -void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in, - AudioBuffer& inToOut, bool audible, bool running) -{ - fillBuffer_(ch, running); - - if (audible) - processIO_(ch, out, in, running); - - if (ch->isPreview()) - processPreview_(ch, out); -} - - -/* -------------------------------------------------------------------------- */ - - -void parseEvents(SampleChannel* ch, mixer::FrameEvents fe) -{ - if (!ch->hasData()) - return; - - /* Quantize only if is single mode and in quantizer-wait mode and a - quantizer step has passed. */ - - if (ch->isAnySingleMode() && ch->quantizing && fe.quantoPassed) - quantize_(ch, fe.frameLocal); - if (fe.onBar) - onBar_(ch, fe.frameLocal); - if (fe.onFirstBeat) - onFirstBeat_(ch, fe.frameLocal); -} -}}}; diff --git a/src/core/channels/sampleChannelProc.h b/src/core/channels/sampleChannelProc.h deleted file mode 100644 index 075f125..0000000 --- a/src/core/channels/sampleChannelProc.h +++ /dev/null @@ -1,89 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_SAMPLE_CHANNEL_PROC_H -#define G_SAMPLE_CHANNEL_PROC_H - - -#include "core/mixer.h" -#include "core/audioBuffer.h" -#include "core/types.h" - - -namespace giada { -namespace m -{ -class SampleChannel; - -namespace sampleChannelProc -{ -/**/ -void render(SampleChannel* ch, AudioBuffer& out, const AudioBuffer& in, - AudioBuffer& inToOut, bool audible, bool running); - -/* parseEvents -Parses events gathered by Mixer::masterPlay(). */ - -void parseEvents(SampleChannel* ch, mixer::FrameEvents ev); - -/* kill -Stops a channel abruptly. */ - -void kill(SampleChannel* ch, int localFrame); - -/* stop -Stops a channel normally (via key or MIDI). */ - -void stop(SampleChannel* ch); - -/* stopInputRec -Prepare a channel for playing when the input recording is done. */ - -void stopInputRec(SampleChannel* ch, int globalFrame); - -/* stopBySeq -Stops a channel when the stop button on main transport is pressed. */ - -void stopBySeq(SampleChannel* ch, bool chansStopOnSeqHalt); - -/* rewind -Rewinds channel when rewind button on main transport is pressed. */ - -void rewindBySeq(SampleChannel* ch); - -/* start -Starts a channel. doQuantize = false (don't quantize) when Mixer is reading -actions from Recorder. */ - -void start(SampleChannel* ch, int localFrame, bool doQuantize, int velocity); - -void setMute(SampleChannel* ch, bool value); -void setSolo(SampleChannel* ch, bool value); -}}}; - - -#endif diff --git a/src/core/channels/sampleChannelRec.cpp b/src/core/channels/sampleChannelRec.cpp deleted file mode 100644 index 1b74a61..0000000 --- a/src/core/channels/sampleChannelRec.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#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(a1.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0); - double vf2 = u::math::map(a2.event.getVelocity(), 0, G_MAX_VELOCITY, 0, 1.0); - - ch->volume_i = vf1; - ch->volume_d = a2.frame == a1.frame ? 0 : (vf2 - vf1) / (a2.frame - a1.frame); -} - - -/* -------------------------------------------------------------------------- */ - - -void parseAction_(SampleChannel* ch, const Action& a, int localFrame, int globalFrame) -{ - switch (a.event.getStatus()) { - case MidiEvent::NOTE_ON: - if (ch->isAnySingleMode()) - ch->start(localFrame, /*quantize=*/false, /*velocity=*/0); - break; - case MidiEvent::NOTE_OFF: - if (ch->isAnySingleMode()) - ch->stop(); - break; - case MidiEvent::NOTE_KILL: - if (ch->isAnySingleMode()) - ch->kill(localFrame); - break; - case MidiEvent::ENVELOPE: - calcVolumeEnv_(ch, a); - break; - } -} - - -/* -------------------------------------------------------------------------- */ - - -void recordKeyPressAction_(SampleChannel* ch) -{ - if (!recorderCanRec_(ch)) - return; - - /* Disable reading actions while recording SINGLE_PRESS mode. Don't let - existing actions interfere with the current one being recorded. */ - - if (ch->mode == ChannelMode::SINGLE_PRESS) - ch->readActions = false; - - recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_ON, 0, 0)); - ch->hasActions = true; -} - - -/* -------------------------------------------------------------------------- */ - - -void quantize_(SampleChannel* ch, bool quantoPassed) -{ - /* Skip if in loop mode or not in a quantization stage. Otherwise the - quantization wait has expired: record the keypress. */ - - if (!ch->isAnyLoopMode() && ch->quantizing && quantoPassed && ch->playStatus == ChannelStatus::PLAY) { - ch->quantizing = false; - recordKeyPressAction_(ch); - } -} -}; // {anonymous} - - -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ - - -void parseEvents(SampleChannel* ch, mixer::FrameEvents fe) -{ - if (!ch->hasWave) - return; - quantize_(ch, fe.quantoPassed); - if (fe.onFirstBeat) - onFirstBeat_(ch, conf::conf.recsStopOnChanHalt); - if (ch->readActions && fe.actions != nullptr) - for (const Action& action : *fe.actions) - if (action.channelId == ch->id) - parseAction_(ch, action, fe.frameLocal, fe.frameGlobal); -} - - -/* -------------------------------------------------------------------------- */ - - -bool recordStart(SampleChannel* ch, bool canQuantize) -{ - /* Record a 'start' event if the quantizer is off, otherwise let mixer to - handle it when a quantoWait has passed (see quantize_()). Also skip if - channel is in any loop mode, where KEYPRESS and KEYREL are meaningless. */ - - if (!canQuantize && !ch->isAnyLoopMode() && recorderCanRec_(ch)) - recordKeyPressAction_(ch); - return true; -} - - -/* -------------------------------------------------------------------------- */ - - -bool recordKill(SampleChannel* ch) -{ - /* Don't record NOTE_KILL actions for LOOP channels. */ - if (recorderCanRec_(ch) && !ch->isAnyLoopMode()) { - recorder::rec(ch->id, clock::getCurrentFrame(), MidiEvent(MidiEvent::NOTE_KILL, 0, 0)); - ch->hasActions = true; - } - return true; -} - - -/* -------------------------------------------------------------------------- */ - - -void recordStop(SampleChannel* ch) -{ - /* Record a stop event only if channel is SINGLE_PRESS. For any other mode - the stop event is meaningless. */ - if (recorderCanRec_(ch) && ch->mode == ChannelMode::SINGLE_PRESS) - recorderHandler::liveRec(ch->id, MidiEvent(MidiEvent::NOTE_OFF, 0, 0)); -} - - -/* -------------------------------------------------------------------------- */ - - -void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt) -{ - ch->readActions = v; - if (!v && recsStopOnChanHalt) - ch->kill(0); // FIXME - wrong frame value -} - - -/* -------------------------------------------------------------------------- */ - - -void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, bool recsStopOnChanHalt) -{ - if (treatRecsAsLoops) - ch->recStatus = ChannelStatus::WAIT; - else - setReadActions(ch, true, recsStopOnChanHalt); -} - - -/* -------------------------------------------------------------------------- */ - - -void stopReadingActions(SampleChannel* ch, bool isClockRunning, bool treatRecsAsLoops, - bool recsStopOnChanHalt) -{ - /* First of all, if the clock is not running just stop and disable everything. - Then if "treatRecsAsLoop" wait until the sequencer reaches beat 0, so put the - channel in REC_ENDING status. */ - - if (!isClockRunning) { - ch->recStatus = ChannelStatus::OFF; - setReadActions(ch, false, false); - } - else - if (ch->recStatus == ChannelStatus::WAIT) - ch->recStatus = ChannelStatus::OFF; - else - if (ch->recStatus == ChannelStatus::ENDING) - ch->recStatus = ChannelStatus::PLAY; - else - if (treatRecsAsLoops) - ch->recStatus = ChannelStatus::ENDING; - else - setReadActions(ch, false, recsStopOnChanHalt); -} -}}}; diff --git a/src/core/channels/sampleChannelRec.h b/src/core/channels/sampleChannelRec.h deleted file mode 100644 index 29a9240..0000000 --- a/src/core/channels/sampleChannelRec.h +++ /dev/null @@ -1,71 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef G_SAMPLE_CHANNEL_REC_H -#define G_SAMPLE_CHANNEL_REC_H - - -namespace giada { -namespace m -{ -class SampleChannel; - -namespace sampleChannelRec -{ -void parseEvents(SampleChannel* ch, mixer::FrameEvents fe); - -/* recordStart -Records a 'start' action if capable of. Returns true if a start() call can -be performed. */ - -bool recordStart(SampleChannel* ch, bool doQuantize); - -/* recordKill -Records a 'kill' action if capable of. Returns true if a kill() call can -be performed. */ - -bool recordKill(SampleChannel* ch); - -/* recordStop -Ends overdub mode SINGLE_PRESS channels. */ - -void recordStop(SampleChannel* ch); - -/* setReadActions -If enabled (v == true), Recorder will read actions from channel 'ch'. If -recsStopOnChanHalt == true and v == false, will also kill the channel. */ - -void setReadActions(SampleChannel* ch, bool v, bool recsStopOnChanHalt); - -void startReadingActions(SampleChannel* ch, bool treatRecsAsLoops, - bool recsStopOnChanHalt); -void stopReadingActions(SampleChannel* ch, bool isClockRunning, - bool treatRecsAsLoops, bool recsStopOnChanHalt); -}}}; - - -#endif \ No newline at end of file diff --git a/src/core/channels/sampleController.cpp b/src/core/channels/sampleController.cpp new file mode 100644 index 0000000..0dc1281 --- /dev/null +++ b/src/core/channels/sampleController.cpp @@ -0,0 +1,419 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#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 block(clock::getCurrentFrame(), clock::getCurrentFrame() + bufferSize); + m_samplePlayerState->quantizer.advance(block, clock::getQuantizerStep()); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::press(Frame localFrame, int velocity, bool manual) const +{ + ChannelStatus playStatus = m_channelState->playStatus.load(); + SamplePlayerMode mode = m_samplePlayerState->mode.load(); + bool isLoop = m_samplePlayerState->isAnyLoopMode(); + + switch (playStatus) { + case ChannelStatus::OFF: + playStatus = pressWhileOff(localFrame, velocity, isLoop, manual); break; + + case ChannelStatus::PLAY: + playStatus = pressWhilePlay(localFrame, mode, isLoop, manual); break; + + case ChannelStatus::WAIT: + playStatus = ChannelStatus::OFF; break; + + case ChannelStatus::ENDING: + playStatus = ChannelStatus::PLAY; break; + + default: break; + } + + m_channelState->playStatus.store(playStatus); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::release(Frame localFrame) const +{ + /* Key release is meaningful only for SINGLE_PRESS modes. */ + + if (m_samplePlayerState->mode.load() != SamplePlayerMode::SINGLE_PRESS) + return; + + /* Kill it if it's SINGLE_PRESS is playing. Otherwise there might be a + quantization step in progress that would play the channel later on: + disable it. */ + + if (m_channelState->playStatus.load() == ChannelStatus::PLAY) + kill(localFrame); + else + if (m_samplePlayerState->quantizer.isTriggered()) + m_samplePlayerState->quantizer.clear(); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::kill(Frame localFrame) const +{ + m_channelState->playStatus.store(ChannelStatus::OFF); + m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load()); + m_samplePlayerState->quantizing = false; + + /* Clear data in range [localFrame, (buffer.size)) if the kill event occurs + in the middle of the buffer. */ + + if (localFrame != 0) + m_channelState->buffer.clear(localFrame); +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::rewind(Frame localFrame) const +{ + /* Quantization stops on rewind. */ + + m_samplePlayerState->quantizer.clear(); + + if (m_channelState->isPlaying()) { + m_samplePlayerState->rewinding = true; + m_samplePlayerState->offset = localFrame; + } + else + m_samplePlayerState->tracker.store(m_samplePlayerState->begin.load()); +} + + +/* -------------------------------------------------------------------------- */ + + +ChannelStatus SampleController::pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const +{ + m_samplePlayerState->offset = localFrame; + + if (isLoop) + return ChannelStatus::WAIT; + + if (m_samplePlayerState->velocityAsVol.load() == true) + m_channelState->volume_i = u::math::map(velocity, G_MAX_VELOCITY, G_MAX_VOLUME); + + if (clock::canQuantize() && manual) { // manual: don't quantize recorded actions + m_samplePlayerState->quantizer.trigger(Q_ACTION_PLAY); + return ChannelStatus::OFF; + } + else + return ChannelStatus::PLAY; +} + + +ChannelStatus SampleController::pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const +{ + if (mode == SamplePlayerMode::SINGLE_RETRIG) { + if (clock::canQuantize() && manual) // manual: don't quantize recorded actions + m_samplePlayerState->quantizer.trigger(Q_ACTION_REWIND); + else + rewind(localFrame); + return ChannelStatus::PLAY; + } + + if (isLoop || mode == SamplePlayerMode::SINGLE_ENDLESS) + return ChannelStatus::ENDING; + + if (mode == SamplePlayerMode::SINGLE_BASIC) { + rewind(localFrame); + return ChannelStatus::OFF; + } + + return ChannelStatus::OFF; +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::onBar(Frame localFrame) const +{ + G_DEBUG("onBar ch=" << m_channelState->id); + + ChannelStatus playStatus = m_channelState->playStatus.load(); + SamplePlayerMode mode = m_samplePlayerState->mode.load(); + + if (playStatus == ChannelStatus::PLAY && mode == SamplePlayerMode::LOOP_REPEAT) + rewind(localFrame); + else + if (playStatus == ChannelStatus::WAIT && mode == SamplePlayerMode::LOOP_ONCE_BAR) { + m_channelState->playStatus.store(ChannelStatus::PLAY); + m_samplePlayerState->offset = localFrame; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::onFirstBeat(Frame localFrame) const +{ +G_DEBUG("onFirstBeat ch=" << m_channelState->id << ", localFrame=" << localFrame); + + ChannelStatus playStatus = m_channelState->playStatus.load(); + bool isLoop = m_samplePlayerState->isAnyLoopMode(); + + switch (playStatus) { + + case ChannelStatus::PLAY: + if (isLoop) + rewind(localFrame); + break; + + case ChannelStatus::WAIT: + m_samplePlayerState->offset = localFrame; + m_channelState->playStatus.store(ChannelStatus::PLAY); + break; + + case ChannelStatus::ENDING: + if (isLoop) + kill(localFrame); + break; + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::onStopBySeq() const +{ + G_DEBUG("onStopBySeq ch=" << m_channelState->id); + + ChannelStatus playStatus = m_channelState->playStatus.load(); + bool isReadingActions = m_channelState->readActions.load() == true; + bool isLoop = m_samplePlayerState->isAnyLoopMode(); + + switch (playStatus) { + + case ChannelStatus::WAIT: + /* Loop-mode channels in wait status get stopped right away. */ + if (isLoop) + m_channelState->playStatus.store(ChannelStatus::OFF); + break; + + case ChannelStatus::PLAY: + /* Kill samples if a) chansStopOnSeqHalt == true (run the sample to end + otherwise); b) when a channel is reading (and playing) actions. */ + if (conf::conf.chansStopOnSeqHalt) + if (isLoop || isReadingActions) + kill(0); + break; + + default: break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::parseAction(const Action& a, Frame localFrame) const +{ + bool isLoop = m_samplePlayerState->isAnyLoopMode(); + + switch (a.event.getStatus()) { + case MidiEvent::NOTE_ON: + if (!isLoop) + press(localFrame, /*velocity=*/G_MAX_VELOCITY, /*manual=*/false); + break; + case MidiEvent::NOTE_OFF: + if (!isLoop) + release(localFrame); + break; + case MidiEvent::NOTE_KILL: + if (!isLoop) + kill(localFrame); + break; + case MidiEvent::ENVELOPE: + //calcVolumeEnv_(ch, a); TODO + break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SampleController::toggleReadActions() const +{ + ChannelStatus recStatus = m_channelState->recStatus.load(); + if (clock::isRunning() && recStatus == ChannelStatus::PLAY && !conf::conf.treatRecsAsLoops) + kill(0); +} +}} // giada::m:: diff --git a/src/core/channels/sampleController.h b/src/core/channels/sampleController.h new file mode 100644 index 0000000..7a38dd2 --- /dev/null +++ b/src/core/channels/sampleController.h @@ -0,0 +1,76 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_SAMPLE_CONTROLLER_H +#define G_CHANNEL_SAMPLE_CONTROLLER_H + + +#include "core/types.h" + + +namespace giada { +namespace m +{ +namespace mixer +{ +struct Event; +} +struct SamplePlayerState; +class SampleController +{ +public: + + SampleController(ChannelState*, SamplePlayerState*); + SampleController(const SampleController&, ChannelState* c=nullptr, SamplePlayerState* s=nullptr); + + void parse(const mixer::Event& e) const; + void onLastFrame() const; + void advance(Frame bufferSize) const; + +private: + + void press(Frame localFrame, int velocity, bool manual) const; + void release(Frame localFrame) const; + void kill(Frame localFrame) const; + void rewind(Frame localFrame) const; + + ChannelStatus pressWhileOff(Frame localFrame, int velocity, bool isLoop, bool manual) const; + ChannelStatus pressWhilePlay(Frame localFrame, SamplePlayerMode mode, bool isLoop, bool manual) const; + void toggleReadActions() const; + + void onBar(Frame localFrame) const; + void onFirstBeat(Frame localFrame) const; + void onStopBySeq() const; + void parseAction(const Action& a, Frame localFrame) const; + + ChannelState* m_channelState; + SamplePlayerState* m_samplePlayerState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/samplePlayer.cpp b/src/core/channels/samplePlayer.cpp new file mode 100644 index 0000000..e9b96ac --- /dev/null +++ b/src/core/channels/samplePlayer.cpp @@ -0,0 +1,249 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#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()) +, m_waveId (0) +, m_sampleController(c, state.get()) +, m_channelState (c) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +SamplePlayer::SamplePlayer(const SamplePlayer& o, ChannelState* c) +: state (std::make_unique(*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(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(used / pitch), buffer.countFrames() - 1); + tracker += m_waveReader.fill(buffer, tracker, offset, pitch); + } + } + + state->offset = 0; + state->tracker.store(tracker); +} + + +/* -------------------------------------------------------------------------- */ + + +void SamplePlayer::loadWave(const Wave* w) +{ + m_waveReader.wave = w; + + state->tracker.store(0); + state->shift.store(0); + state->begin.store(0); + + if (w != nullptr) { + m_waveId = w->id; + m_channelState->playStatus.store(ChannelStatus::OFF); + m_channelState->name = w->getBasename(/*ext=*/false); + state->end.store(w->getSize() - 1); + } + else { + m_waveId = 0; + m_channelState->playStatus.store(ChannelStatus::EMPTY); + m_channelState->name = ""; + state->end.store(0); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void SamplePlayer::setWave(const Wave& w) +{ + m_waveReader.wave = &w; + m_waveId = w.id; +} + + +void SamplePlayer::setInvalidWave() +{ + m_waveReader.wave = nullptr; + m_waveId = 0; +} + + +/* -------------------------------------------------------------------------- */ + + +void SamplePlayer::kickIn(Frame f) +{ + assert(hasWave()); + + state->tracker.store(f); + m_channelState->playStatus.store(ChannelStatus::PLAY); +} + + +/* -------------------------------------------------------------------------- */ + + +bool SamplePlayer::shouldLoop() const +{ + ChannelStatus playStatus = m_channelState->playStatus.load(); + SamplePlayerMode mode = state->mode.load(); + + return (mode == SamplePlayerMode::LOOP_BASIC || + mode == SamplePlayerMode::LOOP_REPEAT || + mode == SamplePlayerMode::SINGLE_ENDLESS) && playStatus == ChannelStatus::PLAY; +} + + +/* -------------------------------------------------------------------------- */ + + +bool SamplePlayer::hasWave() const { return m_waveReader.wave != nullptr; } +bool SamplePlayer::hasLogicalWave() const { return hasWave() && m_waveReader.wave->isLogical(); } +bool SamplePlayer::hasEditedWave() const { return hasWave() && m_waveReader.wave->isEdited(); } + + +/* -------------------------------------------------------------------------- */ + + +ID SamplePlayer::getWaveId() const +{ + return m_waveId; +} + + +/* -------------------------------------------------------------------------- */ + + +Frame SamplePlayer::getWaveSize() const +{ + return hasWave() ? m_waveReader.wave->getSize() : 0; +} +}} // giada::m:: diff --git a/src/core/channels/samplePlayer.h b/src/core/channels/samplePlayer.h new file mode 100644 index 0000000..d22af29 --- /dev/null +++ b/src/core/channels/samplePlayer.h @@ -0,0 +1,115 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 state; + +private: + + bool shouldLoop() const; + + ID m_waveId; + + /* m_waveReader + Used to read data from Wave and fill incoming buffer. */ + + WaveReader m_waveReader; + + /* m_sampleController + Managers events for this Sample Player. */ + + SampleController m_sampleController; + + /* m_channelState + Pointer to Channel state. Needed to alter the playStatus status when the + sample is over. */ + + ChannelState* m_channelState; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/state.cpp b/src/core/channels/state.cpp new file mode 100644 index 0000000..18f4aa7 --- /dev/null +++ b/src/core/channels/state.cpp @@ -0,0 +1,325 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "core/patch.h" +#include "state.h" + + +namespace giada { +namespace m +{ +MidiLearnerState::MidiLearnerState() +: enabled (true) +, filter (0) +, keyPress (0x0) +, keyRelease (0x0) +, kill (0x0) +, arm (0x0) +, volume (0x0) +, mute (0x0) +, solo (0x0) +, readActions (0x0) +, pitch (0x0) +{ +} + + +MidiLearnerState::MidiLearnerState(const patch::Channel& p) +: enabled (p.midiIn) +, filter (p.midiInFilter) +, keyPress (p.midiInKeyPress) +, keyRelease (p.midiInKeyRel) +, kill (p.midiInKill) +, arm (p.midiInArm) +, volume (p.midiInVolume) +, mute (p.midiInMute) +, solo (p.midiInSolo) +, readActions (p.midiInReadActions) +, pitch (p.midiInPitch) +{ +} + + +MidiLearnerState::MidiLearnerState(const MidiLearnerState& o) +: enabled (o.enabled.load()) +, filter (o.filter.load()) +, keyPress (o.keyPress.load()) +, keyRelease (o.keyRelease.load()) +, kill (o.kill.load()) +, arm (o.arm.load()) +, volume (o.volume.load()) +, mute (o.mute.load()) +, solo (o.solo.load()) +, readActions (o.readActions.load()) +, pitch (o.pitch.load()) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +bool MidiLearnerState::isAllowed(int c) const +{ + int filter_ = filter.load(); + bool enabled_ = enabled.load(); + + return enabled_ && (filter_ == -1 || filter_ == c); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +MidiLighterState::MidiLighterState() +: enabled(false) +, playing(0x0) +, mute (0x0) +, solo (0x0) +{ +} + + +MidiLighterState::MidiLighterState(const patch::Channel& p) +: enabled(p.midiOutL) +, playing(p.midiOutLplaying) +, mute (p.midiOutLmute) +, solo (p.midiOutLsolo) +{ +} + + +MidiLighterState::MidiLighterState(const MidiLighterState& o) +: enabled(o.enabled.load()) +, playing(o.playing.load()) +, mute (o.mute.load()) +, solo (o.solo.load()) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +MidiSenderState::MidiSenderState() +: enabled(true) +, filter (0) +{ +} + + +MidiSenderState::MidiSenderState(const patch::Channel& p) +: enabled(p.midiOut) +, filter (p.midiOutChan) +{ +} + + +MidiSenderState::MidiSenderState(const MidiSenderState& o) +: enabled(o.enabled.load()) +, filter (o.filter.load()) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +MidiReceiverState::MidiReceiverState() +{ + midiBuffer.ensureSize(G_DEFAULT_VST_MIDIBUFFER_SIZE); +} + +#endif + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +SamplePlayerState::SamplePlayerState() +: tracker (0) +, pitch (G_DEFAULT_PITCH) +, mode (SamplePlayerMode::SINGLE_BASIC) +, velocityAsVol(false) +, rewinding (false) +, quantizing (false) +, offset (0) +{ +} + + +SamplePlayerState::SamplePlayerState(const SamplePlayerState& o) +: tracker (o.tracker.load()) +, pitch (o.pitch.load()) +, mode (o.mode.load()) +, shift (o.shift.load()) +, begin (o.begin.load()) +, end (o.end.load()) +, velocityAsVol(o.velocityAsVol.load()) +, rewinding (o.rewinding) +, quantizing (o.quantizing) +, offset (o.offset) +, quantizer (o.quantizer) +{ +} + + +SamplePlayerState::SamplePlayerState(const patch::Channel& p) +: tracker (0) +, pitch (p.pitch) +, mode (p.mode) +, shift (p.shift) +, begin (p.begin) +, end (p.end) +, velocityAsVol(p.midiInVeloAsVol) +, rewinding (false) +, quantizing (false) +, offset (0) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +bool SamplePlayerState::isAnyLoopMode() const +{ + SamplePlayerMode m = mode.load(); + + return m == SamplePlayerMode::LOOP_BASIC || + m == SamplePlayerMode::LOOP_ONCE || + m == SamplePlayerMode::LOOP_REPEAT || + m == SamplePlayerMode::LOOP_ONCE_BAR; +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +AudioReceiverState::AudioReceiverState() +: inputMonitor(false) +{ +} + + +AudioReceiverState::AudioReceiverState(const patch::Channel& p) +: inputMonitor(p.inputMonitor) +{ +} + + +AudioReceiverState::AudioReceiverState(const AudioReceiverState& o) +: inputMonitor(o.inputMonitor.load()) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +ChannelState::ChannelState(ID id, Frame bufferSize) +: id (id) +, playStatus (ChannelStatus::OFF) +, recStatus (ChannelStatus::OFF) +, volume (G_DEFAULT_VOL) +, pan (G_DEFAULT_PAN) +, mute (false) +, solo (false) +, armed (false) +, key (0) +, readActions(true) +, buffer (bufferSize, G_MAX_IO_CHANS) +, hasActions (false) +, height (G_GUI_UNIT) +, volume_i (1.0f) +{ +} + + +ChannelState::ChannelState(const ChannelState& o) +: id (o.id) +, playStatus (o.playStatus.load()) +, recStatus (o.recStatus.load()) +, volume (o.volume.load()) +, pan (o.pan.load()) +, mute (o.mute.load()) +, solo (o.solo.load()) +, armed (o.armed.load()) +, key (o.key.load()) +, readActions(o.readActions.load()) +, buffer (o.buffer) +, hasActions (o.hasActions) +, name (o.name) +, height (o.height) +, volume_i (o.volume_i) +{ +} + + +ChannelState::ChannelState(const patch::Channel& p, Frame bufferSize) +: id (p.id) +, playStatus (ChannelStatus::OFF) +, recStatus (ChannelStatus::OFF) +, volume (p.volume) +, pan (p.pan) +, mute (p.mute) +, solo (p.solo) +, armed (p.armed) +, key (p.key) +, readActions(p.readActions) +, buffer (bufferSize, G_MAX_IO_CHANS) +, hasActions (p.hasActions) +, name (p.name) +, height (p.height) +, volume_i (1.0f) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +bool ChannelState::isPlaying() const +{ + ChannelStatus s = playStatus.load(); + return s == ChannelStatus::PLAY || s == ChannelStatus::ENDING; +} +}} // giada::m:: diff --git a/src/core/channels/state.h b/src/core/channels/state.h new file mode 100644 index 0000000..e7e8576 --- /dev/null +++ b/src/core/channels/state.h @@ -0,0 +1,231 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_STATE_H +#define G_CHANNEL_STATE_H + + +#include +#include +#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 enabled; + + /* filter + Which MIDI channel should be filtered out when receiving MIDI messages. + If -1 means 'all'. */ + + std::atomic filter; + + /* MIDI learning fields. */ + + std::atomic keyPress; + std::atomic keyRelease; + std::atomic kill; + std::atomic arm; + std::atomic volume; + std::atomic mute; + std::atomic solo; + std::atomic readActions; // Sample Channels only + std::atomic 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 enabled; + + /* MIDI learning fields for MIDI ligthing. */ + + std::atomic playing; + std::atomic mute; + std::atomic solo; +}; + + +/* -------------------------------------------------------------------------- */ + + +struct MidiSenderState +{ + MidiSenderState(); + MidiSenderState(const patch::Channel& p); + MidiSenderState(const MidiSenderState& o); + + /* enabled + Tells whether MIDI output is enabled or not. */ + + std::atomic enabled; + + /* filter + Which MIDI channel data should be sent to. */ + + std::atomic 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 tracker; + std::atomic pitch; + std::atomic mode; + std::atomic shift; + std::atomic begin; + std::atomic end; + + /* velocityAsVol + Velocity drives volume. */ + + std::atomic velocityAsVol; + + bool rewinding; + bool quantizing; + Frame offset; + Quantizer quantizer; +}; + + +/* -------------------------------------------------------------------------- */ + + +struct AudioReceiverState +{ + AudioReceiverState(); + AudioReceiverState(const patch::Channel& p); + AudioReceiverState(const AudioReceiverState& o); + + std::atomic 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 playStatus; + std::atomic recStatus; + std::atomic volume; + std::atomic pan; + std::atomic mute; + std::atomic solo; + std::atomic armed; + std::atomic key; + std::atomic readActions; + + /* buffer (internal) + Working buffer for internal processing. */ + + AudioBuffer buffer; + + bool hasActions; + std::string name; + Pixel height; + + /* volume_i (internal) + Internal volume used for volume automation and velocity-drives-volume mode + on Sample Channels. */ + + float volume_i; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/channels/waveReader.cpp b/src/core/channels/waveReader.cpp new file mode 100644 index 0000000..b96c71d --- /dev/null +++ b/src/core/channels/waveReader.cpp @@ -0,0 +1,176 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include +#include "core/const.h" +#include "core/model/model.h" +#include "core/audioBuffer.h" +#include "core/wave.h" +#include "utils/log.h" +#include "waveReader.h" + + +namespace giada { +namespace m +{ +WaveReader::WaveReader() +: wave (nullptr), + m_srcState(nullptr) +{ + allocateSrc(); +} + +/* -------------------------------------------------------------------------- */ + + +WaveReader::WaveReader(const WaveReader& o) +: wave (o.wave), + m_srcState(nullptr) +{ + allocateSrc(); +} + + +/* -------------------------------------------------------------------------- */ + + +WaveReader::WaveReader(WaveReader&& o) +: wave (o.wave), + m_srcState(nullptr) +{ + moveSrc(&o.m_srcState); +} + + +/* -------------------------------------------------------------------------- */ + + +WaveReader& WaveReader::operator=(const WaveReader& o) +{ + if (this == &o) return *this; + wave = o.wave; + allocateSrc(); + return *this; +} + + +WaveReader& WaveReader::operator=(WaveReader&& o) +{ + if (this == &o) return *this; + wave = o.wave; + moveSrc(&o.m_srcState); + return *this; +} + + +/* -------------------------------------------------------------------------- */ + + +WaveReader::~WaveReader() +{ + if (m_srcState != nullptr) + src_delete(m_srcState); +} + + +/* -------------------------------------------------------------------------- */ + + +Frame WaveReader::fill(AudioBuffer& out, Frame start, Frame offset, float pitch) const +{ + assert(wave != nullptr); + assert(start >= 0); + assert(offset < out.countFrames()); + + model::WavesLock l(model::waves); // TODO dependency + + if (pitch == 1.0) return fillCopy(out, start, offset); + else return fillResampled(out, start, offset, pitch); +} + + +/* -------------------------------------------------------------------------- */ + + +Frame WaveReader::fillResampled(AudioBuffer& dest, Frame start, Frame offset, float pitch) const +{ + SRC_DATA srcData; + + srcData.data_in = wave->getFrame(start); // Source data + srcData.input_frames = wave->getSize() - start; // How many readable frames + srcData.data_out = dest[offset]; // Destination (processed data) + srcData.output_frames = dest.countFrames() - offset; // How many frames to process + srcData.end_of_input = false; + srcData.src_ratio = 1 / pitch; + + src_process(m_srcState, &srcData); + + return srcData.input_frames_used; +} + + +/* -------------------------------------------------------------------------- */ + + +Frame WaveReader::fillCopy(AudioBuffer& dest, Frame start, Frame offset) const +{ + Frame used = dest.countFrames() - offset; + if (used > wave->getSize() - start) + used = wave->getSize() - start; + + dest.copyData(wave->getFrame(start), used, offset); + + return used; +} + + +/* -------------------------------------------------------------------------- */ + + +void WaveReader::allocateSrc() +{ + m_srcState = src_new(SRC_LINEAR, G_MAX_IO_CHANS, nullptr); + if (m_srcState == nullptr) { + u::log::print("[WaveReader] unable to allocate memory for SRC_STATE!\n"); + throw std::bad_alloc(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void WaveReader::moveSrc(SRC_STATE** other) +{ + if (m_srcState != nullptr) + src_delete(m_srcState); + m_srcState = *other; + *other = nullptr; +} +}} // giada::m:: diff --git a/src/core/channels/waveReader.h b/src/core/channels/waveReader.h new file mode 100644 index 0000000..1d6bc44 --- /dev/null +++ b/src/core/channels/waveReader.h @@ -0,0 +1,74 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_CHANNEL_WAVE_READER_H +#define G_CHANNEL_WAVE_READER_H + + +#include +#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 diff --git a/src/core/clock.cpp b/src/core/clock.cpp index 5a84d3a..3b6b979 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -28,13 +28,15 @@ #include #include #include "glue/main.h" -#include "utils/math.h" +#include "glue/events.h" #include "core/model/model.h" #include "core/conf.h" +#include "core/sequencer.h" #include "core/const.h" #include "core/kernelAudio.h" #include "core/mixerHandler.h" #include "core/kernelMidi.h" +#include "utils/math.h" #include "clock.h" @@ -48,7 +50,13 @@ std::atomic currentFrameWait_(0); std::atomic currentFrame_(0); std::atomic currentBeat_(0); -int quanto_ = 1; // Quantizer step +/* quantizerStep_ +Tells how many frames to wait to perform a quantized action. */ + +int quantizerStep_ = 1; + +/* midiTC* +MIDI timecode variables. */ int midiTCrate_ = 0; // Send MTC data every midiTCrate_ frames int midiTCframes_ = 0; @@ -56,7 +64,6 @@ int midiTCseconds_ = 0; int midiTCminutes_ = 0; int midiTChours_ = 0; - #if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) kernelAudio::JackState jackStatePrev_; #endif @@ -75,7 +82,7 @@ void recomputeFrames_(model::Clock& c) c.framesInSeq = c.framesInBeat * G_MAX_BEATS; if (c.quantize != 0) - quanto_ = c.framesInBeat / c.quantize; + quantizerStep_ = c.framesInBeat / c.quantize; } }; // {anonymous} @@ -131,7 +138,7 @@ bool isActive() bool quantoHasPassed() { - return currentFrame_.load() % quanto_ == 0; + return clock::getQuantizerValue() != 0 && currentFrame_.load() % quantizerStep_ == 0; } @@ -172,16 +179,7 @@ bool isOnFirstBeat() void setBpm(float b) { -#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) - - /* Can't change bpm from within Giada when using JACK. */ - - if (m::kernelAudio::getAPI() == G_SYS_API_JACK) - return; - -#endif - - b = u::math::bound(b, G_MIN_BPM, G_MAX_BPM); + b = std::clamp(b, G_MIN_BPM, G_MAX_BPM); model::onSwap(model::clock, [&](model::Clock& c) { @@ -193,8 +191,8 @@ void setBpm(float b) void setBeats(int newBeats, int newBars) { - newBeats = u::math::bound(newBeats, 1, G_MAX_BEATS); - newBars = u::math::bound(newBars, 1, newBeats); // Bars cannot be greater than beats + newBeats = std::clamp(newBeats, 1, G_MAX_BEATS); + newBars = std::clamp(newBars, 1, newBeats); // Bars cannot be greater than beats model::onSwap(model::clock, [&](model::Clock& c) { @@ -380,6 +378,9 @@ void sendMIDIrewind() kernelMidi::send(0x00, 0x00, 0x00); // mins, secs, frames 0 kernelMidi::send(MIDI_EOX, -1, -1); // end of sysex } + else + if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M) + kernelMidi::send(MIDI_POSITION_PTR, 0, 0); } @@ -393,26 +394,27 @@ void recvJackSync() /* TODO - these things should be processed by a higher level, above clock:: ----> clockManager */ - kernelAudio::JackState jackState = kernelAudio::jackTransportQuery(); + kernelAudio::JackState jackStateCurr = kernelAudio::jackTransportQuery(); + + if (jackStateCurr != jackStatePrev_) { - if (jackState.running != jackStatePrev_.running) { - if (jackState.running) { - if (!isRunning()) - mh::startSequencer(); + if (jackStateCurr.frame != jackStatePrev_.frame && jackStateCurr.frame == 0) { +G_DEBUG("JackState received - rewind to frame 0"); + sequencer::rewind(); } - else { - if (isRunning()) - mh::stopSequencer(); + + if (jackStateCurr.bpm != jackStatePrev_.bpm && jackStateCurr.bpm > 1.0f) { // 0 bpm if Jack does not send that info +G_DEBUG("JackState received - bpm=" << jackStateCurr.bpm); + c::main::setBpm(jackStateCurr.bpm); } - } - if (jackState.bpm != jackStatePrev_.bpm) - if (jackState.bpm > 1.0f) // 0 bpm if Jack does not send that info - c::main::setBpm(jackState.bpm); - if (jackState.frame == 0 && jackState.frame != jackStatePrev_.frame) - mh::rewindSequencer(); + if (jackStateCurr.running != jackStatePrev_.running) { +G_DEBUG("JackState received - running=" << jackStateCurr.running); + jackStateCurr.running ? sequencer::start() : sequencer::stop(); + } + } - jackStatePrev_ = jackState; + jackStatePrev_ = jackStateCurr; } #endif @@ -426,7 +428,6 @@ bool canQuantize() model::ClockLock lock(model::clock); const model::Clock* c = model::clock.get(); - return c->quantize > 0 && c->status == ClockStatus::RUNNING; } @@ -434,16 +435,26 @@ bool canQuantize() /* -------------------------------------------------------------------------- */ -int getCurrentFrame() { return currentFrame_.load(); } -int getCurrentBeat() { return currentBeat_.load(); } -int getQuanto() { return quanto_; } -ClockStatus getStatus() { model::ClockLock lock(model::clock); return model::clock.get()->status; } -int getFramesInLoop() { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; } -int getFramesInBar() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; } -int getFramesInBeat() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; } -int getFramesInSeq() { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; } -int getQuantize() { model::ClockLock lock(model::clock); return model::clock.get()->quantize; } -float getBpm() { model::ClockLock lock(model::clock); return model::clock.get()->bpm; } -int getBeats() { model::ClockLock lock(model::clock); return model::clock.get()->beats; } -int getBars() { model::ClockLock lock(model::clock); return model::clock.get()->bars; } +Frame quantize(Frame f) +{ + if (!canQuantize()) return f; + return u::math::quantize(f, quantizerStep_) % getFramesInLoop(); // No overflow +} + + +/* -------------------------------------------------------------------------- */ + + +int getCurrentFrame() { return currentFrame_.load(); } +int getCurrentBeat() { return currentBeat_.load(); } +int getQuantizerStep() { return quantizerStep_; } +ClockStatus getStatus() { model::ClockLock lock(model::clock); return model::clock.get()->status; } +int getFramesInLoop() { model::ClockLock lock(model::clock); return model::clock.get()->framesInLoop; } +int getFramesInBar() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBar; } +int getFramesInBeat() { model::ClockLock lock(model::clock); return model::clock.get()->framesInBeat; } +int getFramesInSeq() { model::ClockLock lock(model::clock); return model::clock.get()->framesInSeq; } +int getQuantizerValue() { model::ClockLock lock(model::clock); return model::clock.get()->quantize; } +float getBpm() { model::ClockLock lock(model::clock); return model::clock.get()->bpm; } +int getBeats() { model::ClockLock lock(model::clock); return model::clock.get()->beats; } +int getBars() { model::ClockLock lock(model::clock); return model::clock.get()->bars; } }}}; // giada::m::clock:: diff --git a/src/core/clock.h b/src/core/clock.h index 670e4ce..40aaaf0 100644 --- a/src/core/clock.h +++ b/src/core/clock.h @@ -53,7 +53,7 @@ Rewinds timecode to beat 0 and also send a MTC full frame to cue the slave. */ void sendMIDIrewind(); -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) || defined(G_OS_MAC) void recvJackSync(); #endif @@ -66,25 +66,30 @@ int getFramesInBar(); int getFramesInBeat(); int getFramesInLoop(); int getFramesInSeq(); -int getQuantize(); -int getQuanto(); +int getQuantizerValue(); +int getQuantizerStep(); ClockStatus getStatus(); /* incrCurrentFrame -Increases current frame of a single step (+1). */ +Increases current frame by a single step (+1). */ void incrCurrentFrame(); /* quantoHasPassed -Tells whether a quanto unit has passed yet. */ +Tells whether a quantizer unit has passed yet. */ bool quantoHasPassed(); -/* quantoHasPassed -Whether the quantizer value is > 0 and the clock is running. */ +/* canQuantize +Tells whether the quantizer value is > 0 and the clock is running. */ bool canQuantize(); +/* quantize +Quantizes the global frame 'f'. */ + +Frame quantize(Frame f); + void setBpm(float b); void setBeats(int beats, int bars); void setQuantize(int q); diff --git a/src/core/conf.cpp b/src/core/conf.cpp index 7d8b000..f68be90 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -147,7 +147,6 @@ bool read() conf.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap); conf.midiSync = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync); conf.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps); - conf.recsStopOnChanHalt = j.value(CONF_KEY_RECS_STOP_ON_CHAN_HALT, conf.recsStopOnChanHalt); conf.chansStopOnSeqHalt = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, conf.chansStopOnSeqHalt); conf.treatRecsAsLoops = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, conf.treatRecsAsLoops); conf.inputMonitorDefaultOn = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, conf.inputMonitorDefaultOn); @@ -253,7 +252,6 @@ bool write() j[CONF_KEY_MIDI_IN_VOLUME_OUT] = conf.midiInVolumeOut; j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = conf.midiInBeatDouble; j[CONF_KEY_MIDI_IN_BEAT_HALF] = conf.midiInBeatHalf; - j[CONF_KEY_RECS_STOP_ON_CHAN_HALT] = conf.recsStopOnChanHalt; j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT] = conf.chansStopOnSeqHalt; j[CONF_KEY_TREAT_RECS_AS_LOOPS] = conf.treatRecsAsLoops; j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON] = conf.inputMonitorDefaultOn; diff --git a/src/core/conf.h b/src/core/conf.h index c750c7b..d7dc210 100644 --- a/src/core/conf.h +++ b/src/core/conf.h @@ -60,7 +60,6 @@ struct Conf int midiSync = MIDI_SYNC_NONE; float midiTCfps = 25.0f; - bool recsStopOnChanHalt = false; bool chansStopOnSeqHalt = false; bool treatRecsAsLoops = false; bool inputMonitorDefaultOn = false; diff --git a/src/core/const.h b/src/core/const.h index 8547fbe..21999d6 100644 --- a/src/core/const.h +++ b/src/core/const.h @@ -29,6 +29,18 @@ #define G_CONST_H +#include + + +/* -- 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 @@ -48,10 +60,10 @@ /* -- version --------------------------------------------------------------- */ constexpr auto G_APP_NAME = "Giada"; -constexpr auto G_VERSION_STR = "0.16.2"; +constexpr auto G_VERSION_STR = "0.16.3"; constexpr int G_VERSION_MAJOR = 0; constexpr int G_VERSION_MINOR = 16; -constexpr int G_VERSION_PATCH = 2; +constexpr int G_VERSION_PATCH = 3; constexpr auto CONF_FILENAME = "giada.conf"; @@ -65,8 +77,8 @@ constexpr auto CONF_FILENAME = "giada.conf"; /* -- GUI ------------------------------------------------------------------- */ -constexpr float G_GUI_REFRESH_RATE = 0.05; -constexpr float G_GUI_PLUGIN_RATE = 0.05; // refresh rate for plugin GUI +constexpr float G_GUI_REFRESH_RATE = 1 / 30.0; // 30 fps +constexpr float G_GUI_PLUGIN_RATE = 1 / 30.0; // 30 fps constexpr int G_GUI_FONT_SIZE_BASE = 12; constexpr int G_GUI_INNER_MARGIN = 4; constexpr int G_GUI_OUTER_MARGIN = 8; @@ -88,28 +100,31 @@ constexpr int G_GUI_ZOOM_FACTOR = 2; /* -- MIN/MAX values -------------------------------------------------------- */ -constexpr float G_MIN_BPM = 20.0f; -constexpr auto G_MIN_BPM_STR = "20.0"; -constexpr float G_MAX_BPM = 999.0f; -constexpr auto G_MAX_BPM_STR = "999.0"; -constexpr int G_MAX_BEATS = 32; -constexpr int G_MAX_BARS = 32; -constexpr int G_MAX_QUANTIZE = 8; -constexpr float G_MIN_DB_SCALE = 60.0f; -constexpr int G_MIN_COLUMN_WIDTH = 140; -constexpr float G_MAX_BOOST_DB = 20.0f; -constexpr float G_MIN_PITCH = 0.1f; -constexpr float G_MAX_PITCH = 4.0f; -constexpr float G_MAX_VOLUME = 1.0f; -constexpr int G_MAX_GRID_VAL = 64; -constexpr int G_MIN_BUF_SIZE = 8; -constexpr int G_MAX_BUF_SIZE = 4096; -constexpr int G_MIN_GUI_WIDTH = 816; -constexpr int G_MIN_GUI_HEIGHT = 510; -constexpr int G_MAX_IO_CHANS = 2; -constexpr int G_MAX_VELOCITY = 0x7F; -constexpr int G_MAX_MIDI_CHANS = 16; -constexpr int G_MAX_POLYPHONY = 32; +constexpr float G_MIN_BPM = 20.0f; +constexpr auto G_MIN_BPM_STR = "20.0"; +constexpr float G_MAX_BPM = 999.0f; +constexpr auto G_MAX_BPM_STR = "999.0"; +constexpr int G_MAX_BEATS = 32; +constexpr int G_MAX_BARS = 32; +constexpr int G_MAX_QUANTIZE = 8; +constexpr float G_MIN_DB_SCALE = 60.0f; +constexpr int G_MIN_COLUMN_WIDTH = 140; +constexpr float G_MAX_BOOST_DB = 20.0f; +constexpr float G_MIN_PITCH = 0.1f; +constexpr float G_MAX_PITCH = 4.0f; +constexpr float G_MAX_PAN = 1.0f; +constexpr float G_MAX_VOLUME = 1.0f; +constexpr int G_MAX_GRID_VAL = 64; +constexpr int G_MIN_BUF_SIZE = 8; +constexpr int G_MAX_BUF_SIZE = 4096; +constexpr int G_MIN_GUI_WIDTH = 816; +constexpr int G_MIN_GUI_HEIGHT = 510; +constexpr int G_MAX_IO_CHANS = 2; +constexpr int G_MAX_VELOCITY = 0x7F; +constexpr int G_MAX_MIDI_CHANS = 16; +constexpr int G_MAX_POLYPHONY = 32; +constexpr int G_MAX_QUEUE_EVENTS = 32; +constexpr int G_MAX_QUANTIZER_SIZE = 8; @@ -143,28 +158,30 @@ constexpr int G_MIDI_API_ALSA = 0x02; // 0000 0010 #define G_DEFAULT_SOUNDSYS G_SYS_API_CORE #endif -constexpr int G_DEFAULT_SOUNDDEV_OUT = 0; // FIXME - please override with rtAudio::getDefaultDevice (or similar) -constexpr int G_DEFAULT_SOUNDDEV_IN = -1; // no recording by default: input disabled -constexpr int G_DEFAULT_MIDI_SYSTEM = 0; -constexpr int G_DEFAULT_MIDI_PORT_IN = -1; -constexpr int G_DEFAULT_MIDI_PORT_OUT = -1; -constexpr int G_DEFAULT_SAMPLERATE = 44100; -constexpr int G_DEFAULT_BUFSIZE = 1024; -constexpr int G_DEFAULT_BIT_DEPTH = 32; // float -constexpr float G_DEFAULT_VOL = 1.0f; -constexpr float G_DEFAULT_PITCH = 1.0f; -constexpr float G_DEFAULT_BPM = 120.0f; -constexpr int G_DEFAULT_BEATS = 4; -constexpr int G_DEFAULT_BARS = 1; -constexpr int G_DEFAULT_QUANTIZE = 0; // quantizer off -constexpr float G_DEFAULT_FADEOUT_STEP = 0.01f; // micro-fadeout speed -constexpr int G_DEFAULT_COLUMN_WIDTH = 380; -constexpr auto G_DEFAULT_PATCH_NAME = "(default patch)"; -constexpr int G_DEFAULT_ACTION_SIZE = 8192; // frames -constexpr int G_DEFAULT_ZOOM_RATIO = 128; -constexpr float G_DEFAULT_REC_TRIGGER_LEVEL = -10.0f; -constexpr int G_DEFAULT_SUBWINDOW_W = 640; -constexpr int G_DEFAULT_SUBWINDOW_H = 480; +constexpr int G_DEFAULT_SOUNDDEV_OUT = 0; // FIXME - please override with rtAudio::getDefaultDevice (or similar) +constexpr int G_DEFAULT_SOUNDDEV_IN = -1; // no recording by default: input disabled +constexpr int G_DEFAULT_MIDI_SYSTEM = 0; +constexpr int G_DEFAULT_MIDI_PORT_IN = -1; +constexpr int G_DEFAULT_MIDI_PORT_OUT = -1; +constexpr int G_DEFAULT_SAMPLERATE = 44100; +constexpr int G_DEFAULT_BUFSIZE = 1024; +constexpr int G_DEFAULT_BIT_DEPTH = 32; // float +constexpr float G_DEFAULT_VOL = 1.0f; +constexpr float G_DEFAULT_PAN = 0.5f; +constexpr float G_DEFAULT_PITCH = 1.0f; +constexpr float G_DEFAULT_BPM = 120.0f; +constexpr int G_DEFAULT_BEATS = 4; +constexpr int G_DEFAULT_BARS = 1; +constexpr int G_DEFAULT_QUANTIZE = 0; // quantizer off +constexpr float G_DEFAULT_FADEOUT_STEP = 0.01f; // micro-fadeout speed +constexpr int G_DEFAULT_COLUMN_WIDTH = 380; +constexpr auto G_DEFAULT_PATCH_NAME = "(default patch)"; +constexpr int G_DEFAULT_ACTION_SIZE = 8192; // frames +constexpr int G_DEFAULT_ZOOM_RATIO = 128; +constexpr float G_DEFAULT_REC_TRIGGER_LEVEL = -10.0f; +constexpr int G_DEFAULT_SUBWINDOW_W = 640; +constexpr int G_DEFAULT_SUBWINDOW_H = 480; +constexpr int G_DEFAULT_VST_MIDIBUFFER_SIZE = 1024; // TODO - not 100% sure about this size @@ -259,8 +276,8 @@ constexpr int G_MIDI_OUT_L_SOLO = 5; Channel voices messages - controller (0xB0) is a special subset of this family: it drives knobs, volume, faders and such. */ -#define MIDI_CONTROLLER 0xB0 << 24 -#define MIDI_ALL_NOTES_OFF (MIDI_CONTROLLER) | (0x7B << 16) +constexpr uint32_t G_MIDI_CONTROLLER = 0xB0 << 24; +constexpr uint32_t G_MIDI_ALL_NOTES_OFF = (G_MIDI_CONTROLLER) | (0x7B << 16); /* system common / real-time messages. Single bytes */ @@ -273,15 +290,6 @@ it drives knobs, volume, faders and such. */ #define MIDI_STOP 0xFC #define MIDI_EOX 0xF7 // end of sysex -/* Channels */ - -constexpr int G_MIDI_CHANS[G_MAX_MIDI_CHANS] = { - 0x00 << 24, 0x01 << 24, 0x02 << 24, 0x03 << 24, - 0x04 << 24, 0x05 << 24, 0x06 << 24, 0x07 << 24, - 0x08 << 24, 0x09 << 24, 0x0A << 24, 0x0B << 24, - 0x0C << 24, 0x0D << 24, 0x0E << 24, 0x0F << 24 -}; - /* midi sync constants */ #define MIDI_SYNC_NONE 0x00 @@ -406,7 +414,6 @@ constexpr auto CONF_KEY_MIDI_IN_VOLUME_IN = "midi_in_volume_in"; constexpr auto CONF_KEY_MIDI_IN_VOLUME_OUT = "midi_in_volume_out"; constexpr auto CONF_KEY_MIDI_IN_BEAT_DOUBLE = "midi_in_beat_doble"; constexpr auto CONF_KEY_MIDI_IN_BEAT_HALF = "midi_in_beat_half"; -constexpr auto CONF_KEY_RECS_STOP_ON_CHAN_HALT = "recs_stop_on_chan_halt"; constexpr auto CONF_KEY_CHANS_STOP_ON_SEQ_HALT = "chans_stop_on_seq_halt"; constexpr auto CONF_KEY_TREAT_RECS_AS_LOOPS = "treat_recs_as_loops"; constexpr auto CONF_KEY_INPUT_MONITOR_DEFAULT_ON = "input_monitor_default_on"; diff --git a/src/core/init.cpp b/src/core/init.cpp index d43a561..42a10fd 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -45,13 +45,14 @@ #include "gui/dialogs/mainWindow.h" #include "gui/dialogs/warnings.h" #include "glue/main.h" -#include "core/channels/channel.h" +#include "core/model/storage.h" #include "core/channels/channelManager.h" #include "core/mixer.h" #include "core/wave.h" #include "core/const.h" #include "core/clock.h" #include "core/mixerHandler.h" +#include "core/sequencer.h" #include "core/patch.h" #include "core/conf.h" #include "core/waveManager.h" @@ -83,6 +84,8 @@ void initConf_() patch::init(); midimap::init(); midimap::setDefault(); + + model::load(conf::conf); if (!u::log::init(conf::conf.logMode)) u::log::print("[init] log init failed! Using default stdout\n"); @@ -100,6 +103,7 @@ void initAudio_() kernelAudio::openDevice(); clock::init(conf::conf.samplerate, conf::conf.midiTCfps); mh::init(); + sequencer::init(); recorder::init(); recorderHandler::init(); @@ -260,6 +264,7 @@ void reset() waveManager::init(); clock::init(conf::conf.samplerate, conf::conf.midiTCfps); mh::init(); + sequencer::init(); recorder::init(); #ifdef WITH_VST pluginManager::init(conf::conf.samplerate, kernelAudio::getRealBufSize()); @@ -278,6 +283,8 @@ void shutdown() { shutdownGUI_(); + model::store(conf::conf); + if (!conf::write()) u::log::print("[init] error while saving configuration file!\n"); else diff --git a/src/core/kernelAudio.cpp b/src/core/kernelAudio.cpp index 4df1bd4..0068c0c 100644 --- a/src/core/kernelAudio.cpp +++ b/src/core/kernelAudio.cpp @@ -32,6 +32,7 @@ #include "glue/main.h" #include "core/model/model.h" #include "conf.h" +#include "const.h" #include "mixer.h" #include "const.h" #include "kernelAudio.h" @@ -49,11 +50,11 @@ bool inputEnabled = false; unsigned realBufsize = 0; // Real buffer size from the soundcard int api = 0; -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) JackState jackState; -jack_client_t* jackGetHandle() +jack_client_t* jackGetHandle_() { return static_cast(rtSystem->HACK__getJackClient()); } @@ -67,10 +68,22 @@ jack_client_t* jackGetHandle() /* -------------------------------------------------------------------------- */ +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) + +bool JackState::operator!=(const JackState& o) const +{ + return !(running == o.running && bpm == o.bpm && frame == o.frame); +} + +#endif + + +/* -------------------------------------------------------------------------- */ + + bool isReady() { model::KernelLock lock(model::kernel); - return model::kernel.get()->audioReady; } @@ -165,7 +178,7 @@ int openDevice() realBufsize = conf::conf.buffersize; -#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) if (api == G_SYS_API_JACK) { conf::conf.samplerate = getFreq(conf::conf.soundDeviceOut, 0); @@ -185,10 +198,9 @@ int openDevice() nullptr, // user data (unused) &options); - std::unique_ptr k = model::kernel.clone(); - k->audioReady = true; - model::kernel.swap(std::move(k)); - + model::onSwap(model::kernel, [](model::Kernel& k) { + k.audioReady = true; + }); return 1; } catch (RtAudioError &e) { @@ -432,19 +444,21 @@ int getAPI() { return api; } /* -------------------------------------------------------------------------- */ -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) - -const JackState &jackTransportQuery() +JackState jackTransportQuery() { if (api != G_SYS_API_JACK) return jackState; - jack_position_t position; - jack_transport_state_t ts = jack_transport_query(jackGetHandle(), &position); - jackState.running = ts != JackTransportStopped; - jackState.bpm = position.beats_per_minute; - jackState.frame = position.frame; - return jackState; + + jack_position_t position; + jack_transport_state_t ts = jack_transport_query(jackGetHandle_(), &position); + + return { + ts != JackTransportStopped, + position.beats_per_minute, + position.frame + }; } @@ -454,7 +468,7 @@ const JackState &jackTransportQuery() void jackStart() { if (api == G_SYS_API_JACK) - jack_transport_start(jackGetHandle()); + jack_transport_start(jackGetHandle_()); } @@ -466,9 +480,9 @@ void jackSetPosition(uint32_t frame) if (api != G_SYS_API_JACK) return; jack_position_t position; - jack_transport_query(jackGetHandle(), &position); + jack_transport_query(jackGetHandle_(), &position); position.frame = frame; - jack_transport_reposition(jackGetHandle(), &position); + jack_transport_reposition(jackGetHandle_(), &position); } @@ -480,13 +494,13 @@ void jackSetBpm(double bpm) if (api != G_SYS_API_JACK) return; jack_position_t position; - jack_transport_query(jackGetHandle(), &position); + jack_transport_query(jackGetHandle_(), &position); position.valid = jack_position_bits_t::JackPositionBBT; position.bar = 0; // no such info from Giada position.beat = 0; // no such info from Giada position.tick = 0; // no such info from Giada position.beats_per_minute = bpm; - jack_transport_reposition(jackGetHandle(), &position); + jack_transport_reposition(jackGetHandle_(), &position); } @@ -496,7 +510,7 @@ void jackSetBpm(double bpm) void jackStop() { if (api == G_SYS_API_JACK) - jack_transport_stop(jackGetHandle()); + jack_transport_stop(jackGetHandle_()); } #endif // defined(__linux__) || defined(__FreeBSD__) diff --git a/src/core/kernelAudio.h b/src/core/kernelAudio.h index 942a139..f0d5595 100644 --- a/src/core/kernelAudio.h +++ b/src/core/kernelAudio.h @@ -45,9 +45,11 @@ namespace kernelAudio struct JackState { - bool running; - double bpm; - uint32_t frame; + bool running; + double bpm; + uint32_t frame; + + bool operator!=(const JackState& o) const; }; #endif @@ -82,9 +84,10 @@ void jackStart(); void jackStop(); void jackSetPosition(uint32_t frame); void jackSetBpm(double bpm); -const JackState &jackTransportQuery(); +JackState jackTransportQuery(); #endif }}}; // giada::m::kernelAudio:: + #endif diff --git a/src/core/kernelMidi.cpp b/src/core/kernelMidi.cpp index 93e8da2..3347356 100644 --- a/src/core/kernelMidi.cpp +++ b/src/core/kernelMidi.cpp @@ -72,11 +72,12 @@ void sendMidiLightningInitMsgs_() for (const midimap::Message& m : midimap::midimap.initCommands) { if (m.value != 0x0 && m.channel != -1) { u::log::print("[KM] MIDI send (init) - Channel %x - Event 0x%X\n", m.channel, m.value); - send(m.value | G_MIDI_CHANS[m.channel]); + MidiEvent e(m.value); + e.setChannel(m.channel); + send(e.getRaw()); } } } - } // {anonymous} @@ -224,7 +225,7 @@ void send(uint32_t data) msg.push_back(getB3(data)); midiOut_->sendMessage(&msg); - u::log::print("[KM] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]); + u::log::print("[KM::send] send msg=0x%X (%X %X %X)\n", data, msg[0], msg[1], msg[2]); } @@ -244,30 +245,30 @@ void send(int b1, int b2, int b3) msg.push_back(b3); midiOut_->sendMessage(&msg); - //u::log::print("[KM] send msg=(%X %X %X)\n", b1, b2, b3); + u::log::print("[KM::send] send msg=(%X %X %X)\n", b1, b2, b3); } /* -------------------------------------------------------------------------- */ -void sendMidiLightning(uint32_t learn, const midimap::Message& m) +void sendMidiLightning(uint32_t learnt, const midimap::Message& m) { // Skip lightning message if not defined in midi map if (!midimap::isDefined(m)) { - u::log::print("[KM] message skipped (not defined in midimap)"); + u::log::print("[KM::sendMidiLightning] message skipped (not defined in midimap)"); return; } - u::log::print("[KM] learn=%#X, chan=%d, msg=%#X, offset=%d\n", learn, m.channel, - m.value, m.offset); + u::log::print("[KM::sendMidiLightning] learnt=0x%X, chan=%d, msg=0x%X, offset=%d\n", + learnt, m.channel, m.value, m.offset); /* Isolate 'channel' from learnt message and offset it as requested by 'nn' in the midimap configuration file. */ - uint32_t out = ((learn & 0x00FF0000) >> 16) << m.offset; + uint32_t out = ((learnt & 0x00FF0000) >> 16) << m.offset; /* Merge the previously prepared channel into final message, and finally send it. */ @@ -284,6 +285,7 @@ unsigned countInPorts() { return numInPorts_; } unsigned countOutPorts() { return numOutPorts_; } bool getStatus() { return status_; } + /* -------------------------------------------------------------------------- */ @@ -296,15 +298,4 @@ uint32_t getIValue(int b1, int b2, int b3) { return (b1 << 24) | (b2 << 16) | (b3 << 8) | (0x00); } - - -/* -------------------------------------------------------------------------- */ - - -uint32_t setChannel(uint32_t iValue, int channel) -{ - uint32_t chanMask = 0xF << 24; - return (iValue & (~chanMask)) | (channel << 24); -} - }}}; // giada::m::kernelMidi:: diff --git a/src/core/kernelMidi.h b/src/core/kernelMidi.h index f185b0c..2ec6b35 100644 --- a/src/core/kernelMidi.h +++ b/src/core/kernelMidi.h @@ -44,12 +44,6 @@ int getB3(uint32_t iValue); uint32_t getIValue(int b1, int b2, int b3); -/* setChannel -Changes MIDI channel number inside iValue. Returns new message with updated -channel. */ - -uint32_t setChannel(uint32_t iValue, int channel); - /* send Sends a MIDI message 's' as uint32_t or as separate bytes. */ @@ -59,7 +53,7 @@ void send(int b1, int b2=-1, int b3=-1); /* sendMidiLightning Sends a MIDI lightning message defined by 'msg'. */ -void sendMidiLightning(uint32_t learn, const midimap::Message& msg); +void sendMidiLightning(uint32_t learnt, const midimap::Message& msg); /* setApi Sets the Api in use for both in & out messages. */ diff --git a/src/core/midiDispatcher.cpp b/src/core/midiDispatcher.cpp index 09227fc..269cb7f 100644 --- a/src/core/midiDispatcher.cpp +++ b/src/core/midiDispatcher.cpp @@ -28,15 +28,10 @@ #include #include #include "glue/plugin.h" -#include "glue/io.h" -#include "glue/channel.h" -#include "glue/main.h" +#include "glue/events.h" #include "utils/log.h" #include "utils/math.h" #include "core/model/model.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/conf.h" #include "core/mixer.h" #include "core/mixerHandler.h" @@ -79,7 +74,7 @@ bool isMasterMidiInAllowed_(int c) bool isChannelMidiInAllowed_(ID channelId, int c) { model::ChannelsLock l(model::channels); - return model::get(model::channels, channelId).isMidiInAllowed(c); + return model::get(model::channels, channelId).midiLearner.state->isAllowed(c); } @@ -101,11 +96,11 @@ void processPlugins_(const std::vector& ids, const MidiEvent& midiEvent) m::model::PluginsLock l(m::model::plugins); for (ID id : ids) { - m::Plugin& p = m::model::get(m::model::plugins, id); + const m::Plugin& p = m::model::get(m::model::plugins, id); for (unsigned k = 0; k < p.midiInParams.size(); k++) { if (pure != p.midiInParams.at(k)) continue; - c::plugin::setParameter(id, k, vf, /*gui=*/false); + c::events::setPluginParameter(id, k, vf, /*gui=*/false); u::log::print(" >>> [plugin %d parameter %d] (pure=0x%X, value=%d, float=%f)\n", p.id, k, pure, midiEvent.getVelocity(), vf); } @@ -122,98 +117,68 @@ void processChannels_(const MidiEvent& midiEvent) { uint32_t pure = midiEvent.getRawNoVelocity(); - /* TODO - this is definitely not the best approach but it's necessary as - you can't call actions on m::model::channels while locking on a upper - level. Let's wait for a better async mechanism... */ + model::ChannelsLock lock(model::channels); - std::vector> 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(ch); - if (pure == sch->midiInPitch) { - actions.push_back([=] { - float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH); - u::log::print(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n", - sch->id, pure, midiEvent.getVelocity(), vf); - c::channel::setPitch(sch->id, vf); - }); - } - else - if (pure == sch->midiInReadActions) { - actions.push_back([=] { - u::log::print(" >>> toggle read actions ch=%d (pure=0x%X)\n", sch->id, pure); - c::channel::toggleReadingActions(sch->id); - }); - } + else if (pure == c->midiLearner.state->pitch.load()) { + float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_PITCH); + u::log::print(" >>> pitch ch=%d (pure=0x%X, value=%d, float=%f)\n", + c->id, pure, midiEvent.getVelocity(), vf); + c::events::setChannelPitch(c->id, vf, Thread::MIDI); + } + else if (pure == c->midiLearner.state->readActions.load()) { + u::log::print(" >>> toggle read actions ch=%d (pure=0x%X)\n", c->id, pure); + c::events::toggleReadActionsChannel(c->id, Thread::MIDI); } -#ifdef WITH_VST +#ifdef WITH_VST /* Process learned plugins parameters. */ - processPlugins_(ch->pluginIds, midiEvent); - + processPlugins_(c->pluginIds, midiEvent); #endif - /* Redirect full midi message (pure + velocity) to plugins. */ - ch->receiveMidi(midiEvent.getRaw()); - } - model::channels.unlock(); + /* Redirect raw MIDI message (pure + velocity) to plug-ins in armed + channels. */ - /* Apply all the collected actions. */ - for (auto& action : actions) - action(); + if (c->state->armed.load() == true) + c::events::sendMidiToChannel(c->id, midiEvent, Thread::MIDI); + } } @@ -228,43 +193,43 @@ void processMaster_(const MidiEvent& midiEvent) const model::MidiIn* midiIn = model::midiIn.get(); if (pure == midiIn->rewind) { - mh::rewindSequencer(); + c::events::rewindSequencer(Thread::MIDI); u::log::print(" >>> rewind (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->startStop) { - mh::toggleSequencer(); + c::events::toggleSequencer(Thread::MIDI); u::log::print(" >>> startStop (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->actionRec) { - recManager::toggleActionRec(conf::conf.recTriggerMode); + c::events::toggleActionRecording(); u::log::print(" >>> actionRec (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->inputRec) { - c::main::toggleInputRec(); + c::events::toggleInputRecording(); u::log::print(" >>> inputRec (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->metronome) { - m::mixer::toggleMetronome(); + c::events::toggleMetronome(); u::log::print(" >>> metronome (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->volumeIn) { float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); - c::main::setInVol(vf, /*gui=*/false); + c::events::setMasterInVolume(vf, Thread::MIDI); u::log::print(" >>> input volume (master) (pure=0x%X, value=%d, float=%f)\n", pure, midiEvent.getVelocity(), vf); } else if (pure == midiIn->volumeOut) { float vf = u::math::map(midiEvent.getVelocity(), G_MAX_VELOCITY, G_MAX_VOLUME); - c::main::setOutVol(vf, /*gui=*/false); + c::events::setMasterOutVolume(vf, Thread::MIDI); u::log::print(" >>> output volume (master) (pure=0x%X, value=%d, float=%f)\n", pure, midiEvent.getVelocity(), vf); } else if (pure == midiIn->beatDouble) { - c::main::beatsMultiply(); + c::events::multiplyBeats(); u::log::print(" >>> sequencer x2 (master) (pure=0x%X)\n", pure); } else if (pure == midiIn->beatHalf) { - c::main::beatsDivide(); + c::events::divideBeats(); u::log::print(" >>> sequencer /2 (master) (pure=0x%X)\n", pure); } } @@ -280,18 +245,21 @@ void learnChannel_(MidiEvent e, int param, ID channelId, std::function d uint32_t raw = e.getRawNoVelocity(); - model::onSwap(model::channels, channelId, [&](Channel& c) + model::onGet(model::channels, channelId, [param, raw](Channel& c) { switch (param) { - case G_MIDI_IN_KEYPRESS: c.midiInKeyPress = raw; break; - case G_MIDI_IN_KEYREL: c.midiInKeyRel = raw; break; - case G_MIDI_IN_KILL: c.midiInKill = raw; break; - case G_MIDI_IN_ARM: c.midiInArm = raw; break; - case G_MIDI_IN_MUTE: c.midiInVolume = raw; break; - case G_MIDI_IN_SOLO: c.midiInMute = raw; break; - case G_MIDI_IN_VOLUME: c.midiInVolume = raw; break; - case G_MIDI_IN_PITCH: static_cast(c).midiInPitch = raw; break; - case G_MIDI_IN_READ_ACTIONS: static_cast(c).midiInReadActions = raw; break; + case G_MIDI_IN_KEYPRESS: c.midiLearner.state->keyPress.store(raw); break; + case G_MIDI_IN_KEYREL: c.midiLearner.state->keyRelease.store(raw); break; + case G_MIDI_IN_KILL: c.midiLearner.state->kill.store(raw); break; + case G_MIDI_IN_ARM: c.midiLearner.state->arm.store(raw); break; + case G_MIDI_IN_MUTE: c.midiLearner.state->mute.store(raw); break; + case G_MIDI_IN_SOLO: c.midiLearner.state->solo.store(raw); break; + case G_MIDI_IN_VOLUME: c.midiLearner.state->volume.store(raw); break; + case G_MIDI_IN_PITCH: c.midiLearner.state->pitch.store(raw); break; + case G_MIDI_IN_READ_ACTIONS: c.midiLearner.state->readActions.store(raw); break; + case G_MIDI_OUT_L_PLAYING: c.midiLighter.state->playing.store(raw); break; + case G_MIDI_OUT_L_MUTE: c.midiLighter.state->mute.store(raw); break; + case G_MIDI_OUT_L_SOLO: c.midiLighter.state->solo.store(raw); break; } }); @@ -307,7 +275,7 @@ void learnMaster_(MidiEvent e, int param, std::function doneCb) uint32_t raw = e.getRawNoVelocity(); - model::onSwap(model::midiIn, [&](model::MidiIn& m) + model::onSwap(model::midiIn, [param, raw](model::MidiIn& m) { switch (param) { case G_MIDI_IN_REWIND: m.rewind = raw; break; diff --git a/src/core/midiEvent.cpp b/src/core/midiEvent.cpp index abdbea4..b845693 100644 --- a/src/core/midiEvent.cpp +++ b/src/core/midiEvent.cpp @@ -27,31 +27,30 @@ #include #include "const.h" +#include "utils/math.h" #include "midiEvent.h" namespace giada { namespace m { -MidiEvent::MidiEvent() - : m_status (0), - m_channel (0), - m_note (0), - m_velocity(0), - m_delta (0) +namespace { -} +constexpr int FLOAT_FACTOR = 10000; +} // {anonymous} +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ MidiEvent::MidiEvent(uint32_t raw) - : m_status ((raw & 0xF0000000) >> 24), - m_channel ((raw & 0x0F000000) >> 24), - m_note ((raw & 0x00FF0000) >> 16), - m_velocity((raw & 0x0000FF00) >> 8), - m_delta (0) // not used +: m_status ((raw & 0xF0000000) >> 24) +, m_channel ((raw & 0x0F000000) >> 24) +, m_note ((raw & 0x00FF0000) >> 16) +, m_velocity((raw & 0x0000FF00) >> 8) +, m_delta (0) // not used { } @@ -59,9 +58,20 @@ MidiEvent::MidiEvent(uint32_t raw) /* -------------------------------------------------------------------------- */ +/* static_cast to avoid ambiguity with MidiEvent(float). */ MidiEvent::MidiEvent(int byte1, int byte2, int byte3) - : MidiEvent((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00)) +: MidiEvent(static_cast((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | (0x00))) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +MidiEvent::MidiEvent(float v) +: MidiEvent(ENVELOPE, 0, 0) { + m_velocity = v * FLOAT_FACTOR; } @@ -128,6 +138,12 @@ int MidiEvent::getVelocity() const } +float MidiEvent::getVelocityFloat() const +{ + return m_velocity / static_cast(FLOAT_FACTOR); +} + + bool MidiEvent::isNoteOnOff() const { return m_status == NOTE_ON || m_status == NOTE_OFF; diff --git a/src/core/midiEvent.h b/src/core/midiEvent.h index 711c54d..eefeb6c 100644 --- a/src/core/midiEvent.h +++ b/src/core/midiEvent.h @@ -44,14 +44,25 @@ public: static const int NOTE_KILL = 0x70; static const int ENVELOPE = 0xB0; - MidiEvent(); + /* MidiEvent (1) + Creates and empty and invalid MIDI event. */ + + MidiEvent() = default; + MidiEvent(uint32_t raw); MidiEvent(int byte1, int byte2, int byte3); + /* MidiEvent (4) + A constructor that takes a float parameter. Useful to build ENVELOPE events + for automations, volume and pitch. */ + + MidiEvent(float v); + int getStatus() const; int getChannel() const; int getNote() const; - int getVelocity() const; + int getVelocity() const; + float getVelocityFloat() const; bool isNoteOnOff() const; int getDelta() const; diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp index fa4efc7..ea42857 100644 --- a/src/core/midiMapConf.cpp +++ b/src/core/midiMapConf.cpp @@ -92,7 +92,7 @@ void parse_(Message& message) std::string input = message.valueStr; - size_t f = input.find("0x"); // check if "0x" is there + std::size_t f = input.find("0x"); // check if "0x" is there if (f != std::string::npos) input = message.valueStr.replace(f, 2, ""); diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index ebe4966..a556b7a 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -31,9 +31,6 @@ #include "utils/log.h" #include "utils/math.h" #include "core/model/model.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/wave.h" #include "core/kernelAudio.h" #include "core/recorder.h" @@ -45,6 +42,7 @@ #include "core/const.h" #include "core/audioBuffer.h" #include "core/action.h" +#include "core/sequencer.h" #include "core/mixer.h" @@ -54,56 +52,15 @@ namespace mixer { namespace { -struct Metronome -{ - static constexpr Frame CLICK_SIZE = 38; - - float beat[CLICK_SIZE] = { - 0.059033, 0.117240, 0.173807, 0.227943, 0.278890, 0.325936, - 0.368423, 0.405755, 0.437413, 0.462951, 0.482013, 0.494333, - 0.499738, 0.498153, 0.489598, 0.474195, 0.452159, 0.423798, - 0.389509, 0.349771, 0.289883, 0.230617, 0.173194, 0.118739, - 0.068260, 0.022631, -0.017423, -0.051339, -0.078721, -0.099345, - -0.113163, -0.120295, -0.121028, -0.115804, -0.105209, -0.089954, - -0.070862, -0.048844 - }; - - float bar[CLICK_SIZE] = { - 0.175860, 0.341914, 0.488904, 0.608633, 0.694426, 0.741500, - 0.747229, 0.711293, 0.635697, 0.524656, 0.384362, 0.222636, - 0.048496, -0.128348, -0.298035, -0.451105, -0.579021, -0.674653, - -0.732667, -0.749830, -0.688924, -0.594091, -0.474481, -0.340160, - -0.201360, -0.067752, 0.052194, 0.151746, 0.226280, 0.273493, - 0.293425, 0.288307, 0.262252, 0.220811, 0.170435, 0.117887, - 0.069639, 0.031320 - }; - - Frame tracker = 0; - bool running = false; - bool playBar = false; - bool playBeat = false; - - void render(AudioBuffer& outBuf, bool& process, float* data, Frame f) - { - process = true; - for (int i=0; i Metronome::CLICK_SIZE) { - process = false; - tracker = 0; - } - } -} metronome_; - -/* vChanInput_ -Virtual channel for input recording. */ +/* recBuffer_ +Working buffer for audio recording. */ -AudioBuffer vChanInput_; +AudioBuffer recBuffer_; -/* vChanInToOut_ -Virtual channel in->out bridge (hear what you're playin). */ +/* inBuffer_ +Working buffer for input channel. */ -AudioBuffer vChanInToOut_; +AudioBuffer inBuffer_; /* inputTracker_ Frame position while recording. */ @@ -118,16 +75,25 @@ std::function signalCb_ = nullptr; std::atomic processing_(false); std::atomic 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& peak) +bool isChannelAudible_(const Channel& c) { - for (int i=0; i peak) - peak = buf[i][j]; + if (c.getType() == ChannelType::MASTER || c.getType() == ChannelType::PREVIEW) + return true; + if (c.state->mute.load() == true) + return false; + model::MixerLock ml(model::mixer); + bool hasSolos = model::mixer.get()->hasSolos; + return !hasSolos || (hasSolos && c.state->solo.load() == true); } @@ -140,151 +106,105 @@ void lineInRec_(const AudioBuffer& inBuf) { if (!recManager::isRecordingInput() || !kernelAudio::isInputEnabled()) return; + + float inVol = mh::getInVol(); + int framesInLoop = clock::getFramesInLoop(); - for (int i=0; i= 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; iinToOut) // Merge vChanInToOut_, if enabled - outBuf[i][j] += vChanInToOut_[i][j]; - outBuf[i][j] *= mh::getOutVol(); - } + if (inToOut) + outBuf.addData(inBuffer_, outVol); + else + outBuf.applyGain(outVol); + + if (conf::conf.limitOutput) + limit_(outBuf); + + peakOut.store(outBuf.getPeak()); } }; // {anonymous} @@ -343,26 +262,26 @@ void finalizeOutput_(AudioBuffer& outBuf) /* -------------------------------------------------------------------------- */ -std::atomic rewindWait(false); std::atomic peakOut(0.0); std::atomic peakIn(0.0); +Queue UIevents; +Queue MidiEvents; + /* -------------------------------------------------------------------------- */ void init(Frame framesInSeq, Frame framesInBuffer) { - /* Allocate virtual inputs. vChanInput_ has variable size: it depends + /* Allocate virtual inputs. recBuffer_ has variable size: it depends on how many frames there are in sequencer. */ - vChanInput_.alloc(framesInSeq, G_MAX_IO_CHANS); - vChanInToOut_.alloc(framesInBuffer, G_MAX_IO_CHANS); + recBuffer_.alloc(framesInSeq, G_MAX_IO_CHANS); + inBuffer_.alloc(framesInBuffer, G_MAX_IO_CHANS); u::log::print("[mixer::init] buffers ready - framesInSeq=%d, framesInBuffer=%d\n", - framesInSeq, framesInBuffer); - - clock::rewind(); + framesInSeq, framesInBuffer); } @@ -388,21 +307,21 @@ void disable() /* -------------------------------------------------------------------------- */ -void allocVirtualInput(Frame frames) +void allocRecBuffer(Frame frames) { - vChanInput_.alloc(frames, G_MAX_IO_CHANS); + recBuffer_.alloc(frames, G_MAX_IO_CHANS); } -void clearVirtualInput() +void clearRecBuffer() { - vChanInput_.clear(); + recBuffer_.clear(); } -const AudioBuffer& getVirtualInput() +const AudioBuffer& getRecBuffer() { - return vChanInput_; + return recBuffer_; } @@ -417,11 +336,9 @@ int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize, processing_.store(true); -#if defined(__linux__) || defined(__FreeBSD__) - +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) if (kernelAudio::getAPI() == G_SYS_API_JACK) clock::recvJackSync(); - #endif AudioBuffer out, in; @@ -437,22 +354,28 @@ int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize, prepareBuffers_(out); processLineIn_(in); - /* Process model. */ - //out[0][0] = 3.0f; - if (clock::isActive()) - processSequencer_(out, in); - render_(out, in, vChanInToOut_); + renderMasterIn_(inBuffer_); + + fillEventBuffer_(); + processSequencer_(inBuffer_); + processChannels_(out, inBuffer_); + + renderMasterOut_(out); + + /* Advance sequencer only when rendering is done. */ + + if (clock::isActive()) + sequencer::advance(out); /* Post processing. */ finalizeOutput_(out); - limitOutput_(out); - computePeak_(out, peakOut); /* Unset data in buffers. If you don't do this, buffers go out of scope and destroy memory allocated by RtAudio ---> havoc. */ + out.setData(nullptr, 0, 0); in.setData (nullptr, 0, 0); @@ -474,20 +397,6 @@ void close() /* -------------------------------------------------------------------------- */ -bool isChannelAudible(const Channel* ch) -{ - model::MixerLock l(model::mixer); - - bool hasSolos = model::mixer.get()->hasSolos; - return !hasSolos || (hasSolos && ch->solo); -} - -bool isMetronomeOn() { return metronome_.running; } - - -/* -------------------------------------------------------------------------- */ - - void startInputRec() { /* Start inputTracker_ from the current frame, not the beginning. */ @@ -504,23 +413,17 @@ void stopInputRec() /* -------------------------------------------------------------------------- */ -void toggleMetronome() +void setSignalCallback(std::function f) { - metronome_.running = !metronome_.running; -} - - -void setMetronome(bool v) -{ - metronome_.running = v; + signalCb_ = f; } /* -------------------------------------------------------------------------- */ -void setSignalCallback(std::function f) +void pumpEvent(Event e) { - signalCb_ = f; + eventBuffer_.push_back(e); } }}}; // giada::m::mixer:: diff --git a/src/core/mixer.h b/src/core/mixer.h index 1ced345..ea830e4 100644 --- a/src/core/mixer.h +++ b/src/core/mixer.h @@ -33,8 +33,11 @@ #include #include #include "deps/rtaudio/RtAudio.h" +#include "core/ringBuffer.h" #include "core/recorder.h" #include "core/types.h" +#include "core/queue.h" +#include "core/midiEvent.h" namespace giada { @@ -42,28 +45,61 @@ namespace m { struct Action; class Channel; +class Channel; class AudioBuffer; namespace mixer { -struct FrameEvents +enum class EventType +{ + KEY_PRESS, + KEY_RELEASE, + KEY_KILL, + SEQUENCER_FIRST_BEAT, // 3 + SEQUENCER_BAR, // 4 + SEQUENCER_START, // 5 + SEQUENCER_STOP, // 6 + SEQUENCER_REWIND, // 7 + SEQUENCER_REWIND_REQ, // 8 + MIDI, + ACTION, + CHANNEL_TOGGLE_READ_ACTIONS, + CHANNEL_TOGGLE_ARM, + CHANNEL_MUTE, + CHANNEL_SOLO, + CHANNEL_VOLUME, + CHANNEL_PITCH, + CHANNEL_PAN +}; + +struct Event { - Frame frameLocal; - Frame frameGlobal; - bool doQuantize; - bool onBar; - bool onFirstBeat; - bool quantoPassed; - const std::vector* 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; + constexpr int MASTER_OUT_CHANNEL_ID = 1; constexpr int MASTER_IN_CHANNEL_ID = 2; constexpr int PREVIEW_CHANNEL_ID = 3; -extern std::atomic rewindWait; // rewind guard, if quantized -extern std::atomic peakOut; -extern std::atomic peakIn; +extern std::atomic peakOut; // TODO - move to model:: +extern std::atomic 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 UIevents; +extern Queue MidiEvents; void init(Frame framesInSeq, Frame framesInBuffer); @@ -75,22 +111,22 @@ called. */ void enable(); void disable(); -/* allocVirtualInput +/* allocRecBuffer Allocates new memory for the virtual input channel. Call this whenever you shrink or resize the sequencer. */ -void allocVirtualInput(Frame frames); +void allocRecBuffer(Frame frames); -/* clearVirtualInput +/* clearRecBuffer Clears internal virtual channel. */ -void clearVirtualInput(); +void clearRecBuffer(); -/* getVirtualInput +/* getRecBuffer Returns a read-only reference to the internal virtual channel. Use this to merge data into channel after an input recording session. */ -const AudioBuffer& getVirtualInput(); +const AudioBuffer& getRecBuffer(); void close(); @@ -100,19 +136,21 @@ Core method (callback) */ int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize, double streamTime, RtAudioStreamStatus status, void* userData); -bool isChannelAudible(const Channel* ch); - /* startInputRec, stopInputRec Starts/stops input recording on frame clock::getCurrentFrame(). */ void startInputRec(); void stopInputRec(); -void toggleMetronome(); -bool isMetronomeOn(); -void setMetronome(bool v); - void setSignalCallback(std::function 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::; diff --git a/src/core/mixerHandler.cpp b/src/core/mixerHandler.cpp index 9f31505..6d864d7 100644 --- a/src/core/mixerHandler.cpp +++ b/src/core/mixerHandler.cpp @@ -35,9 +35,6 @@ #include "glue/main.h" #include "glue/channel.h" #include "core/model/model.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/channels/channelManager.h" #include "core/kernelMidi.h" #include "core/mixer.h" @@ -76,7 +73,7 @@ std::unique_ptr createChannel_(ChannelType type, ID columnId, ID channe ch->id = channelId; } - return ch; + return ch; } @@ -102,7 +99,7 @@ waveManager::Result createWave_(const std::string& fname) /* -------------------------------------------------------------------------- */ -bool channelHas_(std::function f) +bool anyChannel_(std::function f) { model::ChannelsLock lock(model::channels); return std::any_of(model::channels.begin(), model::channels.end(), f); @@ -112,29 +109,51 @@ bool channelHas_(std::function f) /* -------------------------------------------------------------------------- */ -bool canInputRec_(size_t chanIndex) +template +std::vector getChannelsIf_(F f) { model::ChannelsLock l(model::channels); - return model::channels.get(chanIndex)->canInputRec(); + + std::vector ids; + for (const Channel* c : model::channels) + if (f(c)) ids.push_back(c->id); + + return ids; +} + + +std::vector getChannelsWithWave_() +{ + return getChannelsIf_([] (const Channel* c) + { + return c->samplePlayer && c->samplePlayer->hasWave(); + }); +} + + +std::vector 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&& w, bool clone) +void pushWave_(Channel& ch, std::unique_ptr&& w) { - if (ch.hasWave && !clone) // Don't pop if cloning a channel - model::waves.pop(model::getIndex(model::waves, ch.waveId)); - - ID id = w->id; - Frame size = w->getSize(); + assert(ch.getType() == ChannelType::SAMPLE); model::waves.push(std::move(w)); - ch.pushWave(id, size); + + model::WavesLock l(model::waves); + ch.samplePlayer->loadWave(model::waves.back()); } }; // {anonymous} @@ -172,31 +191,9 @@ void close() /* -------------------------------------------------------------------------- */ -bool uniqueSamplePath(ID channelToSkip, const std::string& path) -{ - model::ChannelsLock cl(model::channels); - model::WavesLock wl(model::waves); - - for (const Channel* c : model::channels) { - if (c->id == channelToSkip || c->type != ChannelType::SAMPLE) - continue; - const SampleChannel* sc = static_cast(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 c = createChannel_(type, columnId); - ID id = c->id; - model::channels.push(std::move(c)); - return id; + model::channels.push(std::move(createChannel_(type, columnId))); } @@ -210,11 +207,20 @@ int loadChannel(ID channelId, const std::string& fname) if (res.status != G_RES_OK) return res.status; + ID oldWaveId; + model::onSwap(model::channels, channelId, [&](Channel& c) { - pushWave_(static_cast(c), std::move(res.wave), /*clone=*/false); + oldWaveId = c.samplePlayer->getWaveId(); + pushWave_(c, std::move(res.wave)); }); + /* Remove old wave, if any. It is safe to do it now: the channel already + points to the new one. */ + + if (oldWaveId != 0) + model::waves.pop(model::getIndex(model::waves, oldWaveId)); + return res.status; } @@ -233,10 +239,9 @@ int addAndLoadChannel(ID columnId, const std::string& fname) void addAndLoadChannel(ID columnId, std::unique_ptr&& w) { - std::unique_ptr ch = createChannel_(ChannelType::SAMPLE, - columnId); + std::unique_ptr ch = createChannel_(ChannelType::SAMPLE, columnId); - pushWave_(static_cast(*ch.get()), std::move(w), /*clone=*/false); + pushWave_(*ch.get(), std::move(w)); /* Then add new channel to Channel list. */ @@ -258,14 +263,14 @@ void cloneChannel(ID channelId) /* Clone plugins, actions and wave first in their own lists. */ #ifdef WITH_VST - pluginHost::clonePlugins(oldChannel, *newChannel.get()); + newChannel->pluginIds = pluginHost::clonePlugins(oldChannel.pluginIds); #endif recorderHandler::cloneActions(channelId, newChannel->id); - if (newChannel->hasData()) { - SampleChannel* sch = static_cast(newChannel.get()); - Wave& wave = model::get(model::waves, sch->waveId); - pushWave_(*sch, waveManager::createFromWave(wave, 0, wave.getSize()), /*clone=*/true); + if (newChannel->samplePlayer && newChannel->samplePlayer->hasWave()) + { + Wave& wave = model::get(model::waves, newChannel->samplePlayer->getWaveId()); + pushWave_(*newChannel, waveManager::createFromWave(wave, 0, wave.getSize())); } /* Then push the new channel in the channels list. */ @@ -279,22 +284,19 @@ void cloneChannel(ID channelId) void freeChannel(ID channelId) { - bool hasWave; - ID waveId; + ID waveId; /* Remove Wave reference from Channel. */ model::onSwap(model::channels, channelId, [&](Channel& c) { - SampleChannel& sc = static_cast(c); - hasWave = sc.hasWave; - waveId = sc.waveId; - sc.empty(); + waveId = c.samplePlayer->getWaveId(); + c.samplePlayer->loadWave(nullptr); }); /* Then remove the actual Wave, if any. */ - if (hasWave) + if (waveId != 0) model::waves.pop(model::getIndex(model::waves, waveId)); } @@ -304,8 +306,13 @@ void freeChannel(ID channelId) void freeAllChannels() { - for (size_t i = 0; i < model::channels.size(); i++) - model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.empty(); }); + for (ID id : getChannelsWithWave_()) { + model::onSwap(model::channels, id, [](Channel& c) + { + c.samplePlayer->loadWave(nullptr); + }); + } + model::waves.clear(); } @@ -315,27 +322,22 @@ void freeAllChannels() void deleteChannel(ID channelId) { - bool hasWave = false; ID waveId; #ifdef WITH_VST std::vector 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(c); - hasWave = sc.hasWave; - waveId = sc.waveId; + waveId = c.samplePlayer ? c.samplePlayer->getWaveId() : 0; }); model::channels.pop(model::getIndex(model::channels, channelId)); - if (hasWave) + if (waveId != 0) model::waves.pop(model::getIndex(model::waves, waveId)); #ifdef WITH_VST @@ -349,69 +351,10 @@ void deleteChannel(ID channelId) void renameChannel(ID channelId, const std::string& name) { - model::onSwap(model::channels, channelId, [&](Channel& c) { c.name = name; }); -} - - -/* -------------------------------------------------------------------------- */ - - -void startSequencer() -{ - switch (clock::getStatus()) { - case ClockStatus::STOPPED: - clock::setStatus(ClockStatus::RUNNING); - break; - case ClockStatus::WAITING: - clock::setStatus(ClockStatus::RUNNING); - recManager::stopActionRec(); - break; - default: - break; - } - -#ifdef __linux__ - kernelAudio::jackStart(); -#endif -} - - -/* -------------------------------------------------------------------------- */ - - -void stopSequencer() -{ - clock::setStatus(ClockStatus::STOPPED); - - /* Stop channels with explicit locks. The RAII version would trigger a - deadlock if recManager::stopInputRec() is called down below. */ - - model::channels.lock(); - for (Channel* c : model::channels) - c->stopBySeq(conf::conf.chansStopOnSeqHalt); - model::channels.unlock(); - -#ifdef __linux__ - kernelAudio::jackStop(); -#endif - - /* If recordings (both input and action) are active deactivate them, but - store the takes. RecManager takes care of it. */ - - if (recManager::isRecordingAction()) - recManager::stopActionRec(); - else - if (recManager::isRecordingInput()) - recManager::stopInputRec(); -} - - -/* -------------------------------------------------------------------------- */ - - -void toggleSequencer() -{ - clock::isRunning() ? stopSequencer() : startSequencer(); + model::onGet(model::channels, channelId, [&](Channel& c) + { + c.state->name = name; + }, /*rebuild=*/true); } @@ -420,9 +363,11 @@ void toggleSequencer() void updateSoloCount() { - model::onSwap(model::mixer, [&](model::Mixer& m) + model::onSwap(model::mixer, [](model::Mixer& m) { - m.hasSolos = channelHas_([](const Channel* ch) { return ch->solo; }); + m.hasSolos = anyChannel_([](const Channel* ch) { + return !ch->isInternal() && ch->state->solo.load() == true; + }); }); } @@ -430,24 +375,6 @@ void updateSoloCount() /* -------------------------------------------------------------------------- */ -void setInVol(float v) -{ - model::onGet(model::channels, mixer::MASTER_IN_CHANNEL_ID, [&](Channel& c) - { - c.volume = v; - }); -} - - -void setOutVol(float v) -{ - model::onGet(model::channels, mixer::MASTER_OUT_CHANNEL_ID, [&](Channel& c) - { - c.volume = v; - }); -} - - void setInToOut(bool v) { model::onSwap(model::mixer, [&](model::Mixer& m) @@ -463,14 +390,14 @@ void setInToOut(bool v) float getInVol() { model::ChannelsLock l(model::channels); - return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).volume; + return model::get(model::channels, mixer::MASTER_IN_CHANNEL_ID).state->volume.load(); } float getOutVol() { model::ChannelsLock l(model::channels); - return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).volume; + return model::get(model::channels, mixer::MASTER_OUT_CHANNEL_ID).state->volume.load(); } @@ -482,58 +409,13 @@ bool getInToOut() /* -------------------------------------------------------------------------- */ - -void rewindSequencer() -{ - if (clock::getQuantize() > 0 && clock::isRunning()) // quantize rewind - mixer::rewindWait = true; - else { - clock::rewind(); - rewindChannels(); - } - - /* FIXME - potential desync when Quantizer is enabled from this point on. - Mixer would wait, while the following calls would be made regardless of its - state. */ - -#ifdef __linux__ - kernelAudio::jackSetPosition(0); -#endif - - if (conf::conf.midiSync == MIDI_SYNC_CLOCK_M) - kernelMidi::send(MIDI_POSITION_PTR, 0, 0); -} - - -/* -------------------------------------------------------------------------- */ - - -void rewindChannels() -{ - for (size_t i = 3; i < model::channels.size(); i++) - model::onSwap(model::channels, model::getId(model::channels, i), [&](Channel& c) { c.rewindBySeq(); }); -} - - -/* -------------------------------------------------------------------------- */ - - /* Push a new Wave into each recordable channel. Warning: this algorithm will require some changes when we will allow overdubbing (the previous existing Wave has to be overwritten somehow). */ void finalizeInputRec() { - const AudioBuffer& virtualInput = mixer::getVirtualInput(); - - /* Can't loop with foreach, as it would require a lock on model::channels - list which would deadlock during the model::channels::swap() call below. - Also skip channels 0, 1 and 2: they are MASTER_IN, MASTER_OUT and PREVIEW. */ - - for (size_t i = 3; i < model::channels.size(); i++) { - - if (!canInputRec_(i)) - continue; + for (ID id : getRecordableChannels_()) { /* Create a new Wave with audio coming from Mixer's virtual input. */ @@ -542,22 +424,21 @@ void finalizeInputRec() std::unique_ptr wave = 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(c); - pushWave_(sc, std::move(wave), /*clone=*/false); - if (sc.isAnyLoopMode()) - sc.playStatus = ChannelStatus::PLAY; - }); + pushWave_(c, std::move(wave)); + if (c.samplePlayer->state->isAnyLoopMode()) + c.samplePlayer->kickIn(clock::getCurrentFrame()); + }); } - mixer::clearVirtualInput(); + mixer::clearRecBuffer(); } @@ -566,30 +447,39 @@ void finalizeInputRec() bool hasRecordableSampleChannels() { - return channelHas_([](const Channel* ch) { return ch->canInputRec(); }); + return anyChannel_([](const Channel* ch) { return ch->canInputRec(); }); } bool hasLogicalSamples() { - return channelHas_([](const Channel* ch) { return ch->hasLogicalData(); }); + return anyChannel_([](const Channel* ch) + { + return ch->samplePlayer && ch->samplePlayer->hasLogicalWave(); } + ); } bool hasEditedSamples() { - return channelHas_([](const Channel* ch) { return ch->hasEditedData(); }); + return anyChannel_([](const Channel* ch) + { + return ch->samplePlayer && ch->samplePlayer->hasEditedWave(); + }); } bool hasActions() { - return channelHas_([](const Channel* ch) { return ch->hasActions; }); + return anyChannel_([](const Channel* ch) { return ch->state->hasActions; }); } bool hasAudioData() { - return channelHas_([](const Channel* ch) { return ch->hasData(); }); + return anyChannel_([](const Channel* ch) + { + return ch->samplePlayer && ch->samplePlayer->hasWave(); + }); } }}}; // giada::m::mh:: diff --git a/src/core/mixerHandler.h b/src/core/mixerHandler.h index bbf84a9..6598772 100644 --- a/src/core/mixerHandler.h +++ b/src/core/mixerHandler.h @@ -57,7 +57,7 @@ void close(); Adds a new channel of type 'type' into the channels stack. Returns the new channel ID. */ -ID addChannel(ChannelType type, ID columnId); +void addChannel(ChannelType type, ID columnId); /* loadChannel Loads a new Wave inside a Sample Channel. */ @@ -88,15 +88,7 @@ void cloneChannel(ID channelId); void renameChannel(ID channelId, const std::string& name); void freeAllChannels(); -void startSequencer(); -void stopSequencer(); -void toggleSequencer(); -void rewindSequencer(); -void rewindChannels(); - void setInToOut(bool v); -void setInVol(float f); -void setOutVol(float f); /* updateSoloCount Updates the number of solo-ed channels in mixer. */ @@ -109,12 +101,6 @@ session. */ void finalizeInputRec(); -/* uniqueSamplePath -Returns true if path 'p' is unique. Requires SampleChannel 'skip' in order -to skip check against itself. */ - -bool uniqueSamplePath(ID channelToSkip, const std::string& p); - /* hasLogicalSamples True if 1 or more samples are logical (memory only, such as takes) */ diff --git a/src/core/model/model.cpp b/src/core/model/model.cpp index 2fff2e6..3de8e8c 100644 --- a/src/core/model/model.cpp +++ b/src/core/model/model.cpp @@ -27,9 +27,7 @@ #include #include "core/model/model.h" -#ifndef NDEBUG -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" +#ifdef G_DEBUG_MODE #include "core/channels/channelManager.h" #endif @@ -44,7 +42,7 @@ RCUList kernel(std::make_unique()); RCUList recorder(std::make_unique()); RCUList midiIn(std::make_unique()); RCUList actions(std::make_unique()); -RCUList channels; +RCUList channels; RCUList waves; #ifdef WITH_VST RCUList plugins; @@ -60,7 +58,7 @@ Actions::Actions(const Actions& o) : map(o.map) } -#ifndef NDEBUG +#ifdef G_DEBUG_MODE void debug() { @@ -78,14 +76,17 @@ void debug() int i = 0; for (const Channel* c : channels) { - printf(" %d) %p - ID=%d name='%s' columnID=%d\n", i++, (void*)c, c->id, c->name.c_str(), c->columnId); + printf("\t%d) %p - ID=%d name='%s' type=%d columnID=%d\n", + i++, (void*) c, c->state->id, c->state->name.c_str(), (int) c->getType(), c->getColumnId()); +/* if (c->hasData()) - printf(" wave: ID=%d\n", static_cast(c)->waveId); + printf("\t\twave: ID=%d\n", static_cast(c)->waveId); +*/ #ifdef WITH_VST if (c->pluginIds.size() > 0) { - puts(" plugins:"); + puts("\t\tplugins:"); for (ID id : c->pluginIds) - printf(" ID=%d\n", id); + printf("\t\t\tID=%d\n", id); } #endif } @@ -94,7 +95,7 @@ void debug() i = 0; for (const Wave* w : waves) - printf(" %d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str()); + printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)w, w->id, w->getPath().c_str()); #ifdef WITH_VST puts("model::plugins"); @@ -102,31 +103,31 @@ void debug() i = 0; for (const Plugin* p : plugins) { if (p->valid) - printf(" %d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str()); + printf("\t%d) %p - ID=%d name='%s'\n", i++, (void*)p, p->id, p->getName().c_str()); else - printf(" %d) %p - ID=%d INVALID\n", i++, (void*)p, p->id); + printf("\t%d) %p - ID=%d INVALID\n", i++, (void*)p, p->id); } #endif puts("model::clock"); - printf(" clock.status = %d\n", static_cast(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(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:: diff --git a/src/core/model/model.h b/src/core/model/model.h index 15dd75f..ace0ce5 100644 --- a/src/core/model/model.h +++ b/src/core/model/model.h @@ -30,8 +30,9 @@ #include -#include +#include "core/model/traits.h" #include "core/channels/channel.h" +#include "core/channels/state.h" #include "core/const.h" #include "core/wave.h" #include "core/plugin.h" @@ -43,6 +44,44 @@ namespace giada { namespace m { namespace model { +namespace +{ +/* getIter_ +Returns an iterator of an element from list 'list' with given ID. */ + +template +auto getIter_(L& list, ID id) +{ + static_assert(has_id(), "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 +void onSwapByIndex_(L& list, std::size_t i, std::function f) +{ + std::unique_ptr o = list.clone(i); + f(*o.get()); + list.swap(std::move(o), i); +} +} // {anonymous} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + struct Clock { ClockStatus status = ClockStatus::STOPPED; @@ -127,53 +166,45 @@ extern RCUList plugins; #endif -/* ---------------------------------------------------------------------------*/ - - -template struct has_id : std::false_type {}; -template <> struct has_id : std::true_type {}; -template <> struct has_id : std::true_type {}; -#ifdef WITH_VST -template <> struct has_id : std::true_type {}; -#endif - -template struct is_copyable : std::true_type {}; -template <> struct is_copyable : std::false_type {}; - - /* -------------------------------------------------------------------------- */ template -auto getIter(L& list, ID id) +bool exists(L& list, ID id) { - static_assert(has_id(), "This type has no ID"); + static_assert(has_id(), "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 -size_t getIndex(L& list, ID id) +std::size_t getIndex(L& list, ID id) { static_assert(has_id(), "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 -ID getId(L& list, size_t i) +ID getId(L& list, std::size_t i) { static_assert(has_id(), "This type has no ID"); typename L::Lock l(list); @@ -188,26 +219,28 @@ template typename L::value_type& get(L& list, ID id) { static_assert(has_id(), "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 -void onGet(L& list, ID id, std::function f) +void onGet(L& list, ID id, std::function f, bool rebuild=false) { static_assert(has_id(), "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 @@ -222,60 +255,18 @@ void onGet(L& list, std::function f) /* ---------------------------------------------------------------------------*/ -template -void onSwapByIndex_(L& list, size_t i, std::function f) -{ - std::unique_ptr o = list.clone(i); - f(*o.get()); - list.swap(std::move(o), i); -} - -/* onSwapById_ (1) -Regular version for copyable types. */ - -template -void onSwapById_(L& list, ID id, std::function f, - const std::true_type& /*is_copyable=true*/) -{ - static_assert(has_id(), "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 -void onSwapById_(L& list, ID id, std::function f, - const std::false_type& /*is_copyable=false*/) -{ - static_assert(has_id(), "This type has no ID"); - - size_t i = getIndex(list, id); - - list.lock(); - std::unique_ptr 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 void onSwap(L& list, ID id, std::function f) { static_assert(has_id(), "This type has no ID"); - onSwapById_(list, id, f, is_copyable()); + onSwapByIndex_(list, getIndex(list, id), f); } -/* onSwap (2) +/* onSwap (2) (thread safe) Utility function for swapping things in a RCUList when the list contains only a single element (and so with no ID). */ @@ -287,10 +278,10 @@ void onSwap(L& list, std::function f) } -/* ---------------------------------------------------------------------------*/ +/* ---------------------------------------------------------------------------*/ -#ifndef NDEBUG +#ifdef G_DEBUG_MODE void debug(); diff --git a/src/core/model/storage.cpp b/src/core/model/storage.cpp index dbbda73..3fc6d39 100644 --- a/src/core/model/storage.cpp +++ b/src/core/model/storage.cpp @@ -28,14 +28,13 @@ #include #include "core/model/model.h" #include "core/channels/channelManager.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/kernelAudio.h" #include "core/patch.h" #include "core/conf.h" #include "core/pluginManager.h" #include "core/recorderHandler.h" #include "core/waveManager.h" +#include "core/sequencer.h" #include "core/model/storage.h" @@ -43,6 +42,16 @@ namespace giada { namespace m { namespace model { +namespace +{ +} // {anonymous} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + void store(patch::Patch& patch) { #ifdef WITH_VST @@ -57,7 +66,7 @@ void store(patch::Patch& patch) patch.beats = clock.get()->beats; patch.bpm = clock.get()->bpm; patch.quantize = clock.get()->quantize; - patch.metronome = mixer::isMetronomeOn(); // TODO - not here + patch.metronome = sequencer::isMetronomeOn(); // TODO - not here #ifdef WITH_VST for (const Plugin* p : plugins) @@ -113,20 +122,35 @@ void load(const patch::Patch& patch) { a.map = std::move(recorderHandler::deserializeActions(patch.actions)); }); + #ifdef WITH_VST for (const patch::Plugin& pplugin : patch.plugins) plugins.push(pluginManager::deserializePlugin(pplugin)); #endif - for (const patch::Wave& pwave : patch.waves) - waves.push(std::move(waveManager::deserializeWave(pwave))); - - for (const patch::Channel& pchannel : patch.channels) { - if (pchannel.type == ChannelType::MASTER || pchannel.type == ChannelType::PREVIEW) - onSwap(channels, pchannel.id, [&](Channel& ch) { ch.load(pchannel); }); + for (const patch::Wave& pwave : patch.waves) { + std::unique_ptr w = waveManager::deserializeWave(pwave); + if (w != nullptr) + waves.push(std::move(w)); + } + + channels.clear(); + for (const patch::Channel& pchannel : patch.channels) + channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize())); + + /* Load Waves into Channels. */ + + ChannelsLock cl(channels); + WavesLock wl(waves); + + for (Channel* c : channels) { + if (!c->samplePlayer) + continue; + if (exists(waves, c->samplePlayer->getWaveId())) + c->samplePlayer->setWave(get(waves, c->samplePlayer->getWaveId())); else - channels.push(channelManager::deserializeChannel(pchannel, kernelAudio::getRealBufSize())); - } + c->samplePlayer->setInvalidWave(); + } } diff --git a/src/core/model/traits.h b/src/core/model/traits.h new file mode 100644 index 0000000..d5cccef --- /dev/null +++ b/src/core/model/traits.h @@ -0,0 +1,51 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_MODEL_TRAITS_H +#define G_MODEL_TRAITS_H + + +#include +#include "core/wave.h" +#include "core/plugin.h" +#include "core/channels/channel.h" + + +namespace giada { +namespace m { +namespace model +{ +template struct has_id : std::false_type {}; +template <> struct has_id : std::true_type {}; +template <> struct has_id : std::true_type {}; +#ifdef WITH_VST +template <> struct has_id : std::true_type {}; +#endif +}}} // giada::m::model:: + + +#endif diff --git a/src/core/patch.cpp b/src/core/patch.cpp index 671d7cf..84ad90e 100644 --- a/src/core/patch.cpp +++ b/src/core/patch.cpp @@ -149,11 +149,11 @@ void readChannels_(const nl::json& j) if (!j.contains(PATCH_KEY_CHANNELS)) return; - ID id = mixer::PREVIEW_CHANNEL_ID; + ID defaultId = mixer::PREVIEW_CHANNEL_ID; for (const auto& jchannel : j[PATCH_KEY_CHANNELS]) { Channel c; - c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++id); + c.id = jchannel.value(PATCH_KEY_CHANNEL_ID, ++defaultId); c.type = static_cast(jchannel.value(PATCH_KEY_CHANNEL_TYPE, 1)); c.volume = jchannel.value(PATCH_KEY_CHANNEL_VOLUME, G_DEFAULT_VOL); c.height = jchannel.value(PATCH_KEY_CHANNEL_SIZE, G_GUI_UNIT); @@ -178,7 +178,7 @@ void readChannels_(const nl::json& j) c.midiOutLmute = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_MUTE, 0); c.midiOutLsolo = jchannel.value(PATCH_KEY_CHANNEL_MIDI_OUT_L_SOLO, 0); c.armed = jchannel.value(PATCH_KEY_CHANNEL_ARMED, false); - c.mode = static_cast(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1)); + c.mode = static_cast(jchannel.value(PATCH_KEY_CHANNEL_MODE, 1)); c.waveId = jchannel.value(PATCH_KEY_CHANNEL_WAVE_ID, 0); c.begin = jchannel.value(PATCH_KEY_CHANNEL_BEGIN, 0); c.end = jchannel.value(PATCH_KEY_CHANNEL_END, 0); @@ -366,6 +366,32 @@ void writeChannels_(nl::json& j) j[PATCH_KEY_CHANNELS].push_back(jchannel); } } + + +/* -------------------------------------------------------------------------- */ + + +void modernize_() +{ + /* 0.16.3 + - Make sure that ChannelType is correct: ID 1, 2 are MASTER channels, + ID 3 is PREVIEW channel; + - set panning to default (0.5) and waveId to 0 for non-Sample Channels. */ + + for (Channel& c : patch.channels) { + + if (c.id == mixer::MASTER_OUT_CHANNEL_ID || c.id == mixer::MASTER_IN_CHANNEL_ID) + c.type = ChannelType::MASTER; + else + if (c.id == mixer::PREVIEW_CHANNEL_ID) + c.type = ChannelType::PREVIEW; + + if (c.type != ChannelType::SAMPLE) { + c.pan = G_DEFAULT_PAN; + c.waveId = 0; + } + } +} }; // {anonymous} @@ -460,6 +486,7 @@ int read(const std::string& file, const std::string& basePath) readWaves_(j, basePath); readActions_(j); readChannels_(j); + modernize_(); } catch (nl::json::exception& e) { u::log::print("[patch::read] Exception thrown: %s\n", e.what()); diff --git a/src/core/patch.h b/src/core/patch.h index 57ab057..7e0fa13 100644 --- a/src/core/patch.h +++ b/src/core/patch.h @@ -69,7 +69,7 @@ struct Channel bool mute; bool solo; float volume = G_DEFAULT_VOL; - float pan = 0.5f; + float pan = G_DEFAULT_PAN; bool hasActions; bool armed; bool midiIn; @@ -86,17 +86,17 @@ struct Channel uint32_t midiOutLmute; uint32_t midiOutLsolo; // sample channel - ID waveId; - ChannelMode mode; - Frame begin; - Frame end; - Frame shift; - bool readActions; - float pitch = G_DEFAULT_PITCH; - bool inputMonitor; - bool midiInVeloAsVol; - uint32_t midiInReadActions; - uint32_t midiInPitch; + ID waveId = 0; + SamplePlayerMode mode; + Frame begin; + Frame end; + Frame shift; + bool readActions; + float pitch = G_DEFAULT_PITCH; + bool inputMonitor; + bool midiInVeloAsVol; + uint32_t midiInReadActions; + uint32_t midiInPitch; // midi channel bool midiOut; int midiOutChan; diff --git a/src/core/pluginHost.cpp b/src/core/pluginHost.cpp index 32644b4..458eee8 100644 --- a/src/core/pluginHost.cpp +++ b/src/core/pluginHost.cpp @@ -148,7 +148,6 @@ void processStack(AudioBuffer& outBuf, const std::vector& pluginIds, else { audioBuffer_.clear(); processPlugins_(pluginIds, *events); - } juceToGiadaOutBuf_(outBuf); } @@ -209,11 +208,12 @@ void freePlugins(const std::vector& pluginIds) /* -------------------------------------------------------------------------- */ -void clonePlugins(const Channel& oldChannel, Channel& newChannel) +std::vector clonePlugins(std::vector pluginIds) { - newChannel.pluginIds.clear(); - for (ID id : oldChannel.pluginIds) - newChannel.pluginIds.push_back(clonePlugin_(id)); + std::vector out; + for (ID id : pluginIds) + out.push_back(clonePlugin_(id)); + return out; } diff --git a/src/core/pluginHost.h b/src/core/pluginHost.h index c362562..b8683cc 100644 --- a/src/core/pluginHost.h +++ b/src/core/pluginHost.h @@ -41,9 +41,7 @@ namespace giada { namespace m { class Plugin; -class Channel; class AudioBuffer; - namespace pluginHost { using Stack = std::vector>; @@ -78,9 +76,10 @@ Unloads multiple plugins. Useful when freeing or deleting a channel. */ void freePlugins(const std::vector& 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 clonePlugins(std::vector pluginIds); void setPluginParameter(ID pluginId, int paramIndex, float value); diff --git a/src/core/quantizer.cpp b/src/core/quantizer.cpp new file mode 100644 index 0000000..7be2fa1 --- /dev/null +++ b/src/core/quantizer.cpp @@ -0,0 +1,97 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#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 f) +{ + assert(id >= 0); + assert(id < (int) m_callbacks.size()); + + m_callbacks[id] = f; +} + + +/* -------------------------------------------------------------------------- */ + + +void Quantizer::advance(Range block, Frame quantizerStep) +{ + /* Nothing to do if there's no action to perform. */ + + if (m_performId == -1) + return; + + assert(m_callbacks[m_performId] != nullptr); + + for (Frame global = block.getBegin(), local = 0; global < block.getEnd(); global++, local++) { + + if (global % quantizerStep != 0) // Skip if it's not on a quantization unit. + continue; + + m_callbacks[m_performId](local); + m_performId = -1; + return; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void Quantizer::clear() +{ + m_performId = -1; +} + + +/* -------------------------------------------------------------------------- */ + + +bool Quantizer::isTriggered() const +{ + return m_performId != -1; +} +}} // giada::m:: \ No newline at end of file diff --git a/src/core/quantizer.h b/src/core/quantizer.h new file mode 100644 index 0000000..e9973b7 --- /dev/null +++ b/src/core/quantizer.h @@ -0,0 +1,83 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_QUANTIZER_H +#define G_QUANTIZER_H + + +#include +#include +#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); + + /* 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 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, G_MAX_QUANTIZER_SIZE> m_callbacks; + int m_performId = -1; +}; +}} // giada::m:: + + +#endif diff --git a/src/core/queue.h b/src/core/queue.h index 72c10eb..6edf1c5 100644 --- a/src/core/queue.h +++ b/src/core/queue.h @@ -39,7 +39,7 @@ namespace m /* Queue Single producer, single consumer lock-free queue. */ -template +template class Queue { public: @@ -54,7 +54,7 @@ public: bool pop(T& item) { - size_t curr = m_head.load(); + std::size_t curr = m_head.load(); if (curr == m_tail.load()) // Queue empty, nothing to do return false; @@ -66,8 +66,8 @@ public: bool push(const T& item) { - size_t curr = m_tail.load(); - size_t next = increment(curr); + std::size_t curr = m_tail.load(); + std::size_t next = increment(curr); if (next == m_head.load()) // Queue full, nothing to do return false; @@ -79,15 +79,15 @@ public: private: - size_t increment(size_t i) const + std::size_t increment(std::size_t i) const { return (i + 1) % size; } std::array m_data; - std::atomic m_head; - std::atomic m_tail; + std::atomic m_head; + std::atomic m_tail; }; }} // giada::m:: diff --git a/src/core/range.h b/src/core/range.h index ae48fb3..96e9106 100644 --- a/src/core/range.h +++ b/src/core/range.h @@ -37,11 +37,6 @@ namespace giada template class Range { -private: - - T m_a; - T m_b; - public: Range() : m_a(0), m_b(0) {} @@ -50,6 +45,11 @@ public: T getBegin() const { return m_a; } T getEnd() const { return m_b; } T getLength() const { return m_b - m_a; } + +private: + + T m_a; + T m_b; }; } // giada:: diff --git a/src/core/rcuList.h b/src/core/rcuList.h index c5be299..0050489 100644 --- a/src/core/rcuList.h +++ b/src/core/rcuList.h @@ -39,25 +39,26 @@ namespace giada { namespace m { +/* RCUList +Single producer, multiple consumer (i.e. one writer, many readers) RCU-based +list. */ + template 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& r) : rcu(r) - { - rcu.lock(); - } - - ~Lock() - { - rcu.unlock(); - } + Lock(RCUList& r) : rcu(r) { rcu.lock(); } + Lock(const Lock&) = delete; + Lock& operator=(const Lock&) = delete; + ~Lock() { rcu.unlock(); } RCUList& rcu; }; @@ -176,14 +177,13 @@ public: void unlock() { m_readers[t_grace]--; + assert(m_readers[t_grace] >= 0 && "Negative reader"); } /* get Returns a reference to the data held by node 'i'. */ - // TODO - this will return a const ref with the non-virtual Channel - // refactoring. - T* get(size_t i=0) const + T* get(std::size_t i=0) const { assert(i < size() && "Index overflow"); assert(m_readers[t_grace].load() > 0 && "Forgot lock before reading"); @@ -192,18 +192,14 @@ public: /* Subscript operator [] Same as above for the [] syntax. */ - // TODO - this will return a const ref with the non-virtual Channel - // refactoring. - T* operator[] (size_t i) const + T* operator[] (std::size_t i) const { return get(i); } /* back Return data held by the last node. */ - // TODO - this will return a const ref with the non-virtual Channel - // refactoring. T* back() const { @@ -212,19 +208,13 @@ public: } /* clone - Returns a new copy of the data held by node 'i'. The template machinery - is required for when you declare a RCUList and later on want to clone - a derived object. Usage: - - RCUList list; - ... - std::unique_ptr d = list.clone(i); - */ + Returns a new copy of the data held by node 'i'. */ - template - std::unique_ptr clone(size_t i=0) const + std::unique_ptr clone(std::size_t i=0) const { - return std::make_unique(*static_cast(getNode(i)->data.get())); + /* Make sure no one is writing (swapping, popping, pushing). */ + assert(m_writing.load() == false); + return std::make_unique(*getNode(i)->data.get()); } /* swap @@ -233,7 +223,7 @@ public: multiple calls to swap() made by the same thread: the caller is blocked by the spinlock below: no progress is made until m_readers[oldgrace] > 0. */ - void swap(std::unique_ptr data, size_t i=0) + void swap(std::unique_ptr data, std::size_t i=0) { /* Never start two overlapping writing sessions. */ @@ -339,7 +329,7 @@ public: calls to pop() made by the same thread: the caller is blocked by the spinlock below: no progress is made while m_readers[oldgrace] > 0. */ - void pop(size_t i) + void pop(std::size_t i) { /* Never start two overlapping writing sessions. */ @@ -449,7 +439,7 @@ public: /* size Returns the number of nodes in the list. */ - size_t size() const + std::size_t size() const { return m_size.load(); } @@ -467,9 +457,9 @@ public: private: - Node* getNode(size_t i) const + Node* getNode(std::size_t i) const { - size_t p = 0; + std::size_t p = 0; Node* curr = m_head.load(); while (curr != nullptr && p < i) { @@ -482,7 +472,7 @@ private: std::array, 2> m_readers; std::atomic m_grace; - std::atomic m_size; + std::atomic m_size; std::atomic m_writing; /* m_head diff --git a/src/core/recManager.cpp b/src/core/recManager.cpp index 29865b4..a66f7cf 100644 --- a/src/core/recManager.cpp +++ b/src/core/recManager.cpp @@ -26,13 +26,13 @@ #include "gui/dispatcher.h" -#include "core/channels/channel.h" #include "core/model/model.h" #include "core/types.h" #include "core/clock.h" #include "core/kernelAudio.h" #include "core/conf.h" #include "core/mixer.h" +#include "core/sequencer.h" #include "core/mixerHandler.h" #include "core/midiDispatcher.h" #include "core/recorder.h" @@ -72,7 +72,7 @@ bool startActionRec_() if (!kernelAudio::isReady()) return false; clock::setStatus(ClockStatus::RUNNING); - m::mh::startSequencer(); + sequencer::start(); return true; } @@ -85,7 +85,7 @@ bool startInputRec_() if (!kernelAudio::isReady() || !mh::hasRecordableSampleChannels()) return false; mixer::startInputRec(); - mh::startSequencer(); + sequencer::start(); return true; } } // {anonymous} @@ -147,7 +147,7 @@ void stopActionRec() if (clock::getStatus() == ClockStatus::WAITING) { clock::setStatus(ClockStatus::STOPPED); - m::midiDispatcher::setSignalCallback(nullptr); + midiDispatcher::setSignalCallback(nullptr); v::dispatcher::setSignalCallback(nullptr); return; } @@ -158,11 +158,12 @@ void stopActionRec() actions. Start reading right away, without checking whether conf::treatRecsAsLoops is enabled or not. */ - for (ID id : channels) - m::model::onGet(m::model::channels, id, [](Channel& c) + for (ID id : channels) { + model::onGet(model::channels, id, [](Channel& c) { - c.startReadingActions(/*treatRecsAsLoops=*/false, /*recsStopOnChanHalt=*/false); + c.state->readActions.store(true); }); + } } diff --git a/src/core/recorder.cpp b/src/core/recorder.cpp index 92855d1..bbb205e 100644 --- a/src/core/recorder.cpp +++ b/src/core/recorder.cpp @@ -30,7 +30,6 @@ #include #include "utils/log.h" #include "core/model/model.h" -#include "core/channels/channel.h" #include "core/action.h" #include "core/idManager.h" #include "core/recorder.h" @@ -50,8 +49,8 @@ IdManager actionId_; Action* findAction_(ActionMap& src, ID id) { - for (auto& kv : src) - for (Action& a : kv.second) + for (auto& [frame, actions] : src) + for (Action& a : actions) if (a.id == id) return &a; assert(false); @@ -78,14 +77,31 @@ void removeIf_(std::function f) { model::onSwap(model::actions, [&](model::Actions& a) { - for (auto& kv : a.map) { - std::vector& as = kv.second; - as.erase(std::remove_if(as.begin(), as.end(), f), as.end()); - } + for (auto& [frame, actions] : a.map) + actions.erase(std::remove_if(actions.begin(), actions.end(), f), actions.end()); optimize_(a.map); updateMapPointers(a.map); }); } + +/* -------------------------------------------------------------------------- */ + + +bool exists_(ID channelId, Frame frame, const MidiEvent& event, const ActionMap& target) +{ + for (const auto& [_, actions] : target) + for (const Action& a : actions) + if (a.channelId == channelId && a.frame == frame && a.event.getRaw() == event.getRaw()) + return true; + return false; +} + + +bool exists_(ID channelId, Frame frame, const MidiEvent& event) +{ + model::ActionsLock lock(model::actions); + return exists_(channelId, frame, event, model::actions.get()->map); +} } // {anonymous} @@ -156,7 +172,8 @@ void updateKeyFrames(std::function f) { std::unique_ptr ma = model::actions.clone(); - /* Remove all existing actions: let's start from scratch. */ + /* Remove all existing actions: let's start from scratch. + TODO - so why cloning it?! */ ma->map.clear(); @@ -166,14 +183,14 @@ void updateKeyFrames(std::function f) { model::ActionsLock lock(model::actions); - for (const auto& kv : model::actions.get()->map) { - Frame frame = f(kv.first); - for (const Action& a : kv.second) { + for (const auto& [oldFrame, actions] : model::actions.get()->map) { + Frame newFrame = f(oldFrame); + for (const Action& a : actions) { Action copy = a; - copy.frame = frame; - ma->map[frame].push_back(copy); + copy.frame = newFrame; + ma->map[newFrame].push_back(copy); } - u::log::print("[recorder::updateKeyFrames] %d -> %d\n", kv.first, frame); +G_DEBUG(oldFrame << " -> " << newFrame); } } @@ -230,8 +247,8 @@ bool hasActions(ID channelId, int type) { model::ActionsLock lock(model::actions); - for (const auto& kv : model::actions.get()->map) - for (const Action& a : kv.second) + for (const auto& [frame, actions] : model::actions.get()->map) + for (const Action& a : actions) if (a.channelId == channelId && (type == 0 || type == a.event.getStatus())) return true; return false; @@ -262,6 +279,11 @@ Action makeAction(const patch::Action& a) Action rec(ID channelId, Frame frame, MidiEvent event) { + /* Skip duplicates. */ + + if (exists_(channelId, frame, event)) + return {}; + Action a = makeAction(0, channelId, frame, event); /* If key frame doesn't exist yet, the [] operator in std::map is smart @@ -280,26 +302,16 @@ Action rec(ID channelId, Frame frame, MidiEvent event) /* -------------------------------------------------------------------------- */ -void rec(std::vector& as) +void rec(std::vector& actions) { - if (as.size() == 0) + if (actions.size() == 0) return; - /* Generate new action ID and fix next and prev IDs. */ - - for (Action& a : as) { - int id = a.id; - a.id = actionId_.get(); - for (Action& aa : as) { - if (aa.prevId == id) aa.prevId = a.id; - if (aa.nextId == id) aa.nextId = a.id; - } - } - model::onSwap(model::actions, [&](model::Actions& mas) { - for (const Action& a : as) - mas.map[a.frame].push_back(a); + for (const Action& a : actions) + if (!exists_(a.channelId, a.frame, a.event, mas.map)) + mas.map[a.frame].push_back(a); updateMapPointers(mas.map); }); } @@ -393,8 +405,17 @@ void forEachAction(std::function f) { model::ActionsLock lock(model::actions); - for (auto& kv : model::actions.get()->map) - for (const Action& action : kv.second) + for (auto& [_, actions] : model::actions.get()->map) + for (const Action& action : actions) f(action); } + + +/* -------------------------------------------------------------------------- */ + + +ID getNewActionId() +{ + return actionId_.get(); +} }}}; // giada::m::recorder:: diff --git a/src/core/recorder.h b/src/core/recorder.h index 6369f90..971aff1 100644 --- a/src/core/recorder.h +++ b/src/core/recorder.h @@ -150,6 +150,12 @@ vector makes it reallocating the existing ones. Also needed in model::Data copy constructor. */ void updateMapPointers(ActionMap& src); + +/* getNewActionId +Returns a new action ID, internally generated. */ + +ID getNewActionId(); + }}}; // giada::m::recorder:: diff --git a/src/core/recorderHandler.cpp b/src/core/recorderHandler.cpp index 3b38d25..b63c596 100644 --- a/src/core/recorderHandler.cpp +++ b/src/core/recorderHandler.cpp @@ -55,8 +55,8 @@ std::vector recs_; const Action* getActionPtrById_(int id, const recorder::ActionMap& source) { - for (auto& kv : source) - for (const Action& action : kv.second) + for (const auto& [_, actions] : source) + for (const Action& action : actions) if (action.id == id) return &action; return nullptr; @@ -88,7 +88,7 @@ in linear sequence, the potential partner of 'a1' always lies beyond a1 itself. Without this trick (i.e. if it loops from vector.begin() each time) the algorithm would end up matching wrong partners. */ -void consolidate_(const Action& a1, size_t i) +void consolidate_(const Action& a1, std::size_t i) { for (auto it = recs_.begin() + i; it != recs_.end(); ++it) { @@ -201,14 +201,15 @@ bool cloneActions(ID channelId, ID newChannelId) /* -------------------------------------------------------------------------- */ -void liveRec(ID channelId, MidiEvent e) +void liveRec(ID channelId, MidiEvent e, Frame globalFrame) { assert(e.isNoteOnOff()); // Can't record any other kind of events for now + /* TODO - this might allocate on the MIDI thread */ if (recs_.size() >= recs_.capacity()) recs_.reserve(recs_.size() + MAX_LIVE_RECS_CHUNK); - recs_.push_back(recorder::makeAction(-1, channelId, clock::getCurrentFrame(), e)); + recs_.push_back(recorder::makeAction(recorder::getNewActionId(), channelId, globalFrame, e)); } @@ -234,8 +235,13 @@ std::unordered_set consolidate() void clearAllActions() { - for (size_t i = 0; i < model::channels.size(); i++) - model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) { c.hasActions = false; }); + /* TODO - disgusting */ + for (std::size_t i = 0; i < model::channels.size(); i++) { + model::onSwap(model::channels, model::getId(model::channels, i), [](Channel& c) + { + c.state->hasActions = false; + }); + } recorder::clearAll(); } diff --git a/src/core/recorderHandler.h b/src/core/recorderHandler.h index 1a56911..181200a 100644 --- a/src/core/recorderHandler.h +++ b/src/core/recorderHandler.h @@ -68,7 +68,7 @@ bool cloneActions(ID channelId, ID newChannelId); /* liveRec Records a user-generated action. NOTE_ON or NOTE_OFF only for now. */ -void liveRec(ID channelId, MidiEvent e); +void liveRec(ID channelId, MidiEvent e, Frame global); /* consolidate Records all live actions. Returns a set of channels IDs that have been diff --git a/src/core/ringBuffer.h b/src/core/ringBuffer.h new file mode 100644 index 0000000..c2520c3 --- /dev/null +++ b/src/core/ringBuffer.h @@ -0,0 +1,86 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef G_RING_BUFFER_H +#define G_RING_BUFFER_H + + +#include + + +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 +class RingBuffer +{ +public: + using iterator = typename std::array::iterator; + using const_iterator = typename std::array::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 m_data; + std::size_t m_index = 0; + std::size_t m_end = 0; +}; +} // giada:: + + +#endif + + + diff --git a/src/core/sequencer.cpp b/src/core/sequencer.cpp new file mode 100644 index 0000000..0657cd8 --- /dev/null +++ b/src/core/sequencer.cpp @@ -0,0 +1,299 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 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* as = recorder::getActionsOnFrame(global); + if (as != nullptr) + for (const Action& a : *as) + mixer::pumpEvent({ mixer::EventType::ACTION, local, a }); + } + + quantizer_.advance(Range(start, end), clock::getQuantizerStep()); +} + + +/* -------------------------------------------------------------------------- */ + + +void parse(const mixer::EventBuffer& events) +{ + for (const mixer::Event& e : events) { + if (e.type == mixer::EventType::SEQUENCER_START) { + start_(); break; + } + if (e.type == mixer::EventType::SEQUENCER_STOP) { + stop_(); break; + } + if (e.type == mixer::EventType::SEQUENCER_REWIND_REQ) { + rewind_(); break; + } + } +} + + +/* -------------------------------------------------------------------------- */ + + +void advance(AudioBuffer& outBuf) +{ + for (Frame i = 0; i < outBuf.countFrames(); i++) { + clock::sendMIDIsync(); + clock::incrCurrentFrame(); + renderMetronome_(outBuf, i); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void start() +{ + switch (clock::getStatus()) { + case ClockStatus::STOPPED: + clock::setStatus(ClockStatus::RUNNING); + break; + case ClockStatus::WAITING: + clock::setStatus(ClockStatus::RUNNING); + recManager::stopActionRec(); + break; + default: + break; + } +} + + +/* -------------------------------------------------------------------------- */ + + +void stop() +{ + clock::setStatus(ClockStatus::STOPPED); + + /* If recordings (both input and action) are active deactivate them, but + store the takes. RecManager takes care of it. */ + + if (recManager::isRecordingAction()) + recManager::stopActionRec(); + else + if (recManager::isRecordingInput()) + recManager::stopInputRec(); +} + + +/* -------------------------------------------------------------------------- */ + + +void rewind() +{ + if (clock::canQuantize()) + quantizer_.trigger(Q_ACTION_REWIND); + else + rewindQ_(/*delta=*/0); +} + + +/* -------------------------------------------------------------------------- */ + + +bool isMetronomeOn() { return metronome_.running; } +void toggleMetronome() { metronome_.running = !metronome_.running; } +void setMetronome(bool v) { metronome_.running = v; } +}}}; // giada::m::sequencer:: + + diff --git a/src/core/sequencer.h b/src/core/sequencer.h new file mode 100644 index 0000000..19c8898 --- /dev/null +++ b/src/core/sequencer.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 diff --git a/src/core/types.h b/src/core/types.h index 9d628e3..25ad080 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -35,7 +35,9 @@ using ID = int; using Pixel = int; using Frame = int; -enum class ClockStatus { STOPPED, WAITING, RUNNING }; +enum class Thread { MAIN, MIDI, AUDIO }; + +enum class ClockStatus { STOPPED, WAITING, RUNNING, ON_BEAT, ON_BAR, ON_FIRST_BEAT, VOID }; enum class ChannelType : int { SAMPLE = 1, MIDI, MASTER, PREVIEW }; @@ -44,7 +46,7 @@ enum class ChannelStatus : int ENDING = 1, WAIT, PLAY, OFF, EMPTY, MISSING, WRONG }; -enum class ChannelMode : int +enum class SamplePlayerMode : int { LOOP_BASIC = 1, LOOP_ONCE, LOOP_REPEAT, LOOP_ONCE_BAR, SINGLE_BASIC, SINGLE_PRESS, SINGLE_RETRIG, SINGLE_ENDLESS @@ -52,7 +54,6 @@ enum class ChannelMode : int enum class RecTriggerMode : int { NORMAL = 0, SIGNAL }; -enum class PreviewMode : int { NONE = 0, NORMAL, LOOP }; enum class EventType : int { AUTO = 0, MANUAL }; }; diff --git a/src/core/wave.cpp b/src/core/wave.cpp index 7e8f8c0..95859f7 100644 --- a/src/core/wave.cpp +++ b/src/core/wave.cpp @@ -161,6 +161,12 @@ void Wave::copyData(const float* data, int frames, int offset) } +void Wave::copyData(const AudioBuffer& b) +{ + buffer.copyData(b); +} + + /* -------------------------------------------------------------------------- */ diff --git a/src/core/wave.h b/src/core/wave.h index 030c4cb..13c080d 100644 --- a/src/core/wave.h +++ b/src/core/wave.h @@ -83,6 +83,7 @@ public: channels than m_channels. */ void copyData(const float* data, int frames, int offset=0); + void copyData(const AudioBuffer& b); void alloc(int size, int channels, int rate, int bits, const std::string& path); diff --git a/src/core/waveFx.cpp b/src/core/waveFx.cpp index d2cb8d1..df96d95 100644 --- a/src/core/waveFx.cpp +++ b/src/core/waveFx.cpp @@ -71,12 +71,15 @@ float getPeak_(const Wave& w, int a, int b) /* -------------------------------------------------------------------------- */ -void normalizeHard(ID waveId, int a, int b) +constexpr int SMOOTH_SIZE = 32; + + +void normalize(ID waveId, int a, int b) { model::onSwap(m::model::waves, waveId, [&](Wave& w) { float peak = getPeak_(w, a, b); - if (peak == 0.0f || peak > 1.0f) // as in ::normalizeSoft + if (peak == 0.0f || peak > 1.0f) return; for (int i=a; i #include "core/model/model.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/clock.h" #include "core/const.h" #include "core/recorderHandler.h" #include "core/recorder.h" #include "core/action.h" -#include "recorder.h" +#include "glue/events.h" +#include "glue/recorder.h" #include "actionEditor.h" @@ -63,6 +62,7 @@ void recordFirstEnvelopeAction_(ID channelId, Frame frame, int value) { namespace mr = m::recorder; + // TODO - use MidiEvent(float) m::MidiEvent e1 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, G_MAX_VELOCITY); m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value); const m::Action a1 = mr::rec(channelId, 0, e1); @@ -96,6 +96,7 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value) if (frame == -1) // Vertical points, nothing to do here return; + // TODO - use MidiEvent(float) m::MidiEvent e2 = m::MidiEvent(m::MidiEvent::ENVELOPE, 0, value); const m::Action a2 = mr::rec(channelId, frame, e2); @@ -109,10 +110,9 @@ void recordNonFirstEnvelopeAction_(ID channelId, Frame frame, int value) bool isSinglePressMode_(ID channelId) { bool b; - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c) { - const m::SampleChannel& sc = static_cast(c); - b = sc.mode == ChannelMode::SINGLE_PRESS; + b = c.samplePlayer->state->mode == SamplePlayerMode::SINGLE_PRESS; }); return b; } @@ -124,6 +124,43 @@ bool isSinglePressMode_(ID channelId) /* -------------------------------------------------------------------------- */ +SampleData::SampleData(const m::SamplePlayer& s) +: channelMode(s.state->mode.load()) +, isLoopMode (s.state->isAnyLoopMode()) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +Data::Data(const m::Channel& c) +: channelId (c.id) +, channelName(c.state->name) +, actions (m::recorder::getActionsOnChannel(c.id)) +{ + if (c.getType() == ChannelType::SAMPLE) + sample = std::make_optional(*c.samplePlayer); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Data getData(ID channelId) +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); + return Data(mm::get(mm::channels, channelId)); +} + + +/* -------------------------------------------------------------------------- */ + + void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2) { namespace mr = m::recorder; @@ -155,7 +192,6 @@ void recordMidiAction(ID channelId, int note, int velocity, Frame f1, Frame f2) void deleteMidiAction(ID channelId, const m::Action& a) { namespace mr = m::recorder; - namespace cr = c::recorder; assert(a.isValid()); assert(a.event.getStatus() == m::MidiEvent::NOTE_ON); @@ -164,12 +200,7 @@ void deleteMidiAction(ID channelId, const m::Action& a) key_on/key_off sequence. Check if 'next' exist first: could be orphaned. */ if (a.next != nullptr) { - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - m::MidiChannel& mc = static_cast(c); - if (mc.isPlaying() && !mc.mute) - mc.sendMidi(a.next->event, 0); - }); + events::sendMidiToChannel(channelId, a.next->event, Thread::MAIN); mr::deleteAction(a.id, a.next->id); } else @@ -240,8 +271,9 @@ void deleteSampleAction(ID channelId, const m::Action& a) namespace cr = c::recorder; if (a.next != nullptr) // For ChannelMode::SINGLE_PRESS combo - mr::deleteAction(a.next->id); - mr::deleteAction(a.id); + mr::deleteAction(a.id, a.next->id); + else + mr::deleteAction(a.id); recorder::updateChannel(channelId, /*updateActionEditor=*/false); } @@ -283,13 +315,15 @@ void deleteEnvelopeAction(ID channelId, const m::Action& a) /* Deleting a boundary action wipes out everything. If is volume, remember to restore _i and _d members in channel. */ /* TODO - move this to c::*/ + /* TODO - FIX*/ if (mrh::isBoundaryEnvelopeAction(a)) { if (a.isVolumeEnvelope()) { + /* m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.volume_i = 1.0; c.volume_d = 0.0; - }); + });*/ } mr::clearActions(channelId, a.event.getStatus()); } diff --git a/src/glue/actionEditor.h b/src/glue/actionEditor.h index c1414c2..4aed947 100644 --- a/src/glue/actionEditor.h +++ b/src/glue/actionEditor.h @@ -29,7 +29,9 @@ #define G_GLUE_ACTION_EDITOR_H +#include #include +#include #include "core/types.h" @@ -37,13 +39,33 @@ namespace giada { namespace m { struct Action; -class SampleChannel; -class MidiChannel; +class Channel; +class SamplePlayer; } namespace c { namespace actionEditor { -std::vector 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 actions; + + std::optional sample; +}; + +Data getData(ID channelId); /* MIDI actions. */ diff --git a/src/glue/channel.cpp b/src/glue/channel.cpp index 30eaac1..c3ecb15 100644 --- a/src/glue/channel.cpp +++ b/src/glue/channel.cpp @@ -48,9 +48,6 @@ #include "utils/gui.h" #include "utils/fs.h" #include "utils/log.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/model/model.h" #include "core/kernelAudio.h" #include "core/mixerHandler.h" @@ -60,6 +57,7 @@ #include "core/conf.h" #include "core/wave.h" #include "core/recorder.h" +#include "core/recManager.h" #include "core/plugin.h" #include "core/waveManager.h" #include "main.h" @@ -86,25 +84,107 @@ void printLoadError_(int res) else if (res == G_RES_ERR_NO_DATA) v::gdAlert("No file specified."); } +} // {anonymous} +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void onRefreshSampleEditor_(bool gui, std::function 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(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(*c.samplePlayer, *c.audioReceiver); + else + if (c.getType() == ChannelType::MIDI) + midi = std::make_optional(*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 getChannels() +{ + namespace mm = m::model; + mm::ChannelsLock cl(mm::channels); + + std::vector out; + for (const m::Channel* ch : mm::channels) + if (!ch->isInternal()) + out.push_back(Data(*ch)); + + return out; +} + + /* -------------------------------------------------------------------------- */ @@ -187,26 +267,11 @@ void freeChannel(ID channelId) /* -------------------------------------------------------------------------- */ -void setArm(ID channelId, bool value) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = value; }); -} - - -void toggleArm(ID channelId) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) { c.armed = !c.armed; }); -} - - -/* -------------------------------------------------------------------------- */ - - void setInputMonitor(ID channelId, bool value) { - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - static_cast(c).inputMonitor = value; + c.audioReceiver->state->inputMonitor.store(value); }); } @@ -223,131 +288,29 @@ void cloneChannel(ID channelId) /* -------------------------------------------------------------------------- */ -void setVolume(ID channelId, float value, bool gui, bool editor) +void setSamplePlayerMode(ID channelId, SamplePlayerMode m) { - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { c.volume = value; }); - - /* Changing channel volume? Update wave editor (if it's shown). */ - - if (editor) - onRefreshSampleEditor_(gui, [](v::gdSampleEditor* e) { e->volumeTool->rebuild(); }); - - if (!gui) { - Fl::lock(); - G_MainWin->keyboard->getChannel(channelId)->vol->value(value); - Fl::unlock(); - } -} - - -/* -------------------------------------------------------------------------- */ - - -void setPitch(ID channelId, float val, bool gui) -{ m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - static_cast(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(ch).mode = m; + c.samplePlayer->state->mode.store(m); }); - u::gui::refreshActionEditor(); -} - + /* TODO - brutal rebuild! Just rebuild the specific channel instead */ + G_MainWin->keyboard->rebuild(); -/* -------------------------------------------------------------------------- */ - - -void setSolo(ID channelId, bool value) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(value); }); - m::mh::updateSoloCount(); -} - - -void toggleSolo(ID channelId) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) { ch.setSolo(!ch.solo); }); - m::mh::updateSoloCount(); -} - -/* -------------------------------------------------------------------------- */ - - -void start(ID channelId, int velocity, bool record) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) - { - if (record && !ch.recordStart(m::clock::canQuantize())) - return; - ch.start(/*localFrame=*/0, m::clock::canQuantize(), velocity); // Frame 0: user-generated event - }); + u::gui::refreshActionEditor(); } /* -------------------------------------------------------------------------- */ -void kill(ID channelId, bool record) +void setHeight(ID channelId, Pixel p) { - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) - { - if (record && !ch.recordKill()) - return; - ch.kill(/*localFrame=*/0); // Frame 0: user-generated event - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void stop(ID channelId) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - ch.recordStop(); - ch.stop(); - }); + c.state->height = p; + }); } @@ -358,56 +321,4 @@ void setName(ID channelId, const std::string& name) { m::mh::renameChannel(channelId, name); } - - -/* -------------------------------------------------------------------------- */ - - -void toggleReadingActions(ID channelId) -{ - /* When you call startReadingRecs with conf::treatRecsAsLoops, the - member value ch->readActions actually is not set to true immediately, because - the channel is in wait mode (REC_WAITING). ch->readActions will become true on - the next first beat. So a 'stop rec' command should occur also when - ch->readActions is false but the channel is in wait mode; this check will - handle the case of when you press 'R', the channel goes into REC_WAITING and - then you press 'R' again to undo the status. */ - - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) - { - if (!ch.hasActions) - return; - if (ch.readActions || (!ch.readActions && ch.recStatus == ChannelStatus::WAIT)) - ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops, - m::conf::conf.recsStopOnChanHalt); - else - ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt); - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void startReadingActions(ID channelId) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) - { - ch.startReadingActions(m::conf::conf.treatRecsAsLoops, m::conf::conf.recsStopOnChanHalt); - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void stopReadingActions(ID channelId) -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& ch) - { - ch.stopReadingActions(m::clock::isRunning(), m::conf::conf.treatRecsAsLoops, - m::conf::conf.recsStopOnChanHalt); - }); -} - }}}; // giada::c::channel:: diff --git a/src/glue/channel.h b/src/glue/channel.h index 00b298d..2018778 100644 --- a/src/glue/channel.h +++ b/src/glue/channel.h @@ -1,4 +1,4 @@ - /* ----------------------------------------------------------------------------- + /* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * @@ -29,8 +29,11 @@ #define G_GLUE_CHANNEL_H +#include +#include #include #include +#include "core/model/model.h" #include "core/types.h" @@ -38,10 +41,100 @@ namespace giada { namespace m { class Channel; +class SamplePlayer; } namespace c { namespace channel { +struct SampleData +{ + SampleData() = delete; + SampleData(const m::SamplePlayer&, const m::AudioReceiver&); + + Frame a_getTracker() const; + Frame a_getBegin() const; + Frame a_getEnd() const; + bool a_getInputMonitor() const; + + ID waveId; + SamplePlayerMode mode; + bool isLoop; + float pitch; + +private: + + const m::SamplePlayer* m_samplePlayer; + const m::AudioReceiver* m_audioReceiver; +}; + +struct MidiData +{ + MidiData() = delete; + MidiData(const m::MidiSender&); + + bool a_isOutputEnabled() const; + int a_getFilter() const; + +private: + + const m::MidiSender* m_midiSender; +}; + +struct Data +{ + Data() = default; + Data(const m::Channel&); + + bool a_getMute() const; + bool a_getSolo() const; + ChannelStatus a_getPlayStatus() const; + ChannelStatus a_getRecStatus() const; + bool a_getReadActions() const; + bool a_isArmed() const; + bool a_isRecordingInput() const; + bool a_isRecordingAction() const; + + ID id; + ID columnId; +#ifdef WITH_VST + std::vector pluginIds; +#endif + ChannelType type; + Pixel height; + std::string name; + float volume; + float pan; + int key; + bool hasActions; + + std::optional sample; + std::optional 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 getChannels(); + +/* a_get +Returns an atomic property from a Channel, by locking it first. */ + +template +T a_get(const std::atomic& a) +{ + m::model::ChannelsLock l(m::model::channels); + return a.load(); +} + /* addChannel Adds an empty new channel to the stack. */ @@ -50,7 +143,7 @@ void addChannel(ID columnId, ChannelType type); /* loadChannel Fills an existing channel with a wave. */ -int loadChannel(ID channelId, const std::string& fname); +int loadChannel(ID columnId, const std::string& fname); /* addAndLoadChannel Adds a new Sample Channel and fills it with a wave right away. */ @@ -73,38 +166,18 @@ Unloads the sample from a sample channel. */ void freeChannel(ID channelId); /* cloneChannel -Makes an exact copy of Channel *ch. */ +Makes an exact copy of a channel. */ void cloneChannel(ID channelId); /* set* Sets several channel properties. */ -void setArm(ID channelId, bool value); -void toggleArm(ID channelId); void setInputMonitor(ID channelId, bool value); -void setMute(ID channelId, bool value); -void toggleMute(ID channelId); -void setSolo(ID channelId, bool value); -void toggleSolo(ID channelId); -void setVolume(ID channelId, float v, bool gui=true, bool editor=false); void setName(ID channelId, const std::string& name); -void setPitch(ID channelId, float val, bool gui=true); -void setPan(ID channelId, float val, bool gui=true); -void setSampleMode(ID channelId, ChannelMode m); - -void start(ID channelId, int velocity, bool record); -void kill(ID channelId, bool record); -void stop(ID channelId); - -/* toggleReadingRecs -Handles the 'R' button. If gui == true the signal comes from an user interaction -on the GUI, otherwise it's a MIDI/Jack/external signal. */ - -void toggleReadingActions(ID channelId); -void startReadingActions(ID channelId); -void stopReadingActions(ID channelId); +void setHeight(ID channelId, Pixel p); +void setSamplePlayerMode(ID channelId, SamplePlayerMode m); }}}; // giada::c::channel:: #endif diff --git a/src/glue/events.cpp b/src/glue/events.cpp new file mode 100644 index 0000000..504088c --- /dev/null +++ b/src/glue/events.cpp @@ -0,0 +1,297 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include +#include "core/model/model.h" +#include "core/const.h" +#include "core/clock.h" +#include "core/mixer.h" +#include "core/midiEvent.h" +#include "core/pluginHost.h" +#include "core/sequencer.h" +#include "core/mixerHandler.h" +#include "core/conf.h" +#include "core/recManager.h" +#include "utils/log.h" +#include "gui/dialogs/sampleEditor.h" +#include "gui/dialogs/mainWindow.h" +#include "gui/dialogs/warnings.h" +#include "gui/elems/mainWindow/mainIO.h" +#include "gui/elems/mainWindow/mainTimer.h" +#include "gui/elems/basics/dial.h" +#include "gui/elems/mainWindow/keyboard/keyboard.h" +#include "gui/elems/mainWindow/keyboard/channel.h" +#include "gui/elems/sampleEditor/volumeTool.h" +#include "gui/elems/sampleEditor/pitchTool.h" +#include "gui/elems/sampleEditor/panTool.h" +#include "glue/sampleEditor.h" +#include "glue/plugin.h" +#include "glue/main.h" +#include "events.h" + + +extern giada::v::gdMainWindow* G_MainWin; + + +namespace giada { +namespace c { +namespace events +{ +namespace +{ +void pushEvent_(m::mixer::Event e, Thread t) +{ + bool res = true; + if (t == Thread::MAIN) + res = m::mixer::UIevents.push(e); + else + if (t == Thread::MIDI) + res = m::mixer::MidiEvents.push(e); + else + assert(false); + + if (!res) + G_DEBUG("[events] Queue full!\n"); +} +} // {anonymous} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +void pressChannel(ID channelId, int velocity, Thread t) +{ + m::MidiEvent e; + e.setVelocity(velocity); + pushEvent_({ m::mixer::EventType::KEY_PRESS, 0, {0, channelId, 0, e} }, t); +} + + +void releaseChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::KEY_RELEASE, 0, {0, channelId} }, t); +} + + +void killChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::KEY_KILL, 0, {0, channelId} }, t); +} + + +/* -------------------------------------------------------------------------- */ + + +void setChannelVolume(ID channelId, float v, Thread t) +{ + v = std::clamp(v, 0.0f, G_MAX_VOLUME); + + pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, channelId, 0, {v} } }, t); + + sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.volumeTool->update(v); }); + + if (t != Thread::MAIN) { + Fl::lock(); + G_MainWin->keyboard->getChannel(channelId)->vol->value(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void setChannelPitch(ID channelId, float v, Thread t) +{ + v = std::clamp(v, G_MIN_PITCH, G_MAX_PITCH); + + pushEvent_({ m::mixer::EventType::CHANNEL_PITCH, 0, { 0, channelId, 0, {v} } }, t); + + sampleEditor::onRefresh(t == Thread::MAIN, [v](v::gdSampleEditor& e) { e.pitchTool->update(v); }); +} + + +/* -------------------------------------------------------------------------- */ + + +void sendChannelPan(ID channelId, float v) +{ + v = std::clamp(v, 0.0f, G_MAX_PAN); + + /* Pan event is currently triggered only by the main thread. */ + pushEvent_({ m::mixer::EventType::CHANNEL_PAN, 0, { 0, channelId, 0, {v} } }, Thread::MAIN); + + sampleEditor::onRefresh(/*gui=*/true, [v](v::gdSampleEditor& e) { e.panTool->update(v); }); +} + +/* -------------------------------------------------------------------------- */ + + +void toggleMuteChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_MUTE, 0, {0, channelId} }, t); +} + + +void toggleSoloChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_SOLO, 0, {0, channelId} }, t); +} + + +/* -------------------------------------------------------------------------- */ + + +void toggleArmChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_ARM, 0, {0, channelId} }, t); +} + + +void toggleReadActionsChannel(ID channelId, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_TOGGLE_READ_ACTIONS, 0, {0, channelId} }, t); +} + + +/* -------------------------------------------------------------------------- */ + + +void sendMidiToChannel(ID channelId, m::MidiEvent e, Thread t) +{ + pushEvent_({ m::mixer::EventType::MIDI, 0, {0, channelId, 0, e} }, t); +} + + +/* -------------------------------------------------------------------------- */ + + +void toggleMetronome() +{ + m::sequencer::toggleMetronome(); +} + + +/* -------------------------------------------------------------------------- */ + + +void setMasterInVolume(float v, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_IN_CHANNEL_ID, 0, {v} }}, t); + + if (t != Thread::MAIN) { + Fl::lock(); + G_MainWin->mainIO->setInVol(v); + Fl::unlock(); + } +} + + +void setMasterOutVolume(float v, Thread t) +{ + pushEvent_({ m::mixer::EventType::CHANNEL_VOLUME, 0, { 0, m::mixer::MASTER_OUT_CHANNEL_ID, 0, {v} }}, t); + + if (t != Thread::MAIN) { + Fl::lock(); + G_MainWin->mainIO->setOutVol(v); + Fl::unlock(); + } +} + + +/* -------------------------------------------------------------------------- */ + + +void multiplyBeats() +{ + main::setBeats(m::clock::getBeats() * 2, m::clock::getBars()); +} + + +void divideBeats() +{ + main::setBeats(m::clock::getBeats() / 2, m::clock::getBars()); +} + + +/* -------------------------------------------------------------------------- */ + + +void startSequencer(Thread t) +{ + pushEvent_({ m::mixer::EventType::SEQUENCER_START, 0 }, t); +} + + +void stopSequencer(Thread t) +{ + pushEvent_({ m::mixer::EventType::SEQUENCER_STOP, 0 }, t); +} + + +void toggleSequencer(Thread t) +{ + m::clock::isRunning() ? stopSequencer(t) : startSequencer(t); +} + + +void rewindSequencer(Thread t) +{ + pushEvent_({ m::mixer::EventType::SEQUENCER_REWIND_REQ, 0 }, t); +} + + +/* -------------------------------------------------------------------------- */ + + +void toggleActionRecording() +{ + m::recManager::toggleActionRec(m::conf::conf.recTriggerMode); +} + + +void toggleInputRecording() +{ + if (!m::recManager::toggleInputRec(m::conf::conf.recTriggerMode)) + v::gdAlert("No channels armed/available for audio recording."); +} + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST +void setPluginParameter(ID pluginId, int paramIndex, float value, bool gui) +{ + m::pluginHost::setPluginParameter(pluginId, paramIndex, value); + c::plugin::updateWindow(pluginId, gui); +} +#endif +}}}; // giada::c::events:: diff --git a/src/glue/events.h b/src/glue/events.h new file mode 100644 index 0000000..0949f90 --- /dev/null +++ b/src/glue/events.h @@ -0,0 +1,86 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 diff --git a/src/glue/io.cpp b/src/glue/io.cpp index 60de69f..6a40297 100644 --- a/src/glue/io.cpp +++ b/src/glue/io.cpp @@ -40,9 +40,6 @@ #include "utils/log.h" #include "utils/math.h" #include "core/model/model.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/channel.h" -#include "core/channels/midiChannel.h" #include "core/recorder.h" #include "core/conf.h" #include "core/recManager.h" @@ -67,12 +64,10 @@ namespace io { namespace { -void refreshMidiWindows_() +void rebuildMidiWindows_() { - Fl::lock(); - u::gui::refreshSubWindow(WID_MIDI_INPUT); - u::gui::refreshSubWindow(WID_MIDI_OUTPUT); - Fl::unlock(); + u::gui::rebuildSubWindow(WID_MIDI_INPUT); + u::gui::rebuildSubWindow(WID_MIDI_OUTPUT); } } // {anonymous} @@ -81,64 +76,228 @@ void refreshMidiWindows_() /* -------------------------------------------------------------------------- */ -void keyPress(ID channelId, bool ctrl, bool shift, int velocity) +Channel_InputData::Channel_InputData(const m::Channel& c) +: channelId (c.id) +, channelType (c.getType()) +, enabled (c.midiLearner.state->enabled.load()) +, velocityAsVol(c.samplePlayer ? c.samplePlayer->state->velocityAsVol.load() : 0) +, filter (c.midiLearner.state->filter.load()) +, keyPress (c.midiLearner.state->keyPress.load()) +, keyRelease (c.midiLearner.state->keyRelease.load()) +, kill (c.midiLearner.state->kill.load()) +, arm (c.midiLearner.state->arm.load()) +, volume (c.midiLearner.state->volume.load()) +, mute (c.midiLearner.state->mute.load()) +, solo (c.midiLearner.state->solo.load()) +, pitch (c.midiLearner.state->pitch.load()) +, readActions (c.midiLearner.state->readActions.load()) { - if (ctrl) - c::channel::toggleMute(channelId); - else - if (shift) - c::channel::kill(channelId, /*record=*/true); - else - c::channel::start(channelId, velocity, /*record=*/true); +#ifdef WITH_VST + for (ID id : c.pluginIds) { + m::Plugin& p = m::model::get(m::model::plugins, id); + + PluginData pd; + pd.id = p.id; + pd.name = p.getName(); + for (int i = 0; i < p.getNumParameters(); i++) + pd.params.push_back({ i, p.getParameterName(i), p.midiInParams.at(i) }); + + plugins.push_back(pd); + } +#endif +} + + +/* -------------------------------------------------------------------------- */ + + +MidiChannel_OutputData::MidiChannel_OutputData(const m::MidiSender& s) +: enabled(s.state->enabled.load()) +, filter (s.state->filter.load()) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +Channel_OutputData::Channel_OutputData(const m::Channel& c) +: channelId (c.id) +, lightningEnabled(c.midiLighter.state->enabled.load()) +, lightningPlaying(c.midiLighter.state->playing.load()) +, lightningMute (c.midiLighter.state->mute.load()) +, lightningSolo (c.midiLighter.state->solo.load()) +{ + if (c.getType() == ChannelType::MIDI) + output = std::make_optional(*c.midiSender); +} + + +/* -------------------------------------------------------------------------- */ + + +Master_InputData::Master_InputData(const m::model::MidiIn& midiIn) +: enabled (midiIn.enabled) +, filter (midiIn.filter) +, rewind (midiIn.rewind) +, startStop (midiIn.startStop) +, actionRec (midiIn.actionRec) +, inputRec (midiIn.inputRec) +, volumeIn (midiIn.volumeIn) +, volumeOut (midiIn.volumeOut) +, beatDouble(midiIn.beatDouble) +, beatHalf (midiIn.beatHalf) +, metronome (midiIn.metronome) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Channel_InputData channel_getInputData(ID channelId) +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); +#ifdef WITH_VST + mm::PluginsLock ml(mm::plugins); +#endif + + return Channel_InputData(mm::get(mm::channels, channelId)); +} + + +/* -------------------------------------------------------------------------- */ + + +Channel_OutputData channel_getOutputData(ID channelId) +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); + return Channel_OutputData(mm::get(mm::channels, channelId)); +} + + +/* -------------------------------------------------------------------------- */ + + +Master_InputData master_getInputData() +{ + namespace mm = m::model; + + mm::MidiInLock l(mm::midiIn); + return Master_InputData(*mm::midiIn.get()); +} + + +/* -------------------------------------------------------------------------- */ + + +void channel_enableMidiLearn(ID channelId, bool v) +{ + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.midiLearner.state->enabled.store(v); + }); + rebuildMidiWindows_(); } /* -------------------------------------------------------------------------- */ -void keyRelease(ID channelId, bool ctrl, bool shift) +void channel_enableMidiLightning(ID channelId, bool v) { - if (!ctrl && !shift) - c::channel::stop(channelId); + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.midiLighter.state->enabled.store(v); + }); + rebuildMidiWindows_(); } /* -------------------------------------------------------------------------- */ -void setSampleChannelKey(ID channelId, int k) +void channel_enableMidiOutput(ID channelId, bool v) { - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - c.key = k; + c.midiSender->state->enabled.store(v); + }); + rebuildMidiWindows_(); +} + + +/* -------------------------------------------------------------------------- */ + + +void channel_enableVelocityAsVol(ID channelId, bool v) +{ + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.samplePlayer->state->velocityAsVol.store(v); }); +} + + +/* -------------------------------------------------------------------------- */ + + +void channel_setMidiInputFilter(ID channelId, int ch) +{ + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.midiLearner.state->filter.store(ch); + }); +} + - Fl::lock(); - G_MainWin->keyboard->getChannel(channelId)->mainButton->setKey(k); - Fl::unlock(); +void channel_setMidiOutputFilter(ID channelId, int ch) +{ + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.midiSender->state->filter.store(ch); + }); +} + + +/* -------------------------------------------------------------------------- */ + + +void channel_setKey(ID channelId, int k) +{ + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + { + c.state->key.store(k); + }, /*rebuild=*/true); } /* -------------------------------------------------------------------------- */ -void startChannelMidiLearn(int param, ID channelId) +void channel_startMidiLearn(int param, ID channelId) { - m::midiDispatcher::startChannelLearn(param, channelId, refreshMidiWindows_); + m::midiDispatcher::startChannelLearn(param, channelId, rebuildMidiWindows_); } -void startMasterMidiLearn(int param) +void master_startMidiLearn(int param) { - m::midiDispatcher::startMasterLearn(param, refreshMidiWindows_); + m::midiDispatcher::startMasterLearn(param, rebuildMidiWindows_); } #ifdef WITH_VST -void startPluginMidiLearn(int paramIndex, ID pluginId) +void plugin_startMidiLearn(int paramIndex, ID pluginId) { - m::midiDispatcher::startPluginLearn(paramIndex, pluginId, refreshMidiWindows_); + m::midiDispatcher::startPluginLearn(paramIndex, pluginId, rebuildMidiWindows_); } #endif @@ -150,31 +309,56 @@ void startPluginMidiLearn(int paramIndex, ID pluginId) void stopMidiLearn() { m::midiDispatcher::stopLearn(); - refreshMidiWindows_(); + rebuildMidiWindows_(); } /* -------------------------------------------------------------------------- */ -void clearChannelMidiLearn(int param, ID channelId) +void channel_clearMidiLearn(int param, ID channelId) { - m::midiDispatcher::clearChannelLearn(param, channelId, refreshMidiWindows_); + m::midiDispatcher::clearChannelLearn(param, channelId, rebuildMidiWindows_); } -void clearMasterMidiLearn (int param) +void master_clearMidiLearn (int param) { - m::midiDispatcher::clearMasterLearn(param, refreshMidiWindows_); + m::midiDispatcher::clearMasterLearn(param, rebuildMidiWindows_); } #ifdef WITH_VST -void clearPluginMidiLearn (int param, ID pluginId) +void plugin_clearMidiLearn (int param, ID pluginId) { - m::midiDispatcher::clearPluginLearn(param, pluginId, refreshMidiWindows_); + m::midiDispatcher::clearPluginLearn(param, pluginId, rebuildMidiWindows_); } #endif + + +/* -------------------------------------------------------------------------- */ + + +void master_enableMidiLearn(bool v) +{ + m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m) + { + m.enabled = v; + }); + rebuildMidiWindows_(); +} + + +/* -------------------------------------------------------------------------- */ + + +void master_setMidiFilter(int c) +{ + m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m) + { + m.filter = c; + }); +} }}} // giada::c::io:: diff --git a/src/glue/io.h b/src/glue/io.h index 9c05b86..46cbd89 100644 --- a/src/glue/io.h +++ b/src/glue/io.h @@ -43,26 +43,119 @@ class Channel; namespace c { namespace io { -/* keyPress / keyRelease -Handle the key pressure, either via mouse/keyboard or MIDI. */ +struct PluginParamData +{ + int index; + std::string name; + uint32_t value; +}; + +struct PluginData +{ + ID id; + std::string name; + std::vector 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 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 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 diff --git a/src/glue/main.cpp b/src/glue/main.cpp index 22da709..0e084b8 100644 --- a/src/glue/main.cpp +++ b/src/glue/main.cpp @@ -38,7 +38,6 @@ #include "utils/string.h" #include "utils/log.h" #include "core/model/model.h" -#include "core/channels/midiChannel.h" #include "core/mixerHandler.h" #include "core/mixer.h" #include "core/clock.h" @@ -78,8 +77,8 @@ void setBpm_(float current, std::string s) float previous = m::clock::getBpm(); m::clock::setBpm(current); - m::recorderHandler::updateBpm(previous, current, m::clock::getQuanto()); - m::mixer::allocVirtualInput(m::clock::getFramesInLoop()); + m::recorderHandler::updateBpm(previous, current, m::clock::getQuantizerStep()); + m::mixer::allocRecBuffer(m::clock::getFramesInLoop()); /* This function might get called by Jack callback BEFORE the UI is up and running, that is when G_MainWin == nullptr. */ @@ -99,6 +98,82 @@ void setBpm_(float current, std::string s) /* -------------------------------------------------------------------------- */ +Timer::Timer(const m::model::Clock& c) +: bpm (c.bpm) +, beats (c.beats) +, bars (c.bars) +, quantize (c.quantize) +, isUsingJack (m::kernelAudio::getAPI() == G_SYS_API_JACK) +, isRecordingInput(m::recManager::isRecordingInput()) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +IO::IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m) +: masterOutVol (out.state->volume.load()) +, masterInVol (in.state->volume.load()) +#ifdef WITH_VST +, masterOutHasPlugins(out.pluginIds.size() > 0) +, masterInHasPlugins (in.pluginIds.size() > 0) +#endif +, inToOut (m.inToOut) +{ +} + + +/* -------------------------------------------------------------------------- */ + + +float IO::a_getMasterOutPeak() +{ + return m::mixer::peakOut.load(); +} + + +float IO::a_getMasterInPeak() +{ + return m::mixer::peakIn.load(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Timer getTimer() +{ + namespace mm = m::model; + + mm::ClockLock c(mm::clock); + return Timer(*mm::clock.get()); +} + + +/* -------------------------------------------------------------------------- */ + + +IO getIO() +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); + mm::MixerLock ml(mm::mixer); + + return IO(mm::get(mm::channels, m::mixer::MASTER_OUT_CHANNEL_ID), + mm::get(mm::channels, m::mixer::MASTER_IN_CHANNEL_ID), + *mm::mixer.get()); +} + + +/* -------------------------------------------------------------------------- */ + + void setBpm(const char* v1, const char* v2) { /* Never change this stuff while recording audio. */ @@ -152,7 +227,7 @@ void setBeats(int beats, int bars) return; m::clock::setBeats(beats, bars); - m::mixer::allocVirtualInput(m::clock::getFramesInLoop()); + m::mixer::allocRecBuffer(m::clock::getFramesInLoop()); G_MainWin->mainTimer->setMeter(m::clock::getBeats(), m::clock::getBars()); u::gui::refreshActionEditor(); // in case the action editor is open @@ -171,39 +246,9 @@ void quantize(int val) /* -------------------------------------------------------------------------- */ -void setOutVol(float v, bool gui) -{ - m::mh::setOutVol(v); - - if (!gui) { - Fl::lock(); - G_MainWin->mainIO->setOutVol(v); - Fl::unlock(); - } -} - - -/* -------------------------------------------------------------------------- */ - - -void setInVol(float v, bool gui) -{ - m::mh::setInVol(v); - - if (!gui) { - Fl::lock(); - G_MainWin->mainIO->setInVol(v); - Fl::unlock(); - } -} - - -/* -------------------------------------------------------------------------- */ - - void clearAllSamples() { - if (!v::gdConfirmWin("Warning", "Clear all samples: are you sure?")) + if (!v::gdConfirmWin("Warning", "Free all Sample channels: are you sure?")) return; G_MainWin->delSubWindow(WID_SAMPLE_EDITOR); m::clock::setStatus(ClockStatus::STOPPED); @@ -227,58 +272,20 @@ void clearAllActions() /* -------------------------------------------------------------------------- */ -void resetToInitState(bool createColumns) -{ - if (!v::gdConfirmWin("Warning", "Close project: are you sure?")) - return; - m::init::reset(); - m::mixer::enable(); -} - - -/* -------------------------------------------------------------------------- */ - - -void beatsMultiply() -{ - setBeats(m::clock::getBeats() * 2, m::clock::getBars()); -} - -void beatsDivide() -{ - setBeats(m::clock::getBeats() / 2, m::clock::getBars()); -} - - -/* -------------------------------------------------------------------------- */ - - -void toggleInputRec() +void setInToOut(bool v) { - if (!m::recManager::toggleInputRec(static_cast(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(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:: diff --git a/src/glue/main.h b/src/glue/main.h index 7802a72..c0e8490 100644 --- a/src/glue/main.h +++ b/src/glue/main.h @@ -30,9 +30,53 @@ namespace giada { +namespace m +{ +struct Channel; +namespace model +{ +struct Clock; +struct Mixer; +}} namespace c { namespace main { +struct Timer +{ + Timer() = default; + Timer(const m::model::Clock& c); + + float bpm; + int beats; + int bars; + int quantize; + bool isUsingJack; + bool isRecordingInput; +}; + +struct IO +{ + IO() = default; + IO(const m::Channel& out, const m::Channel& in, const m::model::Mixer& m); + + float masterOutVol; + float masterInVol; +#ifdef WITH_VST + bool masterOutHasPlugins; + bool masterInHasPlugins; +#endif + bool inToOut; + + float a_getMasterOutPeak(); + float a_getMasterInPeak(); +}; + +/* get* +Returns viewModel objects filled with data. */ + +Timer getTimer(); +IO getIO(); + /* setBpm (1) Sets bpm value from string to float. */ @@ -45,46 +89,19 @@ void setBpm(float v); void setBeats(int beats, int bars); void quantize(int val); -void setOutVol(float v, bool gui=true); -void setInVol(float v, bool gui=true); void clearAllSamples(); void clearAllActions(); -/* resetToInitState -Resets Giada to init state. If resetGui also refresh all widgets. If -createColumns also build initial empty columns. */ - -void resetToInitState(bool createColumns); - -/* beatsDivide/Multiply -Shrinks or enlarges the number of beats by 2. */ - -void beatsMultiply(); -void beatsDivide(); - +/* setInToOut +Enables the "hear what you playing" feature. */ +void setInToOut(bool v); +/* closeProject +Resets Giada to init state. If resetGui also refresh all widgets. If +createColumns also build initial empty columns. */ - - - - - -void rewind(); -void play(); - -/* toggleInputRec -Handles the input recording.*/ - -void toggleInputRec(); - -void toggleActionRec(); -void startActionRec(); -void stopActionRec(); - - -void toggleMetronome(); - +void closeProject(bool createColumns); }}} // giada::c::main:: #endif diff --git a/src/glue/plugin.cpp b/src/glue/plugin.cpp index 9afc65e..9120b19 100644 --- a/src/glue/plugin.cpp +++ b/src/glue/plugin.cpp @@ -31,7 +31,6 @@ #include #include #include "core/model/model.h" -#include "core/channels/channel.h" #include "core/pluginManager.h" #include "core/pluginHost.h" #include "core/mixer.h" @@ -55,9 +54,94 @@ namespace giada { namespace c { namespace plugin { -namespace +Param::Param(const m::Plugin& p, int index) +: index (index) +, pluginId(p.id) +, name (p.getParameterName(index)) +, text (p.getParameterText(index)) +, label (p.getParameterLabel(index)) +, value (p.getParameter(index)) { -void updatePluginEditor_(ID pluginId, bool gui) +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Plugin::Plugin(const m::Plugin& p, ID channelId) +: id (p.id) +, channelId (channelId) +, valid (p.valid) +, hasEditor (p.hasEditor()) +, isBypassed (p.isBypassed()) +, name (p.getName()) +, uniqueId (p.getUniqueId()) +, currentProgram(p.getCurrentProgram()) +, m_plugin (p) +{ + for (int i = 0; i < p.getNumPrograms(); i++) + programs.push_back({ i, p.getProgramName(i) }); + for (int i = 0; i < p.getNumParameters(); i++) + paramIndexes.push_back(i); +} + + +/* -------------------------------------------------------------------------- */ + + +juce::AudioProcessorEditor* Plugin::createEditor() const +{ + m::model::PluginsLock l(m::model::plugins); + return m_plugin.createEditor(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Plugins::Plugins(const m::Channel& c) +: channelId(c.id) +, pluginIds(c.pluginIds) +{ +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Plugins getPlugins(ID channelId) +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); + return Plugins(mm::get(mm::channels, channelId)); +} + + +Plugin getPlugin(ID pluginId, ID channelId) +{ + m::model::PluginsLock l(m::model::plugins); + return Plugin(m::model::get(m::model::plugins, pluginId), channelId); +} + + +Param getParam (int index, ID pluginId) +{ + m::model::PluginsLock l(m::model::plugins); + return Param(m::model::get(m::model::plugins, pluginId), index); +} + + +/* -------------------------------------------------------------------------- */ + + +void updateWindow(ID pluginId, bool gui) { m::model::PluginsLock l(m::model::plugins); const m::Plugin& p = m::model::get(m::model::plugins, pluginId); @@ -79,11 +163,8 @@ void updatePluginEditor_(ID pluginId, bool gui) child->updateParameters(!gui); if (!gui) Fl::unlock(); } -} // {anonymous} -/* -------------------------------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ @@ -121,17 +202,7 @@ void freePlugin(ID pluginId, ID channelId) void setProgram(ID pluginId, int programIndex) { m::pluginHost::setPluginProgram(pluginId, programIndex); - updatePluginEditor_(pluginId, /*gui=*/true); -} - - -/* -------------------------------------------------------------------------- */ - - -void setParameter(ID pluginId, int paramIndex, float value, bool gui) -{ - m::pluginHost::setPluginParameter(pluginId, paramIndex, value); - updatePluginEditor_(pluginId, gui); + updateWindow(pluginId, /*gui=*/true); } diff --git a/src/glue/plugin.h b/src/glue/plugin.h index 52d79a0..a5afe3c 100644 --- a/src/glue/plugin.h +++ b/src/glue/plugin.h @@ -32,10 +32,17 @@ #ifdef WITH_VST +#include +#include #include "core/pluginHost.h" #include "core/types.h" +namespace juce { +class AudioProcessorEditor; +} + + namespace giada { namespace m { @@ -45,16 +52,75 @@ class Channel; namespace c { namespace plugin { -void addPlugin(int pluginListIndex, ID channelId); +struct Program +{ + int index; + std::string name; +}; -void swapPlugins(ID pluginId1, ID pluginId2, ID channelId); +struct Param +{ + Param() = default; + Param(const m::Plugin&, int index); + + int index; + ID pluginId; + std::string name; + std::string text; + std::string label; + float value; +}; + +struct Plugin +{ + Plugin() = default; + Plugin(const m::Plugin&, ID channelId); -void freePlugin(ID pluginId, ID channelId); + juce::AudioProcessorEditor* createEditor() const; -void setParameter(ID pluginId, int paramIndex, float value, bool gui=true); + ID id; + ID channelId; + bool valid; + bool hasEditor; + bool isBypassed; + std::string name; + std::string uniqueId; + int currentProgram; -void setProgram(ID pluginId, int programIndex); + std::vector programs; + std::vector paramIndexes; + +private: + + const m::Plugin& m_plugin; +}; +struct Plugins +{ + Plugins() = default; + Plugins(const m::Channel&); + + ID channelId; + std::vector 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 diff --git a/src/glue/recorder.cpp b/src/glue/recorder.cpp index aacfc5a..4eab2b2 100644 --- a/src/glue/recorder.cpp +++ b/src/glue/recorder.cpp @@ -30,8 +30,6 @@ #include "gui/elems/mainWindow/keyboard/channel.h" #include "gui/elems/mainWindow/keyboard/sampleChannel.h" #include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/const.h" #include "core/clock.h" #include "core/model/model.h" @@ -89,13 +87,9 @@ void clearStartStopActions(ID channelId) void updateChannel(ID channelId, bool updateActionEditor) { - /* TODO - optimization needed. This functions swaps a channel only to set a - boolean flag. Query the channel first and swap it only if the flag has - actually changed. */ - - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - c.hasActions = m::recorder::hasActions(channelId); + c.state->hasActions = m::recorder::hasActions(channelId); }); if (updateActionEditor) diff --git a/src/glue/sampleEditor.cpp b/src/glue/sampleEditor.cpp index f48219e..d4ae965 100644 --- a/src/glue/sampleEditor.cpp +++ b/src/glue/sampleEditor.cpp @@ -27,6 +27,7 @@ #include #include +#include "glue/events.h" #include "gui/dialogs/mainWindow.h" #include "gui/dialogs/sampleEditor.h" #include "gui/dialogs/warnings.h" @@ -42,8 +43,6 @@ #include "gui/elems/mainWindow/keyboard/keyboard.h" #include "gui/elems/mainWindow/keyboard/channel.h" #include "core/model/model.h" -#include "core/channels/sampleChannel.h" -#include "core/waveFx.h" #include "core/wave.h" #include "core/waveManager.h" #include "core/mixerHandler.h" @@ -68,6 +67,8 @@ A Wave used during cut/copy/paste operations. */ std::unique_ptr waveBuffer_; +Frame previewTracker_ = 0; + /* -------------------------------------------------------------------------- */ @@ -77,15 +78,31 @@ needed for the operation. */ void resetBeginEnd_(ID channelId) { - Frame begin; - Frame end; - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c) { - begin = static_cast(c).begin; - end = static_cast(c).end; + Frame begin = c.samplePlayer->state->begin.load(); + Frame end = c.samplePlayer->state->end.load(); + setBeginEnd(channelId, begin, end); }); +} + + +/* -------------------------------------------------------------------------- */ + +/* updateWavePtr_ +Updates the Wave pointer in Channel::WaveReader. */ + +void updateWavePtr_(ID channelId, ID waveId) +{ + namespace mm = m::model; + + mm::WavesLock wl(mm::waves); + const m::Wave& wave = mm::get(mm::waves, waveId); - setBeginEnd(channelId, begin, end); + mm::onSwap(mm::channels, channelId, [&](m::Channel& c) + { + c.samplePlayer->loadWave(&wave); + }); } }; // {anonymous} @@ -95,6 +112,82 @@ void resetBeginEnd_(ID channelId) /* -------------------------------------------------------------------------- */ +Data::Data(const m::Channel& c, const m::Wave& w) +: channelId (c.id) +, waveId (w.id) +, name (c.state->name) +, volume (c.state->volume.load()) +, pan (c.state->pan.load()) +, pitch (c.samplePlayer->state->pitch.load()) +, begin (c.samplePlayer->state->begin.load()) +, end (c.samplePlayer->state->end.load()) +, shift (c.samplePlayer->state->shift.load()) +, waveSize (w.getSize()) +, waveBits (w.getBits()) +, waveDuration(w.getDuration()) +, waveRate (w.getRate()) +, wavePath (w.getPath()) +, isLogical (w.isLogical()) +{ +} + +/* TODO - use c::channel::a_get() */ +ChannelStatus Data::a_getPreviewStatus() const +{ + namespace mm = m::model; + + mm::ChannelsLock l(mm::channels); + return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).state->playStatus.load(); +} + +/* TODO - use c::channel::a_get() */ +Frame Data::a_getPreviewTracker() const +{ + namespace mm = m::model; + + mm::ChannelsLock l(mm::channels); + return mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID).samplePlayer->state->tracker.load(); +} + + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +Data getData(ID channelId) +{ + namespace mm = m::model; + + mm::ChannelsLock cl(mm::channels); + mm::WavesLock wl(mm::waves); + + const m::Channel& channel = mm::get(mm::channels, channelId); + const m::Wave& wave = mm::get(mm::waves, channel.samplePlayer->getWaveId()); + + /* Prepare the preview channel. */ + + m::Channel& preview = mm::get(mm::channels, m::mixer::PREVIEW_CHANNEL_ID); + preview.samplePlayer->loadWave(&wave); + + return Data(channel, wave); +} + + +/* -------------------------------------------------------------------------- */ + + +void onRefresh(bool gui, std::function f) +{ + v::gdSampleEditor* se = static_cast(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(u::gui::getSubwindow(G_MainWin, WID_SAMPLE_EDITOR)); @@ -108,12 +201,20 @@ v::gdSampleEditor* getSampleEditorWindow() void setBeginEnd(ID channelId, int b, int e) { - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - static_cast(c).setBegin(b); - static_cast(c).setEnd(e); + b = std::clamp(b, 0, c.samplePlayer->getWaveSize() - 1); + e = std::clamp(e, 1, c.samplePlayer->getWaveSize() - 1); + if (b >= e) b = e - 1; + else if (e < b) e = b + 1; + + c.samplePlayer->state->begin.store(b); + c.samplePlayer->state->end.store(e); + if (c.samplePlayer->state->tracker.load() < b) + c.samplePlayer->state->tracker.store(b); }); + /* TODO waveform widget is dumb and wants a rebuild. Refactoring needed! */ getSampleEditorWindow()->rebuild(); } @@ -125,6 +226,7 @@ void cut(ID channelId, ID waveId, int a, int b) { copy(waveId, a, b); m::wfx::cut(waveId, a, b); + updateWavePtr_(channelId, waveId); resetBeginEnd_(channelId); } @@ -135,7 +237,6 @@ void cut(ID channelId, ID waveId, int a, int b) void copy(ID waveId, int a, int b) { m::model::WavesLock lock(m::model::waves); - waveBuffer_ = m::waveManager::createFromWave(m::model::get(m::model::waves, waveId), a, b); } @@ -151,6 +252,7 @@ void paste(ID channelId, ID waveId, int a) } m::wfx::paste(*waveBuffer_, waveId, a); + updateWavePtr_(channelId, waveId); /* Shift begin/end points to keep the previous position. */ @@ -160,8 +262,8 @@ void paste(ID channelId, ID waveId, int a) m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - begin = static_cast(c).begin; - end = static_cast(c).end; + begin = c.samplePlayer->state->begin.load(); + end = c.samplePlayer->state->end.load(); }); if (a < begin && a < end) @@ -177,45 +279,50 @@ void paste(ID channelId, ID waveId, int a) /* -------------------------------------------------------------------------- */ -void silence(ID waveId, int a, int b) +void silence(ID channelId, ID waveId, int a, int b) { m::wfx::silence(waveId, a, b); + updateWavePtr_(channelId, waveId); } /* -------------------------------------------------------------------------- */ -void fade(ID waveId, int a, int b, int type) +void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type) { m::wfx::fade(waveId, a, b, type); + updateWavePtr_(channelId, waveId); } /* -------------------------------------------------------------------------- */ -void smoothEdges(ID waveId, int a, int b) +void smoothEdges(ID channelId, ID waveId, int a, int b) { m::wfx::smooth(waveId, a, b); + updateWavePtr_(channelId, waveId); } /* -------------------------------------------------------------------------- */ -void reverse(ID waveId, int a, int b) +void reverse(ID channelId, ID waveId, int a, int b) { m::wfx::reverse(waveId, a, b); + updateWavePtr_(channelId, waveId); } /* -------------------------------------------------------------------------- */ -void normalizeHard(ID waveId, int a, int b) +void normalize(ID channelId, ID waveId, int a, int b) { - m::wfx::normalizeHard(waveId, a, b); + m::wfx::normalize(waveId, a, b); + updateWavePtr_(channelId, waveId); } @@ -225,6 +332,7 @@ void normalizeHard(ID waveId, int a, int b) void trim(ID channelId, ID waveId, int a, int b) { m::wfx::trim(waveId, a, b); + updateWavePtr_(channelId, waveId); resetBeginEnd_(channelId); } @@ -232,49 +340,58 @@ void trim(ID channelId, ID waveId, int a, int b) /* -------------------------------------------------------------------------- */ -void setPlayHead(ID channelId, Frame f) -{ - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - static_cast(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(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(c).waveId; - }); m::model::onGet(m::model::waves, waveId, [&](m::Wave& w) { @@ -301,24 +418,16 @@ void reload(ID channelId, ID waveId) return; std::string wavePath; - Frame waveSize; - m::model::onGet(m::model::waves, waveId, [&](m::Wave& w) + m::model::onGet(m::model::waves, waveId, [&](const m::Wave& w) { wavePath = w.getPath(); - waveSize = w.getSize(); }); - if (channel::loadChannel(channelId, wavePath) != G_RES_OK) + if (channel::loadChannel(channelId, wavePath) != G_RES_OK) { + v::gdAlert("Unable to reload sample!"); return; + } - ID newWaveId; - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) - { - m::SampleChannel& sc = static_cast(c); - newWaveId = sc.waveId; - }); - - getSampleEditorWindow()->setWaveId(newWaveId); getSampleEditorWindow()->rebuild(); } @@ -329,17 +438,19 @@ void reload(ID channelId, ID waveId) void shift(ID channelId, ID waveId, int offset) { Frame shift; - - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](const m::Channel& c) { - shift = static_cast(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(c).shift = offset; + c.samplePlayer->state->shift.store(offset); }); + + getSampleEditorWindow()->shiftTool->update(offset); } }}}; // giada::c::sampleEditor:: diff --git a/src/glue/sampleEditor.h b/src/glue/sampleEditor.h index 8efc3d4..6cc31b7 100644 --- a/src/glue/sampleEditor.h +++ b/src/glue/sampleEditor.h @@ -29,13 +29,59 @@ #define G_GLUE_SAMPLE_EDITOR_H +#include +#include #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 f); + +/* getData +Returns a Data object filled with data from a channel. */ + +Data getData(ID channelId); + /* setBeginEnd Sets start/end points in the sample editor. */ @@ -46,28 +92,25 @@ void copy(ID waveId, int a, int b); void paste(ID channelId, ID waveId, int a); void trim(ID channelId, ID waveId, int a, int b); -void reverse(ID waveId, int a, int b); -void normalizeHard(ID waveId, int a, int b); -void silence(ID waveId, int a, int b); -void fade(ID waveId, int a, int b, int type); -void smoothEdges(ID waveId, int a, int b); +void reverse(ID channelId, ID waveId, int a, int b); +void normalize(ID channelId, ID waveId, int a, int b); +void silence(ID channelId, ID waveId, int a, int b); +void fade(ID channelId, ID waveId, int a, int b, m::wfx::Fade type); +void smoothEdges(ID channelId, ID waveId, int a, int b); void shift(ID channelId, ID waveId, int offset); void reload(ID channelId, ID waveId); bool isWaveBufferFull(); -/* setPlayHead -Changes playhead's position. Used in preview. */ - -void setPlayHead(ID channelId, Frame f); - -void setPreview(ID channelId, PreviewMode mode); -void rewindPreview(ID channelId); +void playPreview(bool loop); +void stopPreview(); +void setPreviewTracker(Frame f); +void cleanupPreview(); /* toNewChannel Copies the selected range into a new sample channel. */ -void toNewChannel(ID channelId, int a, int b); +void toNewChannel(ID channelId, ID waveId, int a, int b); }}}; // giada::c::sampleEditor:: #endif diff --git a/src/glue/storage.cpp b/src/glue/storage.cpp index 30c1904..f417d2e 100644 --- a/src/glue/storage.cpp +++ b/src/glue/storage.cpp @@ -28,9 +28,6 @@ #include #include "core/model/model.h" #include "core/model/storage.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/mixer.h" #include "core/wave.h" #include "core/mixerHandler.h" @@ -92,6 +89,7 @@ std::string makeUniqueWavePath_(const std::string& base, const m::Wave& w) if (isWavePathUnique_(w, path)) return path; + // TODO - just use a timestamp. e.g. makeWavePath_(..., ..., getTimeStamp()) int k = 0; path = makeWavePath_(base, w, k); while (!isWavePathUnique_(w, path)) @@ -125,14 +123,15 @@ bool savePatch_(const std::string& path, const std::string& name) /* -------------------------------------------------------------------------- */ -void saveWavesToProject_(const std::string& base) +void saveWavesToProject_(const std::string& basePath) { - for (size_t i = 0; i < m::model::waves.size(); i++) { - m::model::onSwap(m::model::waves, m::model::getId(m::model::waves, i), [&](m::Wave& w) - { - w.setPath(makeUniqueWavePath_(base, w)); - m::waveManager::save(w, w.getPath()); // TODO - error checking - }); + /* No need for a hard Wave swap here: nobody is reading the path data. */ + + m::model::WavesLock l(m::model::waves); + + for (m::Wave* w : m::model::waves) { + w->setPath(makeUniqueWavePath_(basePath, *w)); + m::waveManager::save(*w, w->getPath()); // TODO - error checking } } } // {anonymous} @@ -145,7 +144,7 @@ void saveWavesToProject_(const std::string& base) void loadProject(void* data) { - v::gdBrowserLoad* browser = (v::gdBrowserLoad*) data; + v::gdBrowserLoad* browser = static_cast(data); std::string fullPath = browser->getSelectedItem(); bool isProject = u::fs::isProject(browser->getSelectedItem()); @@ -190,10 +189,10 @@ void loadProject(void* data) the current samplerate != patch samplerate. Clock needs to update frames in sequencer. */ - m::mixer::allocVirtualInput(m::clock::getFramesInLoop()); m::mh::updateSoloCount(); m::recorderHandler::updateSamplerate(m::conf::conf.samplerate, m::patch::patch.samplerate); m::clock::recomputeFrames(); + m::mixer::allocRecBuffer(m::clock::getFramesInLoop()); /* Mixer is ready to go back online. */ @@ -221,11 +220,11 @@ void loadProject(void* data) void saveProject(void* data) { - v::gdBrowserSave* browser = (v::gdBrowserSave*) data; - std::string name = u::fs::stripExt(browser->getName()); - std::string folderPath = browser->getCurrentPath(); - std::string fullPath = folderPath + G_SLASH + name + ".gprj"; - std::string gptcPath = fullPath + G_SLASH + name + ".gptc"; + v::gdBrowserSave* browser = static_cast(data); + std::string name = u::fs::stripExt(browser->getName()); + std::string folderPath = browser->getCurrentPath(); + std::string fullPath = folderPath + G_SLASH + name + ".gprj"; + std::string gptcPath = fullPath + G_SLASH + name + ".gptc"; if (name == "") { v::gdAlert("Please choose a project name."); @@ -257,7 +256,7 @@ void saveProject(void* data) void loadSample(void* data) { - v::gdBrowserLoad* browser = (v::gdBrowserLoad*) data; + v::gdBrowserLoad* browser = static_cast(data); std::string fullPath = browser->getSelectedItem(); if (fullPath.empty()) @@ -278,9 +277,10 @@ void loadSample(void* data) void saveSample(void* data) { - v::gdBrowserSave* browser = (v::gdBrowserSave*) data; + v::gdBrowserSave* browser = static_cast(data); std::string name = browser->getName(); std::string folderPath = browser->getCurrentPath(); + ID channelId = browser->getChannelId(); if (name == "") { v::gdAlert("Please choose a file name."); @@ -293,12 +293,12 @@ void saveSample(void* data) return; ID waveId; - m::model::onGet(m::model::channels, browser->getChannelId(), [&](m::Channel& c) + m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) { - waveId = static_cast(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 wave = m::model::waves.clone(waveIndex); diff --git a/src/glue/transport.cpp b/src/glue/transport.cpp deleted file mode 100644 index 905cffc..0000000 --- a/src/glue/transport.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#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 diff --git a/src/gui/dialogs/actionEditor/baseActionEditor.cpp b/src/gui/dialogs/actionEditor/baseActionEditor.cpp index c147abb..7571c8e 100644 --- a/src/gui/dialogs/actionEditor/baseActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/baseActionEditor.cpp @@ -31,13 +31,14 @@ #include #include "utils/gui.h" #include "utils/string.h" -#include "core/channels/channel.h" -#include "core/model/model.h" #include "core/conf.h" +#include "core/action.h" #include "core/const.h" #include "core/clock.h" +#include "core/midiEvent.h" +#include "glue/channel.h" #include "gui/elems/actionEditor/gridTool.h" -#include "gui/elems/basics/scroll.h" +#include "gui/elems/basics/scrollPack.h" #include "gui/elems/basics/choice.h" #include "baseActionEditor.h" @@ -46,9 +47,9 @@ namespace giada { namespace v { gdBaseActionEditor::gdBaseActionEditor(ID channelId) -: gdWindow (640, 284), - channelId(channelId), - ratio (G_DEFAULT_ZOOM_RATIO) +: gdWindow (640, 284) +, channelId(channelId) +, ratio (G_DEFAULT_ZOOM_RATIO) { using namespace giada::m; @@ -85,15 +86,6 @@ void gdBaseActionEditor::cb_zoomOut(Fl_Widget* w, void* p) { ((gdBaseActionEdito /* -------------------------------------------------------------------------- */ -std::vector gdBaseActionEditor::getActions() const -{ - return m_actions; -} - - -/* -------------------------------------------------------------------------- */ - - void gdBaseActionEditor::computeWidth() { fullWidth = frameToPixel(m::clock::getFramesInSeq()); @@ -198,12 +190,9 @@ void gdBaseActionEditor::prepareWindow() { u::gui::setFavicon(this); - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - std::string l = "Action Editor"; - if (c.name != "") l += " - " + c.name; - copy_label(l.c_str()); - }); + std::string l = "Action Editor"; + if (m_data.channelName != "") l += " - " + m_data.channelName; + copy_label(l.c_str()); set_non_modal(); size_range(640, 284); diff --git a/src/gui/dialogs/actionEditor/baseActionEditor.h b/src/gui/dialogs/actionEditor/baseActionEditor.h index b874aa9..f40e4b1 100644 --- a/src/gui/dialogs/actionEditor/baseActionEditor.h +++ b/src/gui/dialogs/actionEditor/baseActionEditor.h @@ -30,12 +30,12 @@ #include "core/types.h" +#include "glue/actionEditor.h" #include "gui/dialogs/window.h" class geChoice; class geButton; -class geScroll; namespace giada { @@ -47,8 +47,7 @@ struct Action; namespace v { class geGridTool; - - +class geScrollPack; class gdBaseActionEditor : public gdWindow { public: @@ -60,16 +59,15 @@ public: Pixel frameToPixel(Frame f) const; Frame pixelToFrame(Pixel p, bool snap=true) const; int getActionType() const; - std::vector getActions() const; - - geChoice* actionType; - geGridTool* gridTool; - geButton* zoomInBtn; - geButton* zoomOutBtn; - geScroll* viewport; // widget container ID channelId; + geChoice* actionType; + geGridTool* gridTool; + geButton* zoomInBtn; + geButton* zoomOutBtn; + geScrollPack* viewport; // widget container + float ratio; Pixel fullWidth; // Full widgets width, i.e. scaled-down full sequencer Pixel loopWidth; // Loop width, i.e. scaled-down sequencer range @@ -81,8 +79,6 @@ protected: static constexpr float MIN_RATIO = 25.0f; static constexpr float MAX_RATIO = 40000.0f; - std::vector m_actions; - gdBaseActionEditor(ID channelId); void zoomIn(); @@ -99,6 +95,8 @@ protected: void centerViewportOut(); void prepareWindow(); + + c::actionEditor::Data m_data; }; }} // giada::v:: diff --git a/src/gui/dialogs/actionEditor/midiActionEditor.cpp b/src/gui/dialogs/actionEditor/midiActionEditor.cpp index 5dc98c4..6b5ec7f 100644 --- a/src/gui/dialogs/actionEditor/midiActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/midiActionEditor.cpp @@ -26,11 +26,10 @@ #include -#include "core/channels/midiChannel.h" -#include "core/model/model.h" #include "core/graphics.h" #include "glue/actionEditor.h" -#include "gui/elems/basics/scroll.h" +#include "glue/channel.h" +#include "gui/elems/basics/scrollPack.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/resizerBar.h" #include "gui/elems/basics/box.h" @@ -47,39 +46,41 @@ namespace v gdMidiActionEditor::gdMidiActionEditor(ID channelId) : gdBaseActionEditor(channelId) { - computeWidth(); - - Fl_Group* upperArea = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w()-16, G_GUI_UNIT); - - upperArea->begin(); + end(); - gridTool = new geGridTool(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN); + computeWidth(); - geBox *b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, G_GUI_UNIT); // padding actionType - zoomButtons - zoomInBtn = new geButton(w()-8-40-4, G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); - zoomOutBtn = new geButton(w()-8-20, G_GUI_OUTER_MARGIN, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm); - - upperArea->end(); + gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL); + gridTool = new geGridTool(0, 0); + geBox* b1 = new geBox (0, 0, w() - 150, G_GUI_UNIT); // padding actionType - zoomButtons + zoomInBtn = new geButton (0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomInOff_xpm, zoomInOn_xpm); + zoomOutBtn = new geButton (0, 0, G_GUI_UNIT, G_GUI_UNIT, "", zoomOutOff_xpm, zoomOutOn_xpm); + upperArea->add(gridTool); + upperArea->add(b1); + upperArea->add(zoomInBtn); + upperArea->add(zoomOutBtn); upperArea->resizable(b1); - zoomInBtn->callback(cb_zoomIn, (void*)this); - zoomOutBtn->callback(cb_zoomOut, (void*)this); - /* Main viewport: contains all widgets. */ - viewport = new geScroll(G_GUI_OUTER_MARGIN, upperArea->y()+upperArea->h()+G_GUI_OUTER_MARGIN, w()-16, h()-44); - - m_ne = new geNoteEditor(viewport->x(), viewport->y(), this); - m_ner = new geResizerBar(m_ne->x(), m_ne->y()+m_ne->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); + viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN, + upperArea->w(), h()-44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0); + m_ne = new geNoteEditor (0, 0, this); + m_ner = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); + m_ve = new geVelocityEditor(0, 0, this); + m_ver = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); viewport->add(m_ne); viewport->add(m_ner); - - m_ve = new geVelocityEditor(viewport->x(), m_ne->y()+m_ne->h()+RESIZER_BAR_H); - m_ver = new geResizerBar(m_ve->x(), m_ve->y()+m_ve->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); viewport->add(m_ve); viewport->add(m_ver); - end(); + zoomInBtn->callback(cb_zoomIn, (void*)this); + zoomOutBtn->callback(cb_zoomOut, (void*)this); + + add(upperArea); + add(viewport); + resizable(upperArea); + prepareWindow(); rebuild(); } @@ -90,12 +91,12 @@ gdMidiActionEditor::gdMidiActionEditor(ID channelId) void gdMidiActionEditor::rebuild() { - m_actions = c::actionEditor::getActions(channelId); + m_data = c::actionEditor::getData(channelId); computeWidth(); - m_ne->rebuild(); + m_ne->rebuild(m_data); m_ner->size(m_ne->w(), m_ner->h()); - m_ve->rebuild(); + m_ve->rebuild(m_data); m_ver->size(m_ve->w(), m_ver->h()); } }} // giada::v:: diff --git a/src/gui/dialogs/actionEditor/sampleActionEditor.cpp b/src/gui/dialogs/actionEditor/sampleActionEditor.cpp index e5b7a44..6b47af2 100644 --- a/src/gui/dialogs/actionEditor/sampleActionEditor.cpp +++ b/src/gui/dialogs/actionEditor/sampleActionEditor.cpp @@ -27,12 +27,13 @@ #include #include "core/model/model.h" -#include "core/channels/sampleChannel.h" #include "core/const.h" #include "core/midiEvent.h" #include "core/graphics.h" #include "glue/actionEditor.h" -#include "gui/elems/basics/scroll.h" +#include "glue/channel.h" +#include "gui/elems/basics/pack.h" +#include "gui/elems/basics/scrollPack.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/resizerBar.h" #include "gui/elems/basics/choice.h" @@ -49,50 +50,53 @@ namespace v gdSampleActionEditor::gdSampleActionEditor(ID channelId) : gdBaseActionEditor(channelId) { + end(); + computeWidth(); /* Container with zoom buttons and the action type selector. Scheme of the resizable boxes: |[--b1--][actionType][--b2--][+][-]| */ - Fl_Group* upperArea = new Fl_Group(8, 8, w()-16, 20); - - upperArea->begin(); - - actionType = new geChoice(8, 8, 80, 20); - gridTool = new geGridTool(actionType->x()+actionType->w()+8, 8); - actionType->add("Key press"); - actionType->add("Key release"); - actionType->add("Kill chan"); - actionType->value(0); - - if (!canChangeActionType()) - actionType->deactivate(); - - geBox* b1 = new geBox(gridTool->x()+gridTool->w()+4, 8, 300, 20); // padding actionType - zoomButtons - zoomInBtn = new geButton(w()-8-40-4, 8, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); - zoomOutBtn = new geButton(w()-8-20, 8, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); - - upperArea->end(); + gePack* upperArea = new gePack(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, Direction::HORIZONTAL); + actionType = new geChoice (0, 0, 80, 20); + gridTool = new geGridTool(0, 0); + geBox* b1 = new geBox (0, 0, w() - 232, 20); // padding actionType - zoomButtons + zoomInBtn = new geButton (0, 0, 20, 20, "", zoomInOff_xpm, zoomInOn_xpm); + zoomOutBtn = new geButton (0, 0, 20, 20, "", zoomOutOff_xpm, zoomOutOn_xpm); + upperArea->add(actionType); + upperArea->add(gridTool); + upperArea->add(b1); + upperArea->add(zoomInBtn); + upperArea->add(zoomOutBtn); upperArea->resizable(b1); + actionType->add("Key press"); + actionType->add("Key release"); + actionType->add("Kill chan"); + actionType->value(0); + if (!canChangeActionType()) + actionType->deactivate(); + zoomInBtn->callback(cb_zoomIn, (void*)this); zoomOutBtn->callback(cb_zoomOut, (void*)this); /* Main viewport: contains all widgets. */ - viewport = new geScroll(8, 36, w()-16, h()-44); - - m_ae = new geSampleActionEditor(viewport->x(), viewport->y()); - m_aer = new geResizerBar(m_ae->x(), m_ae->y()+m_ae->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); + viewport = new geScrollPack(G_GUI_OUTER_MARGIN, upperArea->y() + upperArea->h() + G_GUI_OUTER_MARGIN, + upperArea->w(), h()-44, Fl_Scroll::BOTH, Direction::VERTICAL, /*gutter=*/0); + m_ae = new geSampleActionEditor(0, 0, this); + m_aer = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); + m_ee = new geEnvelopeEditor (0, 0, "volume", this); + m_eer = new geResizerBar (0, 0, viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); viewport->add(m_ae); viewport->add(m_aer); - - m_ee = new geEnvelopeEditor(viewport->x(), m_ae->y()+m_ae->h()+RESIZER_BAR_H, "volume"); - m_eer = new geResizerBar(m_ee->x(), m_ee->y()+m_ee->h(), viewport->w(), RESIZER_BAR_H, MIN_WIDGET_H, geResizerBar::VERTICAL); viewport->add(m_ee); viewport->add(m_eer); - end(); + add(upperArea); + add(viewport); + resizable(upperArea); + prepareWindow(); rebuild(); } @@ -103,13 +107,8 @@ gdSampleActionEditor::gdSampleActionEditor(ID channelId) bool gdSampleActionEditor::canChangeActionType() { - bool res; - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - const m::SampleChannel& sc = static_cast(c); - res = sc.mode != ChannelMode::SINGLE_PRESS && !sc.isAnyLoopMode(); - }); - return res; + return m_data.sample->channelMode != SamplePlayerMode::SINGLE_PRESS && + m_data.sample->isLoopMode == false; } @@ -118,14 +117,14 @@ bool gdSampleActionEditor::canChangeActionType() void gdSampleActionEditor::rebuild() { - m_actions = c::actionEditor::getActions(channelId); + m_data = c::actionEditor::getData(channelId); canChangeActionType() ? actionType->activate() : actionType->deactivate(); computeWidth(); - m_ae->rebuild(); + m_ae->rebuild(m_data); m_aer->size(m_ae->w(), m_aer->h()); - m_ee->rebuild(); + m_ee->rebuild(m_data); m_eer->size(m_ee->w(), m_eer->h()); } }} // giada::v:: diff --git a/src/gui/dialogs/actionEditor/sampleActionEditor.h b/src/gui/dialogs/actionEditor/sampleActionEditor.h index 02d987e..6f47840 100644 --- a/src/gui/dialogs/actionEditor/sampleActionEditor.h +++ b/src/gui/dialogs/actionEditor/sampleActionEditor.h @@ -40,7 +40,6 @@ namespace v { class geSampleActionEditor; class geEnvelopeEditor; - class gdSampleActionEditor : public gdBaseActionEditor { public: @@ -51,13 +50,13 @@ public: private: + bool canChangeActionType(); + geSampleActionEditor* m_ae; geResizerBar* m_aer; geEnvelopeEditor* m_ee; geResizerBar* m_eer; - - bool canChangeActionType(); }; }} // giada::v:: diff --git a/src/gui/dialogs/channelNameInput.cpp b/src/gui/dialogs/channelNameInput.cpp index 764e267..2d3a0cf 100644 --- a/src/gui/dialogs/channelNameInput.cpp +++ b/src/gui/dialogs/channelNameInput.cpp @@ -27,7 +27,6 @@ #include "glue/channel.h" #include "utils/gui.h" -#include "core/channels/channel.h" #include "core/model/model.h" #include "core/const.h" #include "core/conf.h" @@ -40,9 +39,9 @@ namespace giada { namespace v { -gdChannelNameInput::gdChannelNameInput(ID channelId) -: gdWindow (u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"), - m_channelId(channelId) +gdChannelNameInput::gdChannelNameInput(const c::channel::Data& d) +: gdWindow(u::gui::centerWindowX(400), u::gui::centerWindowY(64), 400, 64, "New channel name"), + m_data (d) { set_modal(); @@ -51,10 +50,7 @@ gdChannelNameInput::gdChannelNameInput(ID channelId) m_cancel = new geButton(m_ok->x() - 70 - G_GUI_OUTER_MARGIN, m_ok->y(), 70, G_GUI_UNIT, "Cancel"); end(); - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - m_name->value(c.name.c_str()); - }); + m_name->value(m_data.name.c_str()); m_ok->shortcut(FL_Enter); m_ok->callback(cb_update, (void*)this); @@ -88,7 +84,7 @@ void gdChannelNameInput::cb_cancel() void gdChannelNameInput::cb_update() { - c::channel::setName(m_channelId, m_name->value()); + c::channel::setName(m_data.id, m_name->value()); do_callback(); } diff --git a/src/gui/dialogs/channelNameInput.h b/src/gui/dialogs/channelNameInput.h index 1da6c7f..9ba9b0b 100644 --- a/src/gui/dialogs/channelNameInput.h +++ b/src/gui/dialogs/channelNameInput.h @@ -43,7 +43,7 @@ class gdChannelNameInput : public gdWindow { public: - gdChannelNameInput(ID channelId); + gdChannelNameInput(const c::channel::Data& d); private: @@ -52,7 +52,7 @@ private: void cb_update(); void cb_cancel(); - ID m_channelId; + const c::channel::Data& m_data; geInput* m_name; geButton* m_ok; diff --git a/src/gui/dialogs/keyGrabber.cpp b/src/gui/dialogs/keyGrabber.cpp index 7457f91..71d8fa7 100644 --- a/src/gui/dialogs/keyGrabber.cpp +++ b/src/gui/dialogs/keyGrabber.cpp @@ -30,9 +30,6 @@ #include "utils/string.h" #include "glue/io.h" #include "core/model/model.h" -#include "core/channels/channel.h" -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" #include "core/conf.h" #include "utils/log.h" #include "gui/elems/basics/box.h" @@ -50,9 +47,9 @@ extern giada::v::gdMainWindow* mainWin; namespace giada { namespace v { -gdKeyGrabber::gdKeyGrabber(ID channelId) -: gdWindow (300, 126, "Key configuration"), - m_channelId(channelId) +gdKeyGrabber::gdKeyGrabber(const c::channel::Data& d) +: gdWindow(300, 126, "Key configuration") +, m_data (d) { begin(); m_text = new geBox(8, 8, 284, 80, ""); @@ -63,7 +60,7 @@ gdKeyGrabber::gdKeyGrabber(ID channelId) m_clear->callback(cb_clear, (void*)this); m_cancel->callback(cb_cancel, (void*)this); - rebuild(); + updateText(m_data.key); u::gui::setFavicon(this); set_modal(); @@ -76,10 +73,7 @@ gdKeyGrabber::gdKeyGrabber(ID channelId) void gdKeyGrabber::rebuild() { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - updateText(c.key); - }); + updateText(m_data.key); } @@ -114,7 +108,7 @@ void gdKeyGrabber::cb_clear() void gdKeyGrabber::setButtonLabel(int key) { - c::io::setSampleChannelKey(m_channelId, key); + c::io::channel_setKey(m_data.id, key); } /* -------------------------------------------------------------------------- */ @@ -124,7 +118,7 @@ void gdKeyGrabber::updateText(int key) { std::string tmp = "Press a key.\n\nCurrent binding: "; if (key != 0) - tmp += static_cast(key); + tmp += static_cast(key); else tmp += "[none]"; m_text->copy_label(tmp.c_str()); @@ -148,7 +142,7 @@ int gdKeyGrabber::handle(int e) && x != FL_End && x != ' ') { - u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_channelId); + u::log::print("set key '%c' (%d) for channel ID=%d\n", x, x, m_data.id); setButtonLabel(x); updateText(x); break; diff --git a/src/gui/dialogs/keyGrabber.h b/src/gui/dialogs/keyGrabber.h index a258fae..90b058c 100644 --- a/src/gui/dialogs/keyGrabber.h +++ b/src/gui/dialogs/keyGrabber.h @@ -38,13 +38,18 @@ class geButton; namespace giada { +namespace c { +namespace channel +{ +struct Data; +}} namespace v { class gdKeyGrabber : public gdWindow { public: - gdKeyGrabber(ID channelId); + gdKeyGrabber(const c::channel::Data& d); int handle(int e) override; void rebuild() override; @@ -59,7 +64,7 @@ private: void setButtonLabel(int key); void updateText(int key); - ID m_channelId; + const c::channel::Data& m_data; geBox* m_text; geButton* m_clear; diff --git a/src/gui/dialogs/mainWindow.cpp b/src/gui/dialogs/mainWindow.cpp index 5ad9e14..49db4ad 100644 --- a/src/gui/dialogs/mainWindow.cpp +++ b/src/gui/dialogs/mainWindow.cpp @@ -63,7 +63,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char* title, int argc, char** arg mainMenu = new v::geMainMenu(8, 0); #if defined(WITH_VST) - mainIO = new v::geMainIO(408, 8); + mainIO = new v::geMainIO(412, 8); #else mainIO = new v::geMainIO(476, 8); #endif diff --git a/src/gui/dialogs/midiIO/midiInputBase.cpp b/src/gui/dialogs/midiIO/midiInputBase.cpp index 14d78dc..1bd2cf8 100644 --- a/src/gui/dialogs/midiIO/midiInputBase.cpp +++ b/src/gui/dialogs/midiIO/midiInputBase.cpp @@ -56,16 +56,6 @@ gdMidiInputBase::~gdMidiInputBase() /* -------------------------------------------------------------------------- */ -void gdMidiInputBase::refresh() -{ - for (geMidiLearnerBase* l : m_learners) - l->refresh(); -} - - -/* -------------------------------------------------------------------------- */ - - void gdMidiInputBase::cb_close(Fl_Widget* w, void* p) { ((gdMidiInputBase*)p)->cb_close(); } diff --git a/src/gui/dialogs/midiIO/midiInputBase.h b/src/gui/dialogs/midiIO/midiInputBase.h index 068e78b..8836d32 100644 --- a/src/gui/dialogs/midiIO/midiInputBase.h +++ b/src/gui/dialogs/midiIO/midiInputBase.h @@ -30,7 +30,7 @@ #include "gui/dialogs/window.h" -#include "gui/elems/midiIO/midiLearnerBase.h" +#include "gui/elems/midiIO/midiLearner.h" class geButton; @@ -47,19 +47,13 @@ public: virtual ~gdMidiInputBase(); - void refresh() override; - protected: - gdMidiInputBase(int x, int y, int w, int h, const char* title); - - static const int LEARNER_WIDTH = 284; + gdMidiInputBase(int x, int y, int w, int h, const char* title=""); static void cb_close(Fl_Widget* w, void* p); void cb_close(); - std::vector m_learners; - geButton* m_ok; geCheck* m_enable; geChoice* m_channel; diff --git a/src/gui/dialogs/midiIO/midiInputChannel.cpp b/src/gui/dialogs/midiIO/midiInputChannel.cpp index 32d85df..434f758 100644 --- a/src/gui/dialogs/midiIO/midiInputChannel.cpp +++ b/src/gui/dialogs/midiIO/midiInputChannel.cpp @@ -29,87 +29,148 @@ #include #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(c).midiInVeloAsVol); - m_veloAsVol->callback(cb_veloAsVol, (void*)this); - } - else - m_veloAsVol->hide(); - m_channel->add("Channel (any)"); m_channel->add("Channel 1"); m_channel->add("Channel 2"); @@ -127,15 +188,18 @@ gdMidiInputChannel::gdMidiInputChannel(ID channelId) m_channel->add("Channel 14"); m_channel->add("Channel 15"); m_channel->add("Channel 16"); - m_channel->value(c.midiInFilter == -1 ? 0 : c.midiInFilter + 1); m_channel->callback(cb_setChannel, (void*)this); - resizable(m_container); + m_veloAsVol->callback(cb_veloAsVol, (void*)this); - end(); + add(groupHeader); + add(m_container); + add(groupButtons); + resizable(m_container); u::gui::setFavicon(this); set_modal(); + rebuild(); show(); } @@ -143,66 +207,39 @@ gdMidiInputChannel::gdMidiInputChannel(ID channelId) /* -------------------------------------------------------------------------- */ -void gdMidiInputChannel::addChannelLearners() +void gdMidiInputChannel::rebuild() { - m::model::ChannelsLock l(m::model::channels); - const m::Channel& c = m::model::get(m::model::channels, m_channelId); - - Fl_Pack* pack = new Fl_Pack(m_container->x(), m_container->y(), LEARNER_WIDTH, 200); - pack->spacing(G_GUI_INNER_MARGIN); - pack->begin(); - geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, "Channel"); - header->box(FL_BORDER_BOX); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key press", G_MIDI_IN_KEYPRESS, c.midiInKeyPress, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key release", G_MIDI_IN_KEYREL, c.midiInKeyRel, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "key kill", G_MIDI_IN_KILL, c.midiInKill, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "arm", G_MIDI_IN_ARM, c.midiInArm, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "mute", G_MIDI_IN_MUTE, c.midiInVolume, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "solo", G_MIDI_IN_SOLO, c.midiInMute, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(0, 0, LEARNER_WIDTH, "volume", G_MIDI_IN_VOLUME, c.midiInSolo, m_channelId)); - if (c.type == ChannelType::SAMPLE) { - const m::SampleChannel& sc = static_cast(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(m_container->getChild(i++))->update(m_data); #ifdef WITH_VST + for (c::io::PluginData& plugin : m_data.plugins) + static_cast(m_container->getChild(i++))->update(plugin, m_data.enabled); +#endif -void gdMidiInputChannel::addPluginLearners() -{ - m::model::ChannelsLock cl(m::model::channels); - m::model::PluginsLock ml(m::model::plugins); - - m::Channel& c = m::model::get(m::model::channels, m_channelId); - - int i = 1; - for (ID id : c.pluginIds) { - - m::Plugin& p = m::model::get(m::model::plugins, id); - - Fl_Pack* pack = new Fl_Pack(m_container->x() + (i++ * (LEARNER_WIDTH + G_GUI_OUTER_MARGIN)), - m_container->y(), LEARNER_WIDTH, 200); - pack->spacing(G_GUI_INNER_MARGIN); - pack->begin(); - - geBox* header = new geBox(0, 0, LEARNER_WIDTH, G_GUI_UNIT, p.getName().c_str()); - header->box(FL_BORDER_BOX); - - for (int k = 0; k < p.getNumParameters(); k++) - m_learners.push_back(new geMidiLearnerPlugin(0, 0, LEARNER_WIDTH, p.getParameterName(k), k, p.midiInParams.at(k), p.id)); + m_channel->value(m_data.filter == -1 ? 0 : m_data.filter + 1); - pack->end(); + if (m_data.enabled) { + m_channel->activate(); + if (m_data.channelType == ChannelType::SAMPLE) + m_veloAsVol->activate(); + } + else { + m_channel->deactivate(); + m_veloAsVol->deactivate(); } } -#endif - /* -------------------------------------------------------------------------- */ @@ -217,15 +254,7 @@ void gdMidiInputChannel::cb_veloAsVol(Fl_Widget* w, void* p) { ((gdMidiInputChan void gdMidiInputChannel::cb_enable() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - c.midiIn = m_enable->value(); - }); - - m_enable->value() ? m_channel->activate() : m_channel->deactivate(); - - for (auto* l : m_learners) - m_enable->value() ? l->activate() : l->deactivate(); + c::io::channel_enableMidiLearn(m_data.channelId, m_enable->value()); } @@ -234,10 +263,7 @@ void gdMidiInputChannel::cb_enable() void gdMidiInputChannel::cb_veloAsVol() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - static_cast(c).midiInVeloAsVol = m_veloAsVol->value(); - }); + c::io::channel_enableVelocityAsVol(m_data.channelId, m_veloAsVol->value()); } @@ -246,10 +272,7 @@ void gdMidiInputChannel::cb_veloAsVol() void gdMidiInputChannel::cb_setChannel() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - c.midiInFilter = m_channel->value() == 0 ? -1 : m_channel->value() - 1; - u::log::print("[gdMidiInputChannel] Set MIDI channel to %d\n", c.midiInFilter); - }); + c::io::channel_setMidiInputFilter(m_data.channelId, + m_channel->value() == 0 ? -1 : m_channel->value() - 1); } }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiInputChannel.h b/src/gui/dialogs/midiIO/midiInputChannel.h index c6f520d..8fff86c 100644 --- a/src/gui/dialogs/midiIO/midiInputChannel.h +++ b/src/gui/dialogs/midiIO/midiInputChannel.h @@ -31,10 +31,11 @@ #define GD_MIDI_INPUT_CHANNEL_H +#include "glue/io.h" +#include "gui/elems/midiIO/midiLearnerPack.h" #include "midiInputBase.h" -class geScroll; class geCheck; class geChoice; @@ -42,13 +43,46 @@ class geChoice; namespace giada { namespace v { -class geMidiLearner; +class geScrollPack; +class geChannelLearnerPack : public geMidiLearnerPack +{ +public: + + geChannelLearnerPack(int x, int y, const c::io::Channel_InputData& d); + + void update(const c::io::Channel_InputData&); +}; + + +/* -------------------------------------------------------------------------- */ + + +#ifdef WITH_VST + +class gePluginLearnerPack : public geMidiLearnerPack +{ +public: + + gePluginLearnerPack(int x, int y, const c::io::PluginData&); + + void update(const c::io::PluginData&, bool enabled); +}; + +#endif + + +/* -------------------------------------------------------------------------- */ + + + class gdMidiInputChannel : public gdMidiInputBase { public: gdMidiInputChannel(ID channelId); + void rebuild() override; + private: static void cb_enable(Fl_Widget* w, void* p); @@ -58,18 +92,12 @@ private: void cb_setChannel(); void cb_veloAsVol(); - void addChannelLearners(); - -#ifdef WITH_VST - - void addPluginLearners(); - -#endif - ID m_channelId; + + c::io::Channel_InputData m_data; - geScroll* m_container; - geCheck* m_veloAsVol; + geScrollPack* m_container; + geCheck* m_veloAsVol; }; }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiInputMaster.cpp b/src/gui/dialogs/midiIO/midiInputMaster.cpp index 0ed0c7b..54fea00 100644 --- a/src/gui/dialogs/midiIO/midiInputMaster.cpp +++ b/src/gui/dialogs/midiIO/midiInputMaster.cpp @@ -29,59 +29,80 @@ #include "utils/gui.h" #include "core/conf.h" #include "core/const.h" -#include "core/model/model.h" -#include "gui/elems/midiIO/midiLearnerMaster.h" +#include "gui/elems/midiIO/midiLearner.h" +#include "gui/elems/basics/scrollPack.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/check.h" #include "gui/elems/basics/choice.h" +#include "gui/elems/basics/group.h" #include "midiInputMaster.h" namespace giada { namespace v { -gdMidiInputMaster::gdMidiInputMaster() -: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)") +geMasterLearnerPack::geMasterLearnerPack(int x, int y) +: geMidiLearnerPack(x, y) { - set_modal(); + setCallbacks( + [] (int param) { c::io::master_startMidiLearn(param); }, + [] (int param) { c::io::master_clearMidiLearn(param); } + ); + addMidiLearner("rewind", G_MIDI_IN_REWIND); + addMidiLearner("play/stop", G_MIDI_IN_START_STOP); + addMidiLearner("action recording", G_MIDI_IN_ACTION_REC); + addMidiLearner("input recording", G_MIDI_IN_INPUT_REC); + addMidiLearner("metronome", G_MIDI_IN_METRONOME); + addMidiLearner("input volume", G_MIDI_IN_VOLUME_IN); + addMidiLearner("output volume", G_MIDI_IN_VOLUME_OUT); + addMidiLearner("sequencer ×2", G_MIDI_IN_BEAT_DOUBLE); + addMidiLearner("sequencer ÷2", G_MIDI_IN_BEAT_HALF); +} - m::model::midiIn.lock(); - const m::model::MidiIn* midiIn = m::model::midiIn.get(); - Fl_Group* groupHeader = new Fl_Group(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, w(), 20); - groupHeader->begin(); +/* -------------------------------------------------------------------------- */ - m_enable = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT, - "Enable MIDI input"); - m_channel = new geChoice(m_enable->x()+m_enable->w()+44, G_GUI_OUTER_MARGIN, 120, G_GUI_UNIT); - groupHeader->resizable(nullptr); - groupHeader->end(); - - Fl_Pack* pack = new Fl_Pack(G_GUI_OUTER_MARGIN, groupHeader->y()+groupHeader->h()+G_GUI_OUTER_MARGIN, - LEARNER_WIDTH, 212); - pack->spacing(G_GUI_INNER_MARGIN); - pack->begin(); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "rewind", G_MIDI_IN_REWIND, midiIn->rewind)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "play/stop", G_MIDI_IN_START_STOP, midiIn->startStop)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "action recording", G_MIDI_IN_ACTION_REC, midiIn->actionRec)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input recording", G_MIDI_IN_INPUT_REC, midiIn->inputRec)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "metronome", G_MIDI_IN_METRONOME, midiIn->volumeIn)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "input volume", G_MIDI_IN_VOLUME_IN, midiIn->volumeOut)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "output volume", G_MIDI_IN_VOLUME_OUT, midiIn->beatDouble)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer ×2", G_MIDI_IN_BEAT_DOUBLE, midiIn->beatHalf)); - m_learners.push_back(new geMidiLearnerMaster(0, 0, LEARNER_WIDTH, "sequencer ÷2", G_MIDI_IN_BEAT_HALF, midiIn->metronome)); - pack->end(); - m_ok = new geButton(w()-88, pack->y()+pack->h()+G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close"); +void geMasterLearnerPack::update(const c::io::Master_InputData& d) +{ + learners[0]->update(d.rewind); + learners[1]->update(d.startStop); + learners[2]->update(d.actionRec); + learners[3]->update(d.inputRec); + learners[4]->update(d.metronome); + learners[5]->update(d.volumeIn); + learners[6]->update(d.volumeOut); + learners[7]->update(d.beatDouble); + learners[8]->update(d.beatHalf); + setEnabled(d.enabled); +} + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + + +gdMidiInputMaster::gdMidiInputMaster() +: gdMidiInputBase(m::conf::conf.midiInputX, m::conf::conf.midiInputY, 300, 284, "MIDI Input Setup (global)") +{ end(); - for (geMidiLearnerBase* l : m_learners) - midiIn->enabled ? l->activate() : l->deactivate(); + geGroup* groupHeader = new geGroup(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN); + m_enable = new geCheck(0, 0, 120, G_GUI_UNIT, "Enable MIDI input"); + m_channel = new geChoice(m_enable->x() + m_enable->w() + 44, 0, 120, G_GUI_UNIT); + groupHeader->resizable(nullptr); + groupHeader->add(m_enable); + groupHeader->add(m_channel); - m_ok->callback(cb_close, (void*)this); + m_learners = new geMasterLearnerPack(G_GUI_OUTER_MARGIN, groupHeader->y() + groupHeader->h() + G_GUI_OUTER_MARGIN); + m_ok = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close"); - m_enable->value(midiIn->enabled); + add(groupHeader); + add(m_learners); + add(m_ok); + + m_ok->callback(cb_close, (void*)this); m_enable->callback(cb_enable, (void*)this); m_channel->add("Channel (any)"); @@ -101,14 +122,12 @@ gdMidiInputMaster::gdMidiInputMaster() m_channel->add("Channel 14"); m_channel->add("Channel 15"); m_channel->add("Channel 16"); - - m_channel->value(midiIn->filter - 1 ? 0 : midiIn->filter + 1); m_channel->callback(cb_setChannel, (void*)this); - midiIn->enabled ? m_channel->activate() : m_channel->deactivate(); - - m::model::midiIn.unlock(); u::gui::setFavicon(this); + + set_modal(); + rebuild(); show(); } @@ -116,6 +135,21 @@ gdMidiInputMaster::gdMidiInputMaster() /* -------------------------------------------------------------------------- */ +void gdMidiInputMaster::rebuild() +{ + m_data = c::io::master_getInputData(); + + m_enable->value(m_data.enabled); + m_channel->value(m_data.filter - 1 ? 0 : m_data.filter + 1); + m_learners->update(m_data); + + m_data.enabled ? m_channel->activate() : m_channel->deactivate(); +} + + +/* -------------------------------------------------------------------------- */ + + void gdMidiInputMaster::cb_enable(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_enable(); } void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMaster*)p)->cb_setChannel(); } @@ -125,15 +159,7 @@ void gdMidiInputMaster::cb_setChannel(Fl_Widget* w, void* p) { ((gdMidiInputMast void gdMidiInputMaster::cb_enable() { - m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m) - { - m.enabled = m_enable->value(); - }); - - m_enable->value() ? m_channel->activate() : m_channel->deactivate(); - - for (geMidiLearnerBase* l : m_learners) - m_enable->value() ? l->activate() : l->deactivate(); + c::io::master_enableMidiLearn(m_enable->value()); } @@ -142,9 +168,6 @@ void gdMidiInputMaster::cb_enable() void gdMidiInputMaster::cb_setChannel() { - m::model::onSwap(m::model::midiIn, [&](m::model::MidiIn& m) - { - m.filter = m_channel->value() == 0 ? -1 : m_channel->value() - 1; - }); + c::io::master_setMidiFilter(m_channel->value() == 0 ? -1 : m_channel->value() - 1); } }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiInputMaster.h b/src/gui/dialogs/midiIO/midiInputMaster.h index 7d0ef96..b3655ec 100644 --- a/src/gui/dialogs/midiIO/midiInputMaster.h +++ b/src/gui/dialogs/midiIO/midiInputMaster.h @@ -29,7 +29,8 @@ #define GD_MIDI_INPUT_MASTER_H -#include "core/model/model.h" +#include "glue/io.h" +#include "gui/elems/midiIO/midiLearnerPack.h" #include "midiInputBase.h" @@ -40,18 +41,37 @@ class geChoice; namespace giada { namespace v { +class geMasterLearnerPack : public geMidiLearnerPack +{ +public: + + geMasterLearnerPack(int x, int y); + + void update(const c::io::Master_InputData&); +}; + + +/* -------------------------------------------------------------------------- */ + + class gdMidiInputMaster : public gdMidiInputBase { public: gdMidiInputMaster(); + void rebuild() override; + private: static void cb_enable(Fl_Widget* w, void* p); static void cb_setChannel(Fl_Widget* w, void* p); void cb_enable(); void cb_setChannel(); + + c::io::Master_InputData m_data; + + geMasterLearnerPack* m_learners; }; }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiOutputBase.cpp b/src/gui/dialogs/midiIO/midiOutputBase.cpp index b559215..b8dfe65 100644 --- a/src/gui/dialogs/midiIO/midiOutputBase.cpp +++ b/src/gui/dialogs/midiIO/midiOutputBase.cpp @@ -26,6 +26,7 @@ #include "glue/io.h" +#include "gui/elems/midiIO/midiLearner.h" #include "gui/elems/basics/check.h" #include "midiOutputBase.h" @@ -33,29 +34,49 @@ namespace giada { namespace v { -gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId) -: gdWindow (w, h, "Midi Output Setup"), - m_channelId(channelId) +geLightningLearnerPack::geLightningLearnerPack(int x, int y, ID channelId) +: geMidiLearnerPack(x, y) { + setCallbacks( + [channelId] (int param) { c::io::channel_startMidiLearn(param, channelId); }, + [channelId] (int param) { c::io::channel_clearMidiLearn(param, channelId); } + ); + addMidiLearner("playing", G_MIDI_OUT_L_PLAYING); + addMidiLearner("mute", G_MIDI_OUT_L_MUTE); + addMidiLearner("solo", G_MIDI_OUT_L_SOLO); } /* -------------------------------------------------------------------------- */ -gdMidiOutputBase::~gdMidiOutputBase() +void geLightningLearnerPack::update(const c::io::Channel_OutputData& d) { - c::io::stopMidiLearn(); + learners[0]->update(d.lightningPlaying); + learners[1]->update(d.lightningMute); + learners[2]->update(d.lightningSolo); + setEnabled(d.lightningEnabled); } +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void gdMidiOutputBase::refresh() +gdMidiOutputBase::gdMidiOutputBase(int w, int h, ID channelId) +: gdWindow (w, h, "Midi Output Setup") +, m_channelId(channelId) { - for (geMidiLearnerBase* l : m_learners) - l->refresh(); +} + + +/* -------------------------------------------------------------------------- */ + + +gdMidiOutputBase::~gdMidiOutputBase() +{ + c::io::stopMidiLearn(); } @@ -80,23 +101,16 @@ void gdMidiOutputBase::cb_close() void gdMidiOutputBase::cb_enableLightning() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - c.midiOutL = m_enableLightning->value(); - }); - - for (geMidiLearnerBase* l : m_learners) - m_enableLightning->value() ? l->activate() : l->deactivate(); + c::io::channel_enableMidiLightning(m_channelId, m_enableLightning->value()); } /* -------------------------------------------------------------------------- */ -void gdMidiOutputBase::setTitle(int chanNum) +void gdMidiOutputBase::setTitle(ID channelId) { - std::string tmp = "MIDI Output Setup (channel " + std::to_string(chanNum) + ")"; + std::string tmp = "MIDI Output Setup (channel " + std::to_string(channelId) + ")"; copy_label(tmp.c_str()); } - }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiOutputBase.h b/src/gui/dialogs/midiIO/midiOutputBase.h index de95cf1..84cf498 100644 --- a/src/gui/dialogs/midiIO/midiOutputBase.h +++ b/src/gui/dialogs/midiIO/midiOutputBase.h @@ -30,8 +30,10 @@ #include "core/types.h" +#include "glue/io.h" +#include "gui/elems/midiIO/midiLearnerPack.h" +#include "gui/elems/midiIO/midiLearner.h" #include "gui/dialogs/window.h" -#include "gui/elems/midiIO/midiLearnerBase.h" class geButton; @@ -45,13 +47,22 @@ only with channels. Both MidiOutputMidiCh and MidiOutputSampleCh have the MIDI lighting widget set. In addition MidiOutputMidiCh has the MIDI message output box. */ -/* TODO - gdMidiOutput is almost the same thing of gdMidiInput. Create another -parent class gdMidiIO to inherit from */ - namespace giada { namespace v { -class geMidiLearner; +class geLightningLearnerPack : public geMidiLearnerPack +{ +public: + + geLightningLearnerPack(int x, int y, ID channelId); + + void update(const c::io::Channel_OutputData&); +}; + + +/* -------------------------------------------------------------------------- */ + + class gdMidiOutputBase : public gdWindow { public: @@ -59,8 +70,6 @@ public: gdMidiOutputBase(int w, int h, ID channelId); ~gdMidiOutputBase(); - void refresh() override; - protected: /* cb_close @@ -75,14 +84,15 @@ protected: /* setTitle * set window title. */ - void setTitle(int chanNum); + void setTitle(ID channelId); ID m_channelId; - - std::vector 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:: diff --git a/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp b/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp index 0ccf7f1..f4c7385 100644 --- a/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp +++ b/src/gui/dialogs/midiIO/midiOutputMidiCh.cpp @@ -25,13 +25,13 @@ * -------------------------------------------------------------------------- */ -#include "core/channels/midiChannel.h" -#include "core/model/model.h" -#include "utils/gui.h" -#include "gui/elems/midiIO/midiLearnerChannel.h" +#include +#include "glue/io.h" +#include "gui/elems/midiIO/midiLearner.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/check.h" #include "gui/elems/basics/choice.h" +#include "utils/gui.h" #include "midiOutputMidiCh.h" @@ -41,26 +41,25 @@ namespace v gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId) : gdMidiOutputBase(300, 168, channelId) { - m::model::ChannelsLock l(m::model::channels); - m::MidiChannel& c = static_cast(m::model::get(m::model::channels, m_channelId)); - + end(); setTitle(m_channelId + 1); - begin(); - m_enableOut = new geCheck(x()+8, y()+8, 150, 20, "Enable MIDI output"); - m_chanListOut = new geChoice(w()-108, y()+8, 100, 20); + m_enableOut = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 150, G_GUI_UNIT, "Enable MIDI output"); + m_chanListOut = new geChoice(w()-108, G_GUI_OUTER_MARGIN, 100, G_GUI_UNIT); - m_enableLightning = new geCheck(x()+8, m_chanListOut->y()+m_chanListOut->h()+8, 120, 20, "Enable MIDI lightning output"); - m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+8, - w()-16, "playing", G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+32, - w()-16, "mute", G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(x()+8, m_enableLightning->y()+m_enableLightning->h()+56, - w()-16, "solo", G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId)); + m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, m_chanListOut->y() + m_chanListOut->h() + G_GUI_OUTER_MARGIN, + 120, G_GUI_UNIT, "Enable MIDI lightning output"); + + m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN, + m_enableLightning->y() + m_enableLightning->h() + G_GUI_OUTER_MARGIN, channelId); + + m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + G_GUI_OUTER_MARGIN, 80, G_GUI_UNIT, "Close"); - m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close"); - - end(); + add(m_enableOut); + add(m_chanListOut); + add(m_enableLightning); + add(m_learners); + add(m_close); m_chanListOut->add("Channel 1"); m_chanListOut->add("Channel 2"); @@ -80,24 +79,15 @@ gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId) m_chanListOut->add("Channel 16"); m_chanListOut->value(0); - if (c.midiOut) - m_enableOut->value(1); - else - m_chanListOut->deactivate(); - - m_enableLightning->value(c.midiOutL); - for (geMidiLearnerBase* l : m_learners) - c.midiOutL ? l->activate() : l->deactivate(); - - m_chanListOut->value(c.midiOutChan); m_chanListOut->callback(cb_setChannel, (void*)this); - m_enableOut->callback(cb_enableOut, (void*)this); m_enableLightning->callback(cb_enableLightning, (void*)this); m_close->callback(cb_close, (void*)this); - set_modal(); u::gui::setFavicon(this); + + set_modal(); + rebuild(); show(); } @@ -105,6 +95,23 @@ gdMidiOutputMidiCh::gdMidiOutputMidiCh(ID channelId) /* -------------------------------------------------------------------------- */ +void gdMidiOutputMidiCh::rebuild() +{ + m_data = c::io::channel_getOutputData(m_channelId); + + assert(m_data.output.has_value()); + + m_learners->update(m_data); + m_chanListOut->value(m_data.output->filter); + m_enableOut->value(m_data.output->enabled); + + m_data.output->enabled ? m_chanListOut->activate() : m_chanListOut->deactivate(); +} + + +/* -------------------------------------------------------------------------- */ + + void gdMidiOutputMidiCh::cb_enableOut (Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_enableOut(); } void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget *w, void *p) { ((gdMidiOutputMidiCh*)p)->cb_setChannel(); } @@ -114,13 +121,7 @@ void gdMidiOutputMidiCh::cb_setChannel(Fl_Widget *w, void *p) { ((gdMidiOutputMi void gdMidiOutputMidiCh::cb_enableOut() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - static_cast(c).midiOut = m_enableOut->value(); - static_cast(c).midiOutChan = m_chanListOut->value(); - }); - - m_enableOut->value() ? m_chanListOut->activate() : m_chanListOut->deactivate(); + c::io::channel_enableMidiOutput(m_channelId, m_enableOut->value()); } @@ -129,9 +130,6 @@ void gdMidiOutputMidiCh::cb_enableOut() void gdMidiOutputMidiCh::cb_setChannel() { - m::model::onSwap(m::model::channels, m_channelId, [&](m::Channel& c) - { - static_cast(c).midiOutChan = m_chanListOut->value(); - }); + c::io::channel_setMidiOutputFilter(m_channelId, m_chanListOut->value()); } }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiOutputMidiCh.h b/src/gui/dialogs/midiIO/midiOutputMidiCh.h index 41799fd..79b44d9 100644 --- a/src/gui/dialogs/midiIO/midiOutputMidiCh.h +++ b/src/gui/dialogs/midiIO/midiOutputMidiCh.h @@ -43,6 +43,8 @@ public: gdMidiOutputMidiCh(ID channelId); + void rebuild() override; + private: static void cb_enableOut (Fl_Widget* w, void* p); diff --git a/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp b/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp index dee27dc..257d46a 100644 --- a/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp +++ b/src/gui/dialogs/midiIO/midiOutputSampleCh.cpp @@ -25,10 +25,10 @@ * -------------------------------------------------------------------------- */ +#include #include "core/model/model.h" -#include "core/channels/sampleChannel.h" #include "utils/gui.h" -#include "gui/elems/midiIO/midiLearnerChannel.h" +#include "gui/elems/midiIO/midiLearner.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/check.h" #include "midiOutputSampleCh.h" @@ -40,30 +40,39 @@ namespace v gdMidiOutputSampleCh::gdMidiOutputSampleCh(ID channelId) : gdMidiOutputBase(300, 140, channelId) { - m::model::ChannelsLock l(m::model::channels); - m::Channel& c = m::model::get(m::model::channels, m_channelId); - - setTitle(c.id); + end(); + setTitle(m_channelId); - m_enableLightning = new geCheck(8, 8, 120, 20, "Enable MIDI lightning output"); - m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+8, w()-16, "playing", - G_MIDI_OUT_L_PLAYING, c.midiOutLplaying, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+32, w()-16, "mute", - G_MIDI_OUT_L_MUTE, c.midiOutLmute, m_channelId)); - m_learners.push_back(new geMidiLearnerChannel(8, m_enableLightning->y()+m_enableLightning->h()+56, w()-16, "solo", - G_MIDI_OUT_L_SOLO, c.midiOutLsolo, m_channelId)); + m_enableLightning = new geCheck(G_GUI_OUTER_MARGIN, G_GUI_OUTER_MARGIN, 120, 20, "Enable MIDI lightning output"); - m_close = new geButton(w()-88, m_enableLightning->y()+m_enableLightning->h()+84, 80, 20, "Close"); - m_close->callback(cb_close, (void*)this); + m_learners = new geLightningLearnerPack(G_GUI_OUTER_MARGIN, + m_enableLightning->y() + m_enableLightning->h() + 8, channelId); + + m_close = new geButton(w() - 88, m_learners->y() + m_learners->h() + 8, 80, 20, "Close"); + + add(m_enableLightning); + add(m_learners); + add(m_close); - m_enableLightning->value(c.midiOutL); + m_close->callback(cb_close, (void*)this); m_enableLightning->callback(cb_enableLightning, (void*)this); - for (geMidiLearnerBase* l : m_learners) - c.midiOutL ? l->activate() : l->deactivate(); + u::gui::setFavicon(this); set_modal(); - u::gui::setFavicon(this); + rebuild(); show(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdMidiOutputSampleCh::rebuild() +{ + m_data = c::io::channel_getOutputData(m_channelId); + + m_enableLightning->value(m_data.lightningEnabled); + m_learners->update(m_data); +} }} // giada::v:: diff --git a/src/gui/dialogs/midiIO/midiOutputSampleCh.h b/src/gui/dialogs/midiIO/midiOutputSampleCh.h index 0dd41f5..10f4033 100644 --- a/src/gui/dialogs/midiIO/midiOutputSampleCh.h +++ b/src/gui/dialogs/midiIO/midiOutputSampleCh.h @@ -40,6 +40,8 @@ class gdMidiOutputSampleCh : public gdMidiOutputBase public: gdMidiOutputSampleCh(ID channelId); + + void rebuild() override; }; }} // giada::v:: diff --git a/src/gui/dialogs/pluginChooser.cpp b/src/gui/dialogs/pluginChooser.cpp index 277604b..e15ed6c 100644 --- a/src/gui/dialogs/pluginChooser.cpp +++ b/src/gui/dialogs/pluginChooser.cpp @@ -30,7 +30,6 @@ #include "glue/plugin.h" #include "utils/gui.h" -#include "core/channels/channel.h" #include "core/conf.h" #include "core/pluginManager.h" #include "core/pluginHost.h" @@ -44,9 +43,9 @@ namespace giada { namespace v { -gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID chanID) -: gdWindow(X, Y, W, H, "Available plugins"), - m_chanID(chanID) +gdPluginChooser::gdPluginChooser(int X, int Y, int W, int H, ID channelId) +: gdWindow (X, Y, W, H, "Available plugins") +, m_channelId(channelId) { /* top area */ Fl_Group *group_top = new Fl_Group(8, 8, w()-16, 20); @@ -132,7 +131,7 @@ void gdPluginChooser::cb_add() int pluginIndex = browser->value() - 3; // subtract header lines if (pluginIndex < 0) return; - c::plugin::addPlugin(pluginIndex, m_chanID); + c::plugin::addPlugin(pluginIndex, m_channelId); do_callback(); } }} // giada::v:: diff --git a/src/gui/dialogs/pluginChooser.h b/src/gui/dialogs/pluginChooser.h index beee967..cd5cb66 100644 --- a/src/gui/dialogs/pluginChooser.h +++ b/src/gui/dialogs/pluginChooser.h @@ -49,7 +49,7 @@ class gdPluginChooser : public gdWindow { public: - gdPluginChooser(int x, int y, int w, int h, ID chanID); + gdPluginChooser(int x, int y, int w, int h, ID channelId); ~gdPluginChooser(); private: @@ -66,7 +66,7 @@ private: geButton* cancelBtn; gePluginBrowser* browser; - ID m_chanID; + ID m_channelId; }; }} // giada::v:: diff --git a/src/gui/dialogs/pluginList.cpp b/src/gui/dialogs/pluginList.cpp index f062261..cb025a7 100644 --- a/src/gui/dialogs/pluginList.cpp +++ b/src/gui/dialogs/pluginList.cpp @@ -30,11 +30,8 @@ #include #include -#include "core/model/model.h" -#include "core/channels/channel.h" #include "core/conf.h" #include "core/const.h" -#include "core/pluginHost.h" #include "utils/string.h" #include "utils/gui.h" #include "gui/elems/basics/liquidScroll.h" @@ -55,9 +52,9 @@ extern giada::v::gdMainWindow* G_MainWin; namespace giada { namespace v { -gdPluginList::gdPluginList(ID chanID) -: gdWindow(m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204), - m_channelId(chanID) +gdPluginList::gdPluginList(ID channelId) +: gdWindow (m::conf::conf.pluginListX, m::conf::conf.pluginListY, 468, 204) +, m_channelId(channelId) { end(); @@ -66,20 +63,9 @@ gdPluginList::gdPluginList(ID chanID) list->end(); add(list); - rebuild(); - - if (m_channelId == m::mixer::MASTER_OUT_CHANNEL_ID) - label("Master Out Plug-ins"); - else - if (m_channelId == m::mixer::MASTER_IN_CHANNEL_ID) - label("Master In Plug-ins"); - else { - std::string l = "Channel " + u::string::iToString(m_channelId + 1) + " Plug-ins"; - copy_label(l.c_str()); - } - u::gui::setFavicon(this); set_non_modal(); + rebuild(); show(); } @@ -105,17 +91,25 @@ void gdPluginList::cb_addPlugin(Fl_Widget* v, void* p) { ((gdPluginList*)p)->cb_ void gdPluginList::rebuild() { + m_plugins = c::plugin::getPlugins(m_channelId); + + if (m_plugins.channelId == m::mixer::MASTER_OUT_CHANNEL_ID) + label("Master Out Plug-ins"); + else + if (m_plugins.channelId == m::mixer::MASTER_IN_CHANNEL_ID) + label("Master In Plug-ins"); + else { + std::string l = "Channel " + u::string::iToString(m_plugins.channelId) + " Plug-ins"; + copy_label(l.c_str()); + } + /* Clear the previous list. */ list->clear(); list->scroll_to(0, 0); - m::model::ChannelsLock l(m::model::channels); - - const m::Channel& ch = m::model::get(m::model::channels, m_channelId); - - for (ID pluginId : ch.pluginIds) - list->addWidget(new gePluginElement(pluginId, m_channelId, 0, 0, 0)); + for (ID pluginId : m_plugins.pluginIds) + list->addWidget(new gePluginElement(0, 0, 0, c::plugin::getPlugin(pluginId, m_plugins.channelId))); addPlugin = list->addWidget(new geButton(0, 0, 0, G_GUI_UNIT, "-- add new plugin --")); @@ -133,7 +127,7 @@ void gdPluginList::cb_addPlugin() int ww = m::conf::conf.pluginChooserW; int wh = m::conf::conf.pluginChooserH; u::gui::openSubWindow(G_MainWin, new v::gdPluginChooser(wx, wy, ww, wh, - m_channelId), WID_FX_CHOOSER); + m_plugins.channelId), WID_FX_CHOOSER); } diff --git a/src/gui/dialogs/pluginList.h b/src/gui/dialogs/pluginList.h index 8ac8f46..a56a261 100644 --- a/src/gui/dialogs/pluginList.h +++ b/src/gui/dialogs/pluginList.h @@ -32,7 +32,7 @@ #define GD_PLUGINLIST_H -#include "core/pluginHost.h" +#include "glue/plugin.h" #include "window.h" @@ -48,7 +48,7 @@ class gdPluginList : public gdWindow { public: - gdPluginList(ID chanID); + gdPluginList(ID channelId); ~gdPluginList(); void rebuild() override; @@ -65,8 +65,8 @@ private: geLiquidScroll* list; ID m_channelId; + c::plugin::Plugins m_plugins; }; - }} // giada::v:: diff --git a/src/gui/dialogs/pluginWindow.cpp b/src/gui/dialogs/pluginWindow.cpp index 9e038d6..f210173 100644 --- a/src/gui/dialogs/pluginWindow.cpp +++ b/src/gui/dialogs/pluginWindow.cpp @@ -29,9 +29,8 @@ #include +#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" @@ -41,32 +40,28 @@ 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; iy() + (i * (G_GUI_UNIT + G_GUI_INNER_MARGIN)); + int labelWidth = 100; // TODO + for (int index : m_plugin.paramIndexes) { + int py = m_list->y() + (index * (G_GUI_UNIT + G_GUI_INNER_MARGIN)); int pw = m_list->w() - m_list->scrollbar_size() - (G_GUI_OUTER_MARGIN*3); - new v::gePluginParameter(i, m_pluginId, m_list->x(), py, pw, labelWidth); + new v::gePluginParameter(m_list->x(), py, pw, labelWidth, c::plugin::getParam(index, m_plugin.id)); } m_list->end(); end(); - label(p.getName().c_str()); + label(m_plugin.name.c_str()); size_range(450, (G_GUI_UNIT + (G_GUI_OUTER_MARGIN*2))); resizable(m_list); @@ -79,40 +74,10 @@ gdPluginWindow::gdPluginWindow(ID pluginId) /* -------------------------------------------------------------------------- */ -void gdPluginWindow::updateParameter(int index, bool changeSlider) -{ - static_cast(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(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 width) - width = wl; - } - return width; + for (int index : m_plugin.paramIndexes) + static_cast(m_list->child(index))->update(c::plugin::getParam(index, m_plugin.id), changeSlider); } }} // giada::v:: diff --git a/src/gui/dialogs/pluginWindow.h b/src/gui/dialogs/pluginWindow.h index 3c37c95..a0c106f 100644 --- a/src/gui/dialogs/pluginWindow.h +++ b/src/gui/dialogs/pluginWindow.h @@ -41,6 +41,11 @@ class geLiquidScroll; namespace giada { +namespace c { +namespace plugin +{ +class Plugin; +}} namespace m { class Plugin; @@ -51,16 +56,13 @@ class gdPluginWindow : public gdWindow { public: - gdPluginWindow(ID pluginId); + gdPluginWindow(const c::plugin::Plugin&); - void updateParameter(int index, bool changeSlider=false); void updateParameters(bool changeSlider=false); private: - - int getLabelWidth() const; - ID m_pluginId; + const c::plugin::Plugin& m_plugin; geLiquidScroll* m_list; }; diff --git a/src/gui/dialogs/pluginWindowGUI.cpp b/src/gui/dialogs/pluginWindowGUI.cpp index aa7c581..792003b 100644 --- a/src/gui/dialogs/pluginWindowGUI.cpp +++ b/src/gui/dialogs/pluginWindowGUI.cpp @@ -31,9 +31,7 @@ #include #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 @@ -44,14 +42,14 @@ namespace giada { namespace v { -gdPluginWindowGUI::gdPluginWindowGUI(ID pluginId) +gdPluginWindowGUI::gdPluginWindowGUI(const c::plugin::Plugin& p) #ifdef G_OS_MAC -: gdWindow (Fl::w(), Fl::h()), +: gdWindow(Fl::w(), Fl::h()) #else -: gdWindow (320, 200), +: gdWindow(320, 200) #endif - m_pluginId(pluginId), - m_ui (nullptr) +, m_plugin(p) +, m_ui (nullptr) { show(); @@ -88,15 +86,11 @@ gdPluginWindowGUI::gdPluginWindowGUI(ID pluginId) resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH); - #endif Fl::add_timeout(G_GUI_PLUGIN_RATE, cb_refresh, (void*) this); - m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p) - { - copy_label(p.getName().c_str()); - }); + copy_label(m_plugin.name.c_str()); } @@ -142,10 +136,7 @@ void gdPluginWindowGUI::cb_refresh() void gdPluginWindowGUI::openEditor(void* parent) { - m::model::onGet(m::model::plugins, m_pluginId, [&](m::Plugin& p) - { - m_ui = p.createEditor(); - }); + m_ui = m_plugin.createEditor(); if (m_ui == nullptr) { u::log::print("[gdPluginWindowGUI::openEditor] unable to create editor!\n"); return; diff --git a/src/gui/dialogs/pluginWindowGUI.h b/src/gui/dialogs/pluginWindowGUI.h index 45e3682..ac39f84 100644 --- a/src/gui/dialogs/pluginWindowGUI.h +++ b/src/gui/dialogs/pluginWindowGUI.h @@ -41,13 +41,18 @@ namespace giada { +namespace c { +namespace plugin +{ +struct Plugin; +}} namespace v { class gdPluginWindowGUI : public gdWindow { public: - gdPluginWindowGUI(ID pluginId); + gdPluginWindowGUI(const c::plugin::Plugin&); ~gdPluginWindowGUI(); private: @@ -60,7 +65,7 @@ private: void openEditor(void* parent); void closeEditor(); - ID m_pluginId; + const c::plugin::Plugin& m_plugin; juce::AudioProcessorEditor* m_ui; }; diff --git a/src/gui/dialogs/sampleEditor.cpp b/src/gui/dialogs/sampleEditor.cpp index 93884de..57a8367 100644 --- a/src/gui/dialogs/sampleEditor.cpp +++ b/src/gui/dialogs/sampleEditor.cpp @@ -31,8 +31,6 @@ #include #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" @@ -64,15 +62,14 @@ namespace giada { namespace v { -gdSampleEditor::gdSampleEditor(ID channelId, ID waveId) +gdSampleEditor::gdSampleEditor(ID channelId) : gdWindow (m::conf::conf.sampleEditorX, m::conf::conf.sampleEditorY, - m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH), - m_channelId(channelId), - m_waveId (waveId) + m::conf::conf.sampleEditorW, m::conf::conf.sampleEditorH) +, m_channelId(channelId) { Fl_Group* upperBar = createUpperBar(); - waveTools = new geWaveTools(channelId, waveId, G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN, + waveTools = new geWaveTools(G_GUI_OUTER_MARGIN, upperBar->y()+upperBar->h()+G_GUI_OUTER_MARGIN, w()-16, h()-128); Fl_Group* bottomBar = createBottomBar(G_GUI_OUTER_MARGIN, waveTools->y()+waveTools->h()+G_GUI_OUTER_MARGIN, @@ -107,7 +104,8 @@ gdSampleEditor::~gdSampleEditor() m::conf::conf.sampleEditorGridVal = atoi(grid->text()); m::conf::conf.sampleEditorGridOn = snap->value(); - c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE); + c::sampleEditor::stopPreview(); + c::sampleEditor::cleanupPreview(); } @@ -116,24 +114,21 @@ gdSampleEditor::~gdSampleEditor() void gdSampleEditor::rebuild() { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - copy_label(c.name.c_str()); - }); - - volumeTool->rebuild(); - waveTools->rebuild(); - panTool->rebuild(); - pitchTool->rebuild(); - rangeTool->rebuild(); - shiftTool->rebuild(); - - m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) - { - updateInfo(w); - if (w.isLogical()) // Logical samples (aka takes) cannot be reloaded. - reload->deactivate(); - }); + m_data = c::sampleEditor::getData(m_channelId); + + copy_label(m_data.name.c_str()); + + waveTools->rebuild(m_data); + volumeTool->rebuild(m_data); + panTool->rebuild(m_data); + pitchTool->rebuild(m_data); + rangeTool->rebuild(m_data); + shiftTool->rebuild(m_data); + + updateInfo(); + + if (m_data.isLogical) // Logical samples (aka takes) cannot be reloaded. + reload->deactivate(); } @@ -143,11 +138,7 @@ void gdSampleEditor::rebuild() void gdSampleEditor::refresh() { waveTools->refresh(); - - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - play->setStatus(c.previewMode == PreviewMode::LOOP || c.previewMode == PreviewMode::NORMAL ? 1 : 0); - }); + play->setStatus(m_data.a_getPreviewStatus() == ChannelStatus::PLAY); } @@ -192,22 +183,22 @@ Fl_Group* gdSampleEditor::createUpperBar() return g; } - +\ /* -------------------------------------------------------------------------- */ - +\ Fl_Group* gdSampleEditor::createOpTools(int x, int y, int h) { Fl_Group* g = new Fl_Group(x, y, 572, h); g->begin(); g->resizable(0); - volumeTool = new geVolumeTool(m_channelId, g->x(), g->y()); - panTool = new gePanTool(m_channelId, volumeTool->x()+volumeTool->w()+4, g->y()); + volumeTool = new geVolumeTool(m_data, g->x(), g->y()); + panTool = new gePanTool(m_data, volumeTool->x()+volumeTool->w()+4, g->y()); - pitchTool = new gePitchTool(m_channelId, g->x(), panTool->y()+panTool->h()+8); + pitchTool = new gePitchTool(m_data, g->x(), panTool->y()+panTool->h()+8); - rangeTool = new geRangeTool(m_channelId, m_waveId, g->x(), pitchTool->y()+pitchTool->h()+8); - shiftTool = new geShiftTool(m_channelId, m_waveId, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8); + rangeTool = new geRangeTool(m_data, g->x(), pitchTool->y()+pitchTool->h()+8); + shiftTool = new geShiftTool(m_data, rangeTool->x()+rangeTool->w()+4, pitchTool->y()+pitchTool->h()+8); reload = new geButton(g->x()+g->w()-70, shiftTool->y(), 70, 20, "Reload"); g->end(); @@ -304,16 +295,16 @@ void gdSampleEditor::cb_enableSnap() void gdSampleEditor::cb_togglePreview() { - if (play->getStatus()) - c::sampleEditor::setPreview(m_channelId, PreviewMode::NONE); + if (!play->getStatus()) + c::sampleEditor::playPreview(loop->value()); else - c::sampleEditor::setPreview(m_channelId, loop->value() ? PreviewMode::LOOP : PreviewMode::NORMAL); + c::sampleEditor::stopPreview(); } void gdSampleEditor::cb_rewindPreview() { - c::sampleEditor::rewindPreview(m_channelId); + c::sampleEditor::setPreviewTracker(m_data.begin); } @@ -322,7 +313,7 @@ void gdSampleEditor::cb_rewindPreview() void gdSampleEditor::cb_reload() { - c::sampleEditor::reload(m_channelId, m_waveId); + c::sampleEditor::reload(m_data.channelId, m_data.waveId); redraw(); } @@ -359,27 +350,16 @@ void gdSampleEditor::cb_changeGrid() /* -------------------------------------------------------------------------- */ -void gdSampleEditor::updateInfo(const m::Wave& w) +void gdSampleEditor::updateInfo() { - std::string bitDepth = w.getBits() != 0 ? u::string::iToString(w.getBits()) : "(unknown)"; + std::string bitDepth = m_data.waveBits != 0 ? u::string::iToString(m_data.waveBits) : "(unknown)"; std::string infoText = - "File: " + w.getPath() + "\n" - "Size: " + u::string::iToString(w.getSize()) + " frames\n" - "Duration: " + u::string::iToString(w.getDuration()) + " seconds\n" + "File: " + m_data.wavePath + "\n" + "Size: " + u::string::iToString(m_data.waveSize) + " frames\n" + "Duration: " + u::string::iToString(m_data.waveDuration) + " seconds\n" "Bit depth: " + bitDepth + "\n" - "Frequency: " + u::string::iToString(w.getRate()) + " Hz\n"; + "Frequency: " + u::string::iToString(m_data.waveRate) + " Hz\n"; info->copy_label(infoText.c_str()); } - - -/* -------------------------------------------------------------------------- */ - - -void gdSampleEditor::setWaveId(ID id) -{ - m_waveId = id; - waveTools->waveId = id; - waveTools->waveform->setWaveId(id); -} }} // giada::v:: diff --git a/src/gui/dialogs/sampleEditor.h b/src/gui/dialogs/sampleEditor.h index f31c21d..12bc59a 100644 --- a/src/gui/dialogs/sampleEditor.h +++ b/src/gui/dialogs/sampleEditor.h @@ -30,6 +30,7 @@ #include "core/types.h" +#include "glue/sampleEditor.h" #include "window.h" @@ -44,7 +45,6 @@ class geStatusButton; namespace giada { namespace m { -class SampleChannel; class Wave; } namespace v @@ -56,22 +56,18 @@ class gePanTool; class gePitchTool; class geRangeTool; class geShiftTool; - class gdSampleEditor : public gdWindow { friend class geWaveform; public: - gdSampleEditor(ID channelId, ID waveId); + gdSampleEditor(ID channelId); ~gdSampleEditor(); void rebuild() override; void refresh() override; - void updateInfo(const m::Wave& w); - void setWaveId(ID id); - geChoice* grid; geCheck* snap; geBox* sep1; @@ -118,9 +114,12 @@ private: void cb_enableSnap(); void cb_togglePreview(); void cb_rewindPreview(); - + + void updateInfo(); + ID m_channelId; - ID m_waveId; + + c::sampleEditor::Data m_data; }; }} // giada::v:: diff --git a/src/gui/dispatcher.cpp b/src/gui/dispatcher.cpp index 63f8aa4..2a5e994 100644 --- a/src/gui/dispatcher.cpp +++ b/src/gui/dispatcher.cpp @@ -27,15 +27,8 @@ #include #include -#include "core/channels/channel.h" #include "core/init.h" -#include "core/const.h" -#include "core/mixer.h" -#include "core/mixerHandler.h" -#include "core/recManager.h" -#include "core/conf.h" -#include "glue/io.h" -#include "glue/main.h" +#include "glue/events.h" #include "gui/dialogs/mainWindow.h" #include "gui/elems/mainWindow/keyboard/channel.h" #include "gui/elems/mainWindow/keyboard/keyboard.h" @@ -65,13 +58,20 @@ std::function signalCb_ = nullptr; /* -------------------------------------------------------------------------- */ -void perform_(const geChannel* gch, int event) +void perform_(ID channelId, int event) { - if (event == FL_KEYDOWN) - c::io::keyPress(gch->channelId, Fl::event_ctrl(), Fl::event_shift(), G_MAX_VELOCITY); + if (event == FL_KEYDOWN) { + if (Fl::event_ctrl()) + c::events::toggleMuteChannel(channelId, Thread::MAIN); + else + if (Fl::event_shift()) + c::events::killChannel(channelId, Thread::MAIN); + else + c::events::pressChannel(channelId, G_MAX_VELOCITY, Thread::MAIN); + } else if (event == FL_KEYUP) - c::io::keyRelease(gch->channelId, Fl::event_ctrl(), Fl::event_shift()); + c::events::releaseChannel(channelId, Thread::MAIN); } @@ -86,7 +86,7 @@ void dispatchChannels_(int event) G_MainWin->keyboard->forEachChannel([=](geChannel& c) { if (c.handleKey(event)) - perform_(&c, event); + perform_(c.getData().id, event); }); } @@ -117,19 +117,19 @@ void dispatchKey(int event) if (event == FL_KEYDOWN) { if (Fl::event_key() == FL_BackSpace && !backspace_) { backspace_ = true; - m::mh::rewindSequencer(); + c::events::rewindSequencer(Thread::MAIN); } else if (Fl::event_key() == FL_End && !end_) { end_ = true; - c::main::toggleInputRec(); + c::events::toggleInputRecording(); } else if (Fl::event_key() == FL_Enter && !enter_) { enter_ = true; - m::recManager::toggleActionRec(static_cast(m::conf::conf.recTriggerMode)); + c::events::toggleActionRecording(); } else if (Fl::event_key() == ' ' && !space_) { space_ = true; - m::mh::toggleSequencer(); + c::events::toggleSequencer(Thread::MAIN); } else if (Fl::event_key() == FL_Escape && !esc_) { esc_ = true; @@ -163,10 +163,10 @@ void dispatchKey(int event) /* -------------------------------------------------------------------------- */ -void dispatchTouch(const geChannel* gch, bool status) +void dispatchTouch(const geChannel& gch, bool status) { triggerSignalCb_(); - perform_(gch, status ? FL_KEYDOWN : FL_KEYUP); + perform_(gch.getData().id, status ? FL_KEYDOWN : FL_KEYUP); } diff --git a/src/gui/dispatcher.h b/src/gui/dispatcher.h index e1670fa..bb10117 100644 --- a/src/gui/dispatcher.h +++ b/src/gui/dispatcher.h @@ -47,7 +47,7 @@ void dispatchKey(int event); /* dispatchTouch Processes a mouse click/touch event. */ -void dispatchTouch(const geChannel* gch, bool status); +void dispatchTouch(const geChannel& gch, bool status); void setSignalCallback(std::function f); }}} // giada::v::dispatcher diff --git a/src/gui/elems/actionEditor/baseActionEditor.cpp b/src/gui/elems/actionEditor/baseActionEditor.cpp index 65f5007..ee5cf1b 100644 --- a/src/gui/elems/actionEditor/baseActionEditor.cpp +++ b/src/gui/elems/actionEditor/baseActionEditor.cpp @@ -38,10 +38,12 @@ namespace giada { namespace v { -geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h) -: Fl_Group(x, y, w, h), - m_base (static_cast(window())), - m_action(nullptr) +geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, + gdBaseActionEditor* base) +: Fl_Group(x, y, w, h) +, m_data (nullptr) +, m_base (base) +, m_action(nullptr) { } @@ -51,7 +53,7 @@ geBaseActionEditor::geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h) geBaseAction* geBaseActionEditor::getActionAtCursor() const { - for (int i=0; i(child(i)); if (a->hovered) return a; diff --git a/src/gui/elems/actionEditor/baseActionEditor.h b/src/gui/elems/actionEditor/baseActionEditor.h index e9163b0..74032d9 100644 --- a/src/gui/elems/actionEditor/baseActionEditor.h +++ b/src/gui/elems/actionEditor/baseActionEditor.h @@ -43,12 +43,10 @@ class geBaseActionEditor : public Fl_Group { public: - geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h); - - /* updateActions - Rebuild the actions widgets from scratch. */ + /* updateActions + Rebuild the actions widgets from scratch. */ - virtual void rebuild() = 0; + virtual void rebuild(c::actionEditor::Data& d) = 0; /* handle Override base FL_Group events. */ @@ -63,6 +61,13 @@ public: protected: + geBaseActionEditor(Pixel x, Pixel y, Pixel w, Pixel h, gdBaseActionEditor*); + + c::actionEditor::Data* m_data; + + /* m_base + Pointer to parent class. */ + gdBaseActionEditor* m_base; /* m_action diff --git a/src/gui/elems/actionEditor/envelopeEditor.cpp b/src/gui/elems/actionEditor/envelopeEditor.cpp index aabfd2b..83fe8ec 100644 --- a/src/gui/elems/actionEditor/envelopeEditor.cpp +++ b/src/gui/elems/actionEditor/envelopeEditor.cpp @@ -30,12 +30,12 @@ #include #include "utils/log.h" #include "utils/math.h" -#include "core/channels/sampleChannel.h" #include "core/const.h" #include "core/conf.h" #include "core/action.h" #include "core/recorder.h" #include "glue/actionEditor.h" +#include "glue/channel.h" #include "gui/dialogs/actionEditor/baseActionEditor.h" #include "envelopePoint.h" #include "envelopeEditor.h" @@ -44,8 +44,8 @@ namespace giada { namespace v { -geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l) -: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH) +geEnvelopeEditor::geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor* b) +: geBaseActionEditor(x, y, 200, m::conf::conf.envelopeEditorH, b) { copy_label(l); } @@ -108,10 +108,9 @@ void geEnvelopeEditor::draw() /* -------------------------------------------------------------------------- */ -void geEnvelopeEditor::rebuild() +void geEnvelopeEditor::rebuild(c::actionEditor::Data& d) { - namespace mr = m::recorder; - namespace ca = c::actionEditor; + m_data = &d; /* Remove all existing actions and set a new width, according to the current zoom level. */ @@ -119,7 +118,7 @@ void geEnvelopeEditor::rebuild() clear(); size(m_base->fullWidth, h()); - for (const m::Action& a : m_base->getActions()) { + for (const m::Action& a : m_data->actions) { if (a.event.getStatus() != m::MidiEvent::ENVELOPE) continue; add(new geEnvelopePoint(frameToX(a.frame), valueToY(a.event.getVelocity()), a)); @@ -175,9 +174,9 @@ void geEnvelopeEditor::onAddAction() Frame f = m_base->pixelToFrame(Fl::event_x() - x()); int v = yToValue(Fl::event_y() - y()); - c::actionEditor::recordEnvelopeAction(m_base->channelId, f, v); + c::actionEditor::recordEnvelopeAction(m_data->channelId, f, v); - m_base->rebuild(); + m_base->rebuild(); // TODO - USELESS } @@ -186,9 +185,9 @@ void geEnvelopeEditor::onAddAction() void geEnvelopeEditor::onDeleteAction() { - c::actionEditor::deleteEnvelopeAction(m_base->channelId, m_action->a1); + c::actionEditor::deleteEnvelopeAction(m_data->channelId, m_action->a1); - m_base->rebuild(); + m_base->rebuild(); // TODO - USELESS } @@ -225,7 +224,7 @@ void geEnvelopeEditor::onRefreshAction() { Frame f = m_base->pixelToFrame((m_action->x() - x()) + geEnvelopePoint::SIDE / 2); float v = yToValue(m_action->y() - y(), geEnvelopePoint::SIDE); - c::actionEditor::updateEnvelopeAction(m_base->channelId, m_action->a1, f, v); + c::actionEditor::updateEnvelopeAction(m_data->channelId, m_action->a1, f, v); m_base->rebuild(); } diff --git a/src/gui/elems/actionEditor/envelopeEditor.h b/src/gui/elems/actionEditor/envelopeEditor.h index b1e5591..50def75 100644 --- a/src/gui/elems/actionEditor/envelopeEditor.h +++ b/src/gui/elems/actionEditor/envelopeEditor.h @@ -40,18 +40,16 @@ class SampleChannel; namespace v { class geEnvelopePoint; - - class geEnvelopeEditor : public geBaseActionEditor { public: - geEnvelopeEditor(Pixel x, Pixel y, const char* l); + geEnvelopeEditor(Pixel x, Pixel y, const char* l, gdBaseActionEditor*); ~geEnvelopeEditor(); void draw() override; - void rebuild() override; + void rebuild(c::actionEditor::Data& d) override; private: diff --git a/src/gui/elems/actionEditor/noteEditor.cpp b/src/gui/elems/actionEditor/noteEditor.cpp index 784833f..574280f 100644 --- a/src/gui/elems/actionEditor/noteEditor.cpp +++ b/src/gui/elems/actionEditor/noteEditor.cpp @@ -26,7 +26,6 @@ #include -#include "core/channels/midiChannel.h" #include "core/const.h" #include "core/conf.h" #include "gui/dialogs/actionEditor/midiActionEditor.h" @@ -38,14 +37,16 @@ namespace giada { namespace v { geNoteEditor::geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base) -: geScroll(x, y, 200, 422), - m_base (base) +: geScroll(x, y, 200, 422) +, m_base (base) { - pianoRoll = new gePianoRoll(x, y, m_base->fullWidth); - - size(m_base->fullWidth, m::conf::conf.pianoRollH); + end(); type(Fl_Scroll::VERTICAL_ALWAYS); + size(m_base->fullWidth, m::conf::conf.pianoRollH); + + pianoRoll = new gePianoRoll(x, y, m_base->fullWidth, base); + add(pianoRoll); } @@ -80,9 +81,9 @@ void geNoteEditor::scroll() /* -------------------------------------------------------------------------- */ -void geNoteEditor::rebuild() +void geNoteEditor::rebuild(c::actionEditor::Data& d) { size(m_base->fullWidth, h()); - pianoRoll->rebuild(); + pianoRoll->rebuild(d); } }} // giada::v:: \ No newline at end of file diff --git a/src/gui/elems/actionEditor/noteEditor.h b/src/gui/elems/actionEditor/noteEditor.h index be900d3..143dd83 100644 --- a/src/gui/elems/actionEditor/noteEditor.h +++ b/src/gui/elems/actionEditor/noteEditor.h @@ -37,8 +37,6 @@ namespace v { class gdMidiActionEditor; class gePianoRoll; - - class geNoteEditor : public geScroll { public: @@ -46,7 +44,7 @@ public: geNoteEditor(Pixel x, Pixel y, gdMidiActionEditor* base); ~geNoteEditor(); - void rebuild(); + void rebuild(c::actionEditor::Data& d); void scroll(); gePianoRoll* pianoRoll; diff --git a/src/gui/elems/actionEditor/pianoRoll.cpp b/src/gui/elems/actionEditor/pianoRoll.cpp index 4ff9a55..08c4dba 100644 --- a/src/gui/elems/actionEditor/pianoRoll.cpp +++ b/src/gui/elems/actionEditor/pianoRoll.cpp @@ -27,7 +27,6 @@ #include #include -#include "core/channels/midiChannel.h" #include "core/conf.h" #include "core/const.h" #include "core/clock.h" @@ -36,6 +35,7 @@ #include "utils/log.h" #include "utils/string.h" #include "utils/math.h" +#include "glue/channel.h" #include "glue/actionEditor.h" #include "gui/dialogs/actionEditor/baseActionEditor.h" #include "pianoItem.h" @@ -46,9 +46,9 @@ namespace giada { namespace v { -gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W) - : geBaseActionEditor(X, Y, W, 40), - pick (0) +gePianoRoll::gePianoRoll(Pixel X, Pixel Y, Pixel W, gdBaseActionEditor* b) +: geBaseActionEditor(X, Y, W, 40, b) +, pick (0) { position(x(), m::conf::conf.pianoRollY == -1 ? y()-(h()/2) : m::conf::conf.pianoRollY); } @@ -72,7 +72,7 @@ void gePianoRoll::drawSurface1() int octave = MAX_OCTAVES; - for (int i=1; i<=MAX_KEYS+1; i++) { + for (int i = 1; i <= MAX_KEYS+1; i++) { /* print key note label. C C# D D# E F F# G G# A A# B */ @@ -179,11 +179,12 @@ void gePianoRoll::draw() { fl_copy_offscreen(x(), y(), CELL_W, h(), surface1, 0, 0); -#if defined(__APPLE__) // TODO - is this still useful? - for (Pixel i=36; ifullWidth; 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; iloopWidth; i+=CELL_W) + for (Pixel i = CELL_W; i < m_base->loopWidth; i += CELL_W) fl_copy_offscreen(x()+i, y(), CELL_W, h(), surface2, 0, 0); #endif @@ -216,7 +217,7 @@ void gePianoRoll::onAddAction() { Frame frame = m_base->pixelToFrame(Fl::event_x() - x()); int note = yToNote(Fl::event_y() - y()); - c::actionEditor::recordMidiAction(m_base->channelId, note, G_MAX_VELOCITY, + c::actionEditor::recordMidiAction(m_data->channelId, note, G_MAX_VELOCITY, frame); m_base->rebuild(); // Rebuild velocityEditor as well @@ -228,7 +229,7 @@ void gePianoRoll::onAddAction() void gePianoRoll::onDeleteAction() { - c::actionEditor::deleteMidiAction(m_base->channelId, m_action->a1); + c::actionEditor::deleteMidiAction(m_data->channelId, m_action->a1); m_base->rebuild(); // Rebuild velocityEditor as well } @@ -314,7 +315,7 @@ void gePianoRoll::onRefreshAction() int note = yToNote(m_action->y() - y()); int velocity = m_action->a1.event.getVelocity(); - ca::updateMidiAction(m_base->channelId, m_action->a1, note, velocity, f1, f2); + ca::updateMidiAction(m_data->channelId, m_action->a1, note, velocity, f1, f2); m_base->rebuild(); // Rebuild velocityEditor as well } @@ -355,9 +356,9 @@ Pixel gePianoRoll::getPianoItemW(Pixel px, const m::Action& a1, const m::Action& /* -------------------------------------------------------------------------- */ -void gePianoRoll::rebuild() +void gePianoRoll::rebuild(c::actionEditor::Data& d) { - namespace ca = c::actionEditor; + m_data = &d; /* Remove all existing actions and set a new width, according to the current zoom level. */ @@ -365,7 +366,7 @@ void gePianoRoll::rebuild() clear(); size(m_base->fullWidth, (MAX_KEYS + 1) * CELL_H); - for (const m::Action& a1 : m_base->getActions()) + for (const m::Action& a1 : m_data->actions) { if (a1.event.getStatus() == m::MidiEvent::NOTE_OFF) continue; diff --git a/src/gui/elems/actionEditor/pianoRoll.h b/src/gui/elems/actionEditor/pianoRoll.h index 080ce93..3f49c2c 100644 --- a/src/gui/elems/actionEditor/pianoRoll.h +++ b/src/gui/elems/actionEditor/pianoRoll.h @@ -50,12 +50,12 @@ public: static const Pixel CELL_H = 20; static const Pixel CELL_W = 40; - gePianoRoll(Pixel x, Pixel y, Pixel w); + gePianoRoll(Pixel x, Pixel y, Pixel w, gdBaseActionEditor*); void draw() override; int handle(int e) override; - void rebuild() override; + void rebuild(c::actionEditor::Data& d) override; Pixel pick; diff --git a/src/gui/elems/actionEditor/sampleAction.cpp b/src/gui/elems/actionEditor/sampleAction.cpp index a45cb47..ba42a88 100644 --- a/src/gui/elems/actionEditor/sampleAction.cpp +++ b/src/gui/elems/actionEditor/sampleAction.cpp @@ -26,7 +26,6 @@ #include -#include "core/channels/sampleChannel.h" #include "core/const.h" #include "core/action.h" #include "sampleAction.h" diff --git a/src/gui/elems/actionEditor/sampleActionEditor.cpp b/src/gui/elems/actionEditor/sampleActionEditor.cpp index c54fc00..24bc9df 100644 --- a/src/gui/elems/actionEditor/sampleActionEditor.cpp +++ b/src/gui/elems/actionEditor/sampleActionEditor.cpp @@ -28,14 +28,13 @@ #include #include #include -#include "core/channels/sampleChannel.h" -#include "core/model/model.h" #include "core/recorder.h" #include "core/const.h" #include "core/conf.h" #include "core/action.h" #include "utils/log.h" #include "glue/actionEditor.h" +#include "glue/channel.h" #include "gui/dialogs/actionEditor/baseActionEditor.h" #include "sampleAction.h" #include "sampleActionEditor.h" @@ -44,8 +43,8 @@ namespace giada { namespace v { -geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y) -: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH) +geSampleActionEditor::geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor* b) +: geBaseActionEditor(x, y, 200, m::conf::conf.sampleActionEditorH, b) { } @@ -62,20 +61,12 @@ geSampleActionEditor::~geSampleActionEditor() /* -------------------------------------------------------------------------- */ -void geSampleActionEditor::rebuild() +void geSampleActionEditor::rebuild(c::actionEditor::Data& d) { - namespace mr = m::recorder; - namespace ca = c::actionEditor; + m_data = &d; - bool isSinglePressMode; - bool isAnyLoopMode; - - m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c) - { - const m::SampleChannel& sc = static_cast(c); - isSinglePressMode = sc.mode == ChannelMode::SINGLE_PRESS; - isAnyLoopMode = sc.isAnyLoopMode(); - }); + bool isSinglePressMode = m_data->sample->channelMode == SamplePlayerMode::SINGLE_PRESS; + bool isAnyLoopMode = m_data->sample->isLoopMode; /* Remove all existing actions and set a new width, according to the current zoom level. */ @@ -83,12 +74,12 @@ void geSampleActionEditor::rebuild() clear(); size(m_base->fullWidth, h()); - for (const m::Action& a1 : m_base->getActions()) { + for (const m::Action& a1 : m_data->actions) { if (a1.event.getStatus() == m::MidiEvent::ENVELOPE || isNoteOffSinglePress(a1)) continue; - m::Action a2 = a1.next != nullptr ? *a1.next : m::Action{}; + const m::Action& a2 = a1.next != nullptr ? *a1.next : m::Action{}; Pixel px = x() + m_base->frameToPixel(a1.frame); Pixel py = y() + 4; @@ -140,9 +131,7 @@ void geSampleActionEditor::draw() void geSampleActionEditor::onAddAction() { Frame f = m_base->pixelToFrame(Fl::event_x() - x()); - c::actionEditor::recordSampleAction(m_base->channelId, m_base->getActionType(), f); - - m_base->rebuild(); + c::actionEditor::recordSampleAction(m_data->channelId, m_base->getActionType(), f); } @@ -151,9 +140,7 @@ void geSampleActionEditor::onAddAction() void geSampleActionEditor::onDeleteAction() { - c::actionEditor::deleteSampleAction(m_base->channelId, m_action->a1); - - m_base->rebuild(); + c::actionEditor::deleteSampleAction(m_data->channelId, m_action->a1); } @@ -218,7 +205,7 @@ void geSampleActionEditor::onRefreshAction() f2 = m_base->pixelToFrame(p2); } - ca::updateSampleAction(m_base->channelId, m_action->a1, type, f1, f2); + ca::updateSampleAction(m_data->channelId, m_action->a1, type, f1, f2); m_base->rebuild(); } @@ -229,12 +216,8 @@ void geSampleActionEditor::onRefreshAction() bool geSampleActionEditor::isNoteOffSinglePress(const m::Action& a) { - bool res; - m::model::onGet(m::model::channels, m_base->channelId, [&](m::Channel& c) - { - const m::SampleChannel& sc = static_cast(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:: diff --git a/src/gui/elems/actionEditor/sampleActionEditor.h b/src/gui/elems/actionEditor/sampleActionEditor.h index 05bf0b8..1d23d3d 100644 --- a/src/gui/elems/actionEditor/sampleActionEditor.h +++ b/src/gui/elems/actionEditor/sampleActionEditor.h @@ -46,12 +46,12 @@ class geSampleActionEditor : public geBaseActionEditor { public: - geSampleActionEditor(Pixel x, Pixel y); + geSampleActionEditor(Pixel x, Pixel y, gdBaseActionEditor*); ~geSampleActionEditor(); void draw() override; - void rebuild() override; + void rebuild(c::actionEditor::Data& d) override; private: diff --git a/src/gui/elems/actionEditor/velocityEditor.cpp b/src/gui/elems/actionEditor/velocityEditor.cpp index c3d5195..5791842 100644 --- a/src/gui/elems/actionEditor/velocityEditor.cpp +++ b/src/gui/elems/actionEditor/velocityEditor.cpp @@ -30,7 +30,6 @@ #include #include "utils/log.h" #include "utils/math.h" -#include "core/channels/midiChannel.h" #include "core/const.h" #include "core/conf.h" #include "core/action.h" @@ -44,8 +43,8 @@ namespace giada { namespace v { -geVelocityEditor::geVelocityEditor(Pixel x, Pixel y) -: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH) +geVelocityEditor::geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor* b) +: geBaseActionEditor(x, y, 200, m::conf::conf.velocityEditorH, b) { } @@ -110,9 +109,9 @@ int geVelocityEditor::yToValue(Pixel px) const /* -------------------------------------------------------------------------- */ -void geVelocityEditor::rebuild() +void geVelocityEditor::rebuild(c::actionEditor::Data& d) { - namespace ca = c::actionEditor; + m_data = &d; /* Remove all existing actions and set a new width, according to the current zoom level. */ @@ -120,8 +119,8 @@ void geVelocityEditor::rebuild() clear(); size(m_base->fullWidth, h()); - for (const m::Action& action : m_base->getActions()) - { + for (const m::Action& action : m_data->actions) { + if (action.event.getStatus() == m::MidiEvent::NOTE_OFF) continue; diff --git a/src/gui/elems/actionEditor/velocityEditor.h b/src/gui/elems/actionEditor/velocityEditor.h index 3dc141a..be19b8a 100644 --- a/src/gui/elems/actionEditor/velocityEditor.h +++ b/src/gui/elems/actionEditor/velocityEditor.h @@ -46,20 +46,20 @@ class geVelocityEditor : public geBaseActionEditor { public: - geVelocityEditor(Pixel x, Pixel y); + geVelocityEditor(Pixel x, Pixel y, gdBaseActionEditor*); ~geVelocityEditor(); void draw() override; - void rebuild() override; + void rebuild(c::actionEditor::Data& d) override; private: void onMoveAction() override; void onRefreshAction() override; - void onAddAction() override{}; - void onDeleteAction() override{}; - void onResizeAction() override{}; + void onAddAction() override {}; + void onDeleteAction() override {}; + void onResizeAction() override {}; Pixel valueToY(int v) const; int yToValue(Pixel y) const; diff --git a/src/gui/elems/basics/baseButton.cpp b/src/gui/elems/basics/baseButton.cpp index eb82d80..3eb53ba 100644 --- a/src/gui/elems/basics/baseButton.cpp +++ b/src/gui/elems/basics/baseButton.cpp @@ -2,9 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * geBaseButton - * Base class for every button widget. - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual diff --git a/src/gui/elems/basics/baseButton.h b/src/gui/elems/basics/baseButton.h index 6c60eb1..1a23cee 100644 --- a/src/gui/elems/basics/baseButton.h +++ b/src/gui/elems/basics/baseButton.h @@ -2,9 +2,6 @@ * * Giada - Your Hardcore Loopmachine * - * geBaseButton - * Base class for every button widget. - * * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual @@ -35,6 +32,8 @@ #include #include +/* geBaseButton +Base class for every button widget. */ class geBaseButton : public Fl_Button { diff --git a/src/gui/elems/basics/group.cpp b/src/gui/elems/basics/group.cpp new file mode 100644 index 0000000..d888c1f --- /dev/null +++ b/src/gui/elems/basics/group.cpp @@ -0,0 +1,94 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "group.h" + + +namespace giada { +namespace v +{ +geGroup::geGroup(int x, int y) : Fl_Group(x, y, 0, 0) +{ + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +std::size_t geGroup::countChildren() const +{ + return m_widgets.size(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geGroup::add(Fl_Widget* widget) +{ + widget->position(widget->x() + x(), widget->y() + y()); + + Fl_Group::add(widget); + m_widgets.push_back(widget); + + int newW = 0; + int newH = 0; + + for (const Fl_Widget* wg : m_widgets) { + newW = std::max(newW, (wg->x() + wg->w()) - x()); + newH = std::max(newH, (wg->y() + wg->h()) - y()); + } + + /* Don't call size(newW, newH) as it changes widgets position. Adjust width + and height manually instead. */ + + w(newW); h(newH); +} + + +/* -------------------------------------------------------------------------- */ + + +Fl_Widget* geGroup::getChild(std::size_t i) +{ + return m_widgets.at(i); // Throws std::out_of_range in case +} + + +/* -------------------------------------------------------------------------- */ + + +Fl_Widget* geGroup::getLastChild() +{ + return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case +} +}} \ No newline at end of file diff --git a/src/gui/elems/basics/group.h b/src/gui/elems/basics/group.h new file mode 100644 index 0000000..3bc0ae6 --- /dev/null +++ b/src/gui/elems/basics/group.h @@ -0,0 +1,76 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_GROUP_H +#define GE_GROUP_H + + +#include +#include + + +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 m_widgets; +}; +}} + + +#endif diff --git a/src/gui/elems/basics/pack.cpp b/src/gui/elems/basics/pack.cpp new file mode 100644 index 0000000..288df56 --- /dev/null +++ b/src/gui/elems/basics/pack.cpp @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "core/const.h" +#include "pack.h" + + +namespace giada { +namespace v +{ +gePack::gePack(int x, int y, Direction d, int gutter) +: geGroup (x, y) +, m_direction(d) +, m_gutter (gutter) +{ + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePack::add(Fl_Widget* widget) +{ + if (countChildren() == 0) + widget->position(0, 0); + else + if (m_direction == Direction::HORIZONTAL) + widget->position((getLastChild()->x() + getLastChild()->w() + m_gutter) - x(), 0); + else + widget->position(0, (getLastChild()->y() + getLastChild()->h() + m_gutter) - y()); + + geGroup::add(widget); +} +}} \ No newline at end of file diff --git a/src/gui/elems/basics/pack.h b/src/gui/elems/basics/pack.h new file mode 100644 index 0000000..dda1233 --- /dev/null +++ b/src/gui/elems/basics/pack.h @@ -0,0 +1,67 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * geScroll + * Custom scroll with nice scrollbars and something else. + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 diff --git a/src/gui/elems/basics/scroll.cpp b/src/gui/elems/basics/scroll.cpp index fb40511..eb1fce0 100644 --- a/src/gui/elems/basics/scroll.cpp +++ b/src/gui/elems/basics/scroll.cpp @@ -35,8 +35,9 @@ geScroll::geScroll(int x, int y, int w, int h, int t) - : Fl_Scroll(x, y, w, h) +: Fl_Scroll(x, y, w, h) { + end(); type(t); scrollbar.color(G_COLOR_GREY_2); diff --git a/src/gui/elems/basics/scrollPack.cpp b/src/gui/elems/basics/scrollPack.cpp new file mode 100644 index 0000000..364cbff --- /dev/null +++ b/src/gui/elems/basics/scrollPack.cpp @@ -0,0 +1,89 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "core/const.h" +#include "boxtypes.h" +#include "scrollPack.h" + + +namespace giada { +namespace v +{ +geScrollPack::geScrollPack(int x, int y, int w, int h, int type, Direction dir, + int gutter) +: geScroll (x, y, w, h, type) +, m_direction(dir) +, m_gutter (gutter) +{ + end(); +} + + +/* -------------------------------------------------------------------------- */ + + +std::size_t geScrollPack::countChildren() const +{ + return m_widgets.size(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geScrollPack::add(Fl_Widget* w) +{ + if (countChildren() == 0) + w->position(x(), y()); + else + if (m_direction == Direction::HORIZONTAL) + w->position((getLastChild()->x() + getLastChild()->w() + m_gutter), y()); + else + w->position(x(), (getLastChild()->y() + getLastChild()->h() + m_gutter)); + + geScroll::add(w); + m_widgets.push_back(w); +} + + +/* -------------------------------------------------------------------------- */ + + +Fl_Widget* geScrollPack::getChild(std::size_t i) +{ + return m_widgets.at(i); // Throws std::out_of_range in case +} + +/* -------------------------------------------------------------------------- */ + + +Fl_Widget* geScrollPack::getLastChild() +{ + return m_widgets.at(m_widgets.size() - 1); // Throws std::out_of_range in case +} +}} \ No newline at end of file diff --git a/src/gui/elems/basics/scrollPack.h b/src/gui/elems/basics/scrollPack.h new file mode 100644 index 0000000..64de3b6 --- /dev/null +++ b/src/gui/elems/basics/scrollPack.h @@ -0,0 +1,73 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#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 m_widgets; + + Direction m_direction; + int m_gutter; +}; +}} + + +#endif diff --git a/src/gui/elems/config/tabBehaviors.cpp b/src/gui/elems/config/tabBehaviors.cpp index 931e761..904c78d 100644 --- a/src/gui/elems/config/tabBehaviors.cpp +++ b/src/gui/elems/config/tabBehaviors.cpp @@ -42,16 +42,10 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H) { begin(); - Fl_Group* radioGrp_1 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex - new geBox(x(), y()+10, 70, 25, "When a channel with recorded actions is halted:", FL_ALIGN_LEFT); - recsStopOnChanHalt_1 = new geRadio(x()+25, y()+35, 280, 20, "stop it immediately"); - recsStopOnChanHalt_0 = new geRadio(x()+25, y()+60, 280, 20, "play it until finished"); - radioGrp_1->end(); - - Fl_Group* radioGrp_2 = new Fl_Group(x(), radioGrp_1->y()+radioGrp_1->h(), w(), 70); // radio group for the mutex - new geBox(x(), y()+80, 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT); - chansStopOnSeqHalt_1 = new geRadio(x()+25, y()+105, 280, 20, "stop immediately all dynamic channels"); - chansStopOnSeqHalt_0 = new geRadio(x()+25, y()+130, 280, 20, "play all dynamic channels until finished"); + Fl_Group* radioGrp_2 = new Fl_Group(x(), y()+10, w(), 70); // radio group for the mutex + new geBox(x(), radioGrp_2->y(), 70, 25, "When the sequencer is halted:", FL_ALIGN_LEFT); + chansStopOnSeqHalt_1 = new geRadio(x()+25, radioGrp_2->y() + 25, 280, 20, "stop immediately all dynamic channels"); + chansStopOnSeqHalt_0 = new geRadio(x()+25, radioGrp_2->y() + 50, 280, 20, "play all dynamic channels until finished"); radioGrp_2->end(); treatRecsAsLoops = new geCheck(x(), radioGrp_2->y()+radioGrp_2->h() + 15, 280, 20, "Treat one shot channels with actions as loops"); @@ -62,13 +56,10 @@ geTabBehaviors::geTabBehaviors(int X, int Y, int W, int H) labelsize(G_GUI_FONT_SIZE_BASE); selection_color(G_COLOR_GREY_4); - m::conf::conf.recsStopOnChanHalt == 1 ? recsStopOnChanHalt_1->value(1) : recsStopOnChanHalt_0->value(1); m::conf::conf.chansStopOnSeqHalt == 1 ? chansStopOnSeqHalt_1->value(1) : chansStopOnSeqHalt_0->value(1); treatRecsAsLoops->value(m::conf::conf.treatRecsAsLoops); inputMonitorDefaultOn->value(m::conf::conf.inputMonitorDefaultOn); - recsStopOnChanHalt_1->callback(cb_radio_mutex, (void*)this); - recsStopOnChanHalt_0->callback(cb_radio_mutex, (void*)this); chansStopOnSeqHalt_1->callback(cb_radio_mutex, (void*)this); chansStopOnSeqHalt_0->callback(cb_radio_mutex, (void*)this); } @@ -97,7 +88,6 @@ void geTabBehaviors::cb_radio_mutex(Fl_Widget* w) void geTabBehaviors::save() { - m::conf::conf.recsStopOnChanHalt = recsStopOnChanHalt_1->value() == 1 ? 1 : 0; m::conf::conf.chansStopOnSeqHalt = chansStopOnSeqHalt_1->value() == 1 ? 1 : 0; m::conf::conf.treatRecsAsLoops = treatRecsAsLoops->value() == 1 ? 1 : 0; m::conf::conf.inputMonitorDefaultOn = inputMonitorDefaultOn->value() == 1 ? 1 : 0; diff --git a/src/gui/elems/config/tabBehaviors.h b/src/gui/elems/config/tabBehaviors.h index f5a9f7c..59fb16d 100644 --- a/src/gui/elems/config/tabBehaviors.h +++ b/src/gui/elems/config/tabBehaviors.h @@ -47,8 +47,6 @@ public: void save(); - geRadio *recsStopOnChanHalt_1; - geRadio *recsStopOnChanHalt_0; geRadio *chansStopOnSeqHalt_1; geRadio *chansStopOnSeqHalt_0; geCheck *treatRecsAsLoops; diff --git a/src/gui/elems/mainWindow/keyboard/channel.cpp b/src/gui/elems/mainWindow/keyboard/channel.cpp index 1c9441f..20623a4 100644 --- a/src/gui/elems/mainWindow/keyboard/channel.cpp +++ b/src/gui/elems/mainWindow/keyboard/channel.cpp @@ -27,13 +27,13 @@ #include #include -#include "core/channels/channel.h" #include "core/model/model.h" #include "core/const.h" #include "core/graphics.h" #include "core/pluginHost.h" #include "utils/gui.h" #include "glue/channel.h" +#include "glue/events.h" #include "gui/dialogs/mainWindow.h" #include "gui/dialogs/pluginList.h" #include "gui/elems/basics/button.h" @@ -51,9 +51,9 @@ extern giada::v::gdMainWindow* G_MainWin; namespace giada { namespace v { -geChannel::geChannel(int X, int Y, int W, int H, ID channelId) -: Fl_Group (X, Y, W, H), - channelId(channelId) +geChannel::geChannel(int X, int Y, int W, int H, c::channel::Data d) +: Fl_Group (X, Y, W, H) +, m_channel(d) { } @@ -97,16 +97,18 @@ void geChannel::cb_openFxWindow(Fl_Widget* v, void* p) { ((geChannel*)p)->cb_ope void geChannel::refresh() { - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - if (mainButton->visible()) - mainButton->refresh(); - if (c.recStatus == ChannelStatus::WAIT || c.playStatus == ChannelStatus::WAIT) - blink(); - playButton->setStatus(c.isPlaying()); - mute->setStatus(c.mute); - solo->setStatus(c.solo); - }); + ChannelStatus playStatus = m_channel.a_getPlayStatus(); + ChannelStatus recStatus = m_channel.a_getRecStatus(); + + if (mainButton->visible()) + mainButton->refresh(); + + if (recStatus == ChannelStatus::WAIT || playStatus == ChannelStatus::WAIT) + blink(); + + playButton->setStatus(playStatus == ChannelStatus::PLAY || playStatus == ChannelStatus::ENDING); + mute->setStatus(m_channel.a_getMute()); + solo->setStatus(m_channel.a_getSolo()); } @@ -115,7 +117,7 @@ void geChannel::refresh() void geChannel::cb_arm() { - c::channel::setArm(channelId, arm->value()); + c::events::toggleArmChannel(m_channel.id, Thread::MAIN); } @@ -124,7 +126,7 @@ void geChannel::cb_arm() void geChannel::cb_mute() { - c::channel::toggleMute(channelId); + c::events::toggleMuteChannel(m_channel.id, Thread::MAIN); } @@ -133,7 +135,7 @@ void geChannel::cb_mute() void geChannel::cb_solo() { - c::channel::toggleSolo(channelId); + c::events::toggleSoloChannel(m_channel.id, Thread::MAIN); } @@ -142,7 +144,7 @@ void geChannel::cb_solo() void geChannel::cb_changeVol() { - c::channel::setVolume(channelId, vol->value()); + c::events::setChannelVolume(m_channel.id, vol->value(), Thread::MAIN); } @@ -152,7 +154,7 @@ void geChannel::cb_changeVol() #ifdef WITH_VST void geChannel::cb_openFxWindow() { - u::gui::openSubWindow(G_MainWin, new v::gdPluginList(channelId), WID_FX_LIST); + u::gui::openSubWindow(G_MainWin, new v::gdPluginList(m_channel.id), WID_FX_LIST); } #endif @@ -218,10 +220,7 @@ void geChannel::packWidgets() bool geChannel::handleKey(int e) { - m::model::ChannelsLock l(m::model::channels); - const m::Channel& ch = m::model::get(m::model::channels, channelId); - - if (Fl::event_key() != ch.key) + if (Fl::event_key() != m_channel.key) return false; if (e == FL_KEYDOWN && !playButton->value()) { // Key not already pressed @@ -234,7 +233,16 @@ bool geChannel::handleKey(int e) playButton->value(0); return true; } - + return false; } + + +/* -------------------------------------------------------------------------- */ + + +const c::channel::Data& geChannel::getData() const +{ + return m_channel; +} }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channel.h b/src/gui/elems/mainWindow/keyboard/channel.h index 10180d8..d3fac32 100644 --- a/src/gui/elems/mainWindow/keyboard/channel.h +++ b/src/gui/elems/mainWindow/keyboard/channel.h @@ -30,6 +30,7 @@ #include +#include "glue/channel.h" #include "core/types.h" @@ -40,19 +41,14 @@ class geStatusButton; namespace giada { -namespace m -{ -class Channel; -} namespace v { class geChannelButton; - class geChannel : public Fl_Group { public: - geChannel(int x, int y, int w, int h, ID channelId); + geChannel(int x, int y, int w, int h, c::channel::Data d); void draw() override; @@ -72,7 +68,10 @@ public: bool handleKey(int e); - ID channelId; + /* getData + Returns a reference to the internal data. Read-only. */ + + const c::channel::Data& getData() const; geStatusButton* playButton; geButton* arm; @@ -115,7 +114,7 @@ protected: void cb_solo(); void cb_changeVol(); #ifdef WITH_VST - void cb_openFxWindow(); + void cb_openFxWindow(); #endif /* blink @@ -127,6 +126,11 @@ protected: Spread widgets across available space. */ void packWidgets(); + + /* m_channel + Channel's data. */ + + c::channel::Data m_channel; }; }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channelButton.cpp b/src/gui/elems/mainWindow/keyboard/channelButton.cpp index eaa2057..c8e7f25 100644 --- a/src/gui/elems/mainWindow/keyboard/channelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelButton.cpp @@ -30,6 +30,7 @@ #include "core/model/model.h" #include "core/const.h" #include "core/recorder.h" +#include "glue/channel.h" #include "utils/string.h" #include "channelButton.h" @@ -37,10 +38,9 @@ namespace giada { namespace v { -geChannelButton::geChannelButton(int x, int y, int w, int h, ID channelId) -: geButton (x, y, w, h), - m_channelId(channelId), - m_key ("") +geChannelButton::geChannelButton(int x, int y, int w, int h, const c::channel::Data& d) +: geButton (x, y, w, h) +, m_channel(d) { } @@ -50,35 +50,21 @@ geChannelButton::geChannelButton(int x, int y, int w, int h, ID channelId) void geChannelButton::refresh() { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - switch (c.playStatus) { - case ChannelStatus::OFF: - case ChannelStatus::EMPTY: - setDefaultMode(); break; - case ChannelStatus::PLAY: - setPlayMode(); break; - case ChannelStatus::ENDING: - setEndingMode(); break; - default: break; - } - - switch (c.recStatus) { - case ChannelStatus::ENDING: - setEndingMode(); break; - default: break; - } - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void geChannelButton::setKey(int k) -{ - m_key = k == 0 ? "" : std::string(1, k); - redraw(); + switch (m_channel.a_getPlayStatus()) { + case ChannelStatus::OFF: + case ChannelStatus::EMPTY: + setDefaultMode(); break; + case ChannelStatus::PLAY: + setPlayMode(); break; + case ChannelStatus::ENDING: + setEndingMode(); break; + default: break; + } + switch (m_channel.a_getRecStatus()) { + case ChannelStatus::ENDING: + setEndingMode(); break; + default: break; + } } @@ -89,7 +75,7 @@ void geChannelButton::draw() { geButton::draw(); - if (m_key == "") + if (m_channel.key == 0) return; /* draw background */ @@ -100,7 +86,7 @@ void geChannelButton::draw() fl_color(G_COLOR_LIGHT_2); fl_font(FL_HELVETICA, 11); - fl_draw(m_key.c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER); + fl_draw(std::string(1, static_cast(m_channel.key)).c_str(), x(), y(), 18, h(), FL_ALIGN_CENTER); } @@ -154,5 +140,4 @@ void geChannelButton::setEndingMode() { bgColor0 = G_COLOR_GREY_4; } - }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channelButton.h b/src/gui/elems/mainWindow/keyboard/channelButton.h index 633addb..a648d4e 100644 --- a/src/gui/elems/mainWindow/keyboard/channelButton.h +++ b/src/gui/elems/mainWindow/keyboard/channelButton.h @@ -33,23 +33,23 @@ namespace giada { -namespace m -{ -class Channel; -} +namespace c { +namespace channel +{ +struct Data; +}} namespace v { class geChannelButton : public geButton { public: - geChannelButton(int x, int y, int w, int h, ID channelId); + geChannelButton(int x, int y, int w, int h, const c::channel::Data& d); virtual void refresh(); void draw() override; - void setKey(int k); void setPlayMode(); void setEndingMode(); void setDefaultMode(const char* l=0); @@ -58,8 +58,7 @@ public: protected: - ID m_channelId; - std::string m_key; + const c::channel::Data& m_channel; }; }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.cpp b/src/gui/elems/mainWindow/keyboard/channelMode.cpp index b4dbb0b..758580b 100644 --- a/src/gui/elems/mainWindow/keyboard/channelMode.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelMode.cpp @@ -30,7 +30,8 @@ #include #include #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" @@ -42,23 +43,25 @@ 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(m_channel.sample->mode)); } @@ -69,32 +72,29 @@ void geChannelMode::draw() { fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // border - m::model::ChannelsLock l(m::model::channels); - const m::SampleChannel& ch = static_cast(m::model::get(m::model::channels, m_channelId)); - - switch (ch.mode) { - case ChannelMode::LOOP_BASIC: + switch (m_channel.sample->mode) { + case SamplePlayerMode::LOOP_BASIC: fl_draw_pixmap(loopBasic_xpm, x()+1, y()+1); break; - case ChannelMode::LOOP_ONCE: + case SamplePlayerMode::LOOP_ONCE: fl_draw_pixmap(loopOnce_xpm, x()+1, y()+1); break; - case ChannelMode::LOOP_ONCE_BAR: + case SamplePlayerMode::LOOP_ONCE_BAR: fl_draw_pixmap(loopOnceBar_xpm, x()+1, y()+1); break; - case ChannelMode::LOOP_REPEAT: + case SamplePlayerMode::LOOP_REPEAT: fl_draw_pixmap(loopRepeat_xpm, x()+1, y()+1); break; - case ChannelMode::SINGLE_BASIC: + case SamplePlayerMode::SINGLE_BASIC: fl_draw_pixmap(oneshotBasic_xpm, x()+1, y()+1); break; - case ChannelMode::SINGLE_PRESS: + case SamplePlayerMode::SINGLE_PRESS: fl_draw_pixmap(oneshotPress_xpm, x()+1, y()+1); break; - case ChannelMode::SINGLE_RETRIG: + case SamplePlayerMode::SINGLE_RETRIG: fl_draw_pixmap(oneshotRetrig_xpm, x()+1, y()+1); break; - case ChannelMode::SINGLE_ENDLESS: + case SamplePlayerMode::SINGLE_ENDLESS: fl_draw_pixmap(oneshotEndless_xpm, x()+1, y()+1); break; } @@ -112,6 +112,6 @@ void geChannelMode::cb_changeMode(Fl_Widget* v, void* p) { ((geChannelMode*)v)-> void geChannelMode::cb_changeMode(int mode) { - c::channel::setSampleMode(m_channelId, static_cast(mode)); + c::channel::setSamplePlayerMode(m_channel.id, static_cast(mode)); } }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channelMode.h b/src/gui/elems/mainWindow/keyboard/channelMode.h index b6072b8..7333f9b 100644 --- a/src/gui/elems/mainWindow/keyboard/channelMode.h +++ b/src/gui/elems/mainWindow/keyboard/channelMode.h @@ -35,17 +35,13 @@ namespace giada { -namespace m -{ -class SampleChannel; -} namespace v { class geChannelMode : public Fl_Menu_Button { public: - geChannelMode(int x, int y, int w, int h, ID channelId); + geChannelMode(int x, int y, int w, int h, c::channel::Data& d); void draw() override; @@ -54,7 +50,7 @@ private: static void cb_changeMode(Fl_Widget* v, void* p); void cb_changeMode(int mode); - ID m_channelId; + c::channel::Data& m_channel; }; }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp index 95d32fb..616708a 100644 --- a/src/gui/elems/mainWindow/keyboard/channelStatus.cpp +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.cpp @@ -26,21 +26,17 @@ #include -#include "core/channels/sampleChannel.h" -#include "core/model/model.h" -#include "core/mixer.h" -#include "core/clock.h" -#include "core/recorder.h" -#include "core/recManager.h" #include "core/const.h" +#include "glue/channel.h" #include "channelStatus.h" namespace giada { namespace v { -geChannelStatus::geChannelStatus(int x, int y, int w, int h, ID channelId) -: Fl_Box(x, y, w, h), channelId(channelId) +geChannelStatus::geChannelStatus(int x, int y, int w, int h, c::channel::Data& d) +: Fl_Box (x, y, w, h) +, m_channel(d) { } @@ -53,38 +49,32 @@ void geChannelStatus::draw() fl_rect(x(), y(), w(), h(), G_COLOR_GREY_4); // reset border fl_rectf(x()+1, y()+1, w()-2, h()-2, G_COLOR_GREY_2); // reset background - m::model::ChannelsLock l(m::model::channels); - const m::SampleChannel& ch = static_cast(m::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:: diff --git a/src/gui/elems/mainWindow/keyboard/channelStatus.h b/src/gui/elems/mainWindow/keyboard/channelStatus.h index 3afbe8e..90e0dbb 100644 --- a/src/gui/elems/mainWindow/keyboard/channelStatus.h +++ b/src/gui/elems/mainWindow/keyboard/channelStatus.h @@ -35,21 +35,24 @@ 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:: diff --git a/src/gui/elems/mainWindow/keyboard/column.cpp b/src/gui/elems/mainWindow/keyboard/column.cpp index 1a2959f..dc06502 100644 --- a/src/gui/elems/mainWindow/keyboard/column.cpp +++ b/src/gui/elems/mainWindow/keyboard/column.cpp @@ -28,8 +28,7 @@ #include #include #include -#include "core/channels/sampleChannel.h" -#include "core/channels/midiChannel.h" +#include "core/channels/state.h" #include "core/model/model.h" #include "glue/channel.h" #include "utils/log.h" @@ -76,32 +75,34 @@ void geColumn::cb_addChannel(Fl_Widget* v, void* p) { ((geColumn*)p)->cb_addChan /* -------------------------------------------------------------------------- */ -geChannel* geColumn::addChannel(ID channelId, ChannelType t, int height) +geChannel* geColumn::addChannel(c::channel::Data d) { geChannel* gch = nullptr; Fl_Widget* last = m_channels.size() == 0 ? static_cast(m_addChannelBtn) : m_channels.back(); - if (t == ChannelType::SAMPLE) - gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId); + if (d.type == ChannelType::SAMPLE) + gch = new geSampleChannel(x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d); else - gch = new geMidiChannel (x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), height, channelId); + gch = new geMidiChannel (x(), last->y() + last->h() + G_GUI_INNER_MARGIN, w(), d.height, d); geResizerBar* bar = new geResizerBar(x(), gch->y() + gch->h(), w(), G_GUI_INNER_MARGIN, G_GUI_UNIT, geResizerBar::VERTICAL, gch); /* Update the column height while dragging the resizer bar. */ - bar->onDrag = [=](const Fl_Widget* w) + bar->onDrag = [this](const Fl_Widget* w) { resizable(nullptr); size(this->w(), (child(children() - 1)->y() - y()) + G_GUI_INNER_MARGIN); + }; /* Store the channel height in model when the resizer bar is released. */ - bar->onRelease = [=](const Fl_Widget* w) + bar->onRelease = [channelId = d.id, this](const Fl_Widget* w) { - storeChannelHeight(w, channelId); + resizable(this); + c::channel::setHeight(channelId, w->h()); }; m_channels.push_back(gch); @@ -164,7 +165,7 @@ void geColumn::cb_addChannel() geChannel* geColumn::getChannel(ID channelId) const { for (geChannel* c : m_channels) - if (c->channelId == channelId) + if (c->getData().id == channelId) return c; return nullptr; } @@ -214,17 +215,4 @@ int geColumn::computeHeight() const out += c->h() + G_GUI_INNER_MARGIN; return out + m_addChannelBtn->h() + G_GUI_INNER_MARGIN; } - - -/* -------------------------------------------------------------------------- */ - - -void geColumn::storeChannelHeight(const Fl_Widget* w, ID channelId) const -{ - m::model::onSwap(m::model::channels, channelId, [&](m::Channel& c) - { - c.height = w->h(); - }); -} - }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/column.h b/src/gui/elems/mainWindow/keyboard/column.h index 745f3ef..fbcd6bf 100644 --- a/src/gui/elems/mainWindow/keyboard/column.h +++ b/src/gui/elems/mainWindow/keyboard/column.h @@ -32,6 +32,7 @@ #include #include #include +#include "glue/channel.h" #include "core/types.h" @@ -55,7 +56,7 @@ public: /* addChannel Adds a new channel in this column. */ - geChannel* addChannel(ID channelId, ChannelType t, int size); + geChannel* addChannel(c::channel::Data d); /* refreshChannels Updates channels' graphical statues. Called on each GUI cycle. */ @@ -77,7 +78,6 @@ private: int countChannels() const; int computeHeight() const; - void storeChannelHeight(const Fl_Widget* c, ID channelId) const; std::vector m_channels; diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.cpp b/src/gui/elems/mainWindow/keyboard/keyboard.cpp index 01fc0e4..46e64fa 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.cpp +++ b/src/gui/elems/mainWindow/keyboard/keyboard.cpp @@ -27,8 +27,6 @@ #include #include -#include "core/model/model.h" -#include "core/channels/sampleChannel.h" #include "glue/io.h" #include "glue/channel.h" #include "utils/fs.h" @@ -49,22 +47,10 @@ namespace giada { namespace v { geKeyboard::geKeyboard(int X, int Y, int W, int H) -: Fl_Scroll (X, Y, W, H), - m_addColumnBtn(nullptr) +: geScroll (X, Y, W, H, Fl_Scroll::BOTH_ALWAYS) +, m_addColumnBtn(nullptr) { end(); - - color(G_COLOR_GREY_1); - type(Fl_Scroll::BOTH_ALWAYS); - scrollbar.color(G_COLOR_GREY_2); - scrollbar.selection_color(G_COLOR_GREY_4); - scrollbar.labelcolor(G_COLOR_LIGHT_1); - scrollbar.slider(G_CUSTOM_BORDER_BOX); - hscrollbar.color(G_COLOR_GREY_2); - hscrollbar.selection_color(G_COLOR_GREY_4); - hscrollbar.labelcolor(G_COLOR_LIGHT_1); - hscrollbar.slider(G_CUSTOM_BORDER_BOX); - init(); } @@ -102,13 +88,8 @@ void geKeyboard::rebuild() for (ColumnLayout c : layout) addColumn(c.width, c.id); - /* Parse the model and assign each channel to its column. */ - - m::model::ChannelsLock lock(m::model::channels); - - for (const m::Channel* ch : m::model::channels) - if (!ch->isInternal()) - getColumn(ch->columnId)->addChannel(ch->id, ch->type, ch->height); + for (const c::channel::Data& ch : c::channel::getChannels()) + getColumn(ch.columnId)->addChannel(ch); redraw(); } diff --git a/src/gui/elems/mainWindow/keyboard/keyboard.h b/src/gui/elems/mainWindow/keyboard/keyboard.h index aa4fead..29728d9 100644 --- a/src/gui/elems/mainWindow/keyboard/keyboard.h +++ b/src/gui/elems/mainWindow/keyboard/keyboard.h @@ -30,8 +30,8 @@ #include -#include -#include "core/channels/channel.h" +#include +#include "gui/elems/basics/scroll.h" #include "core/idManager.h" #include "core/const.h" @@ -45,9 +45,7 @@ namespace v { class geColumn; class geChannel; -class geSampleChannel; - -class geKeyboard : public Fl_Scroll +class geKeyboard : public geScroll { public: diff --git a/src/gui/elems/mainWindow/keyboard/midiChannel.cpp b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp index c703907..34377e7 100644 --- a/src/gui/elems/mainWindow/keyboard/midiChannel.cpp +++ b/src/gui/elems/mainWindow/keyboard/midiChannel.cpp @@ -29,7 +29,6 @@ #include #include "core/const.h" #include "core/graphics.h" -#include "core/channels/midiChannel.h" #include "core/model/model.h" #include "core/recorder.h" #include "utils/gui.h" @@ -83,38 +82,37 @@ enum class Menu void menuCallback(Fl_Widget* w, void* v) { - geMidiChannel* gch = static_cast(w); + const geMidiChannel* gch = static_cast(w); + const c::channel::Data& data = gch->getData(); - Menu selectedItem = (Menu) (intptr_t) v; - - switch (selectedItem) + switch ((Menu) (intptr_t) v) { case Menu::CLEAR_ACTIONS: case Menu::__END_CLEAR_ACTION_SUBMENU__: break; case Menu::EDIT_ACTIONS: - u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(gch->channelId), WID_ACTION_EDITOR); + u::gui::openSubWindow(G_MainWin, new v::gdMidiActionEditor(data.id), WID_ACTION_EDITOR); break; case Menu::CLEAR_ACTIONS_ALL: - c::recorder::clearAllActions(gch->channelId); + c::recorder::clearAllActions(data.id); break; case Menu::SETUP_KEYBOARD_INPUT: - u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId), WID_KEY_GRABBER); + u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER); break; case Menu::SETUP_MIDI_INPUT: - u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId), WID_MIDI_INPUT); + u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT); break; case Menu::SETUP_MIDI_OUTPUT: - u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(gch->channelId), WID_MIDI_OUTPUT); + u::gui::openSubWindow(G_MainWin, new gdMidiOutputMidiCh(data.id), WID_MIDI_OUTPUT); break; case Menu::CLONE_CHANNEL: - c::channel::cloneChannel(gch->channelId); + c::channel::cloneChannel(data.id); break; case Menu::RENAME_CHANNEL: - u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId), WID_SAMPLE_NAME); + u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME); break; case Menu::DELETE_CHANNEL: - c::channel::deleteChannel(gch->channelId); + c::channel::deleteChannel(data.id); break; } } @@ -124,8 +122,9 @@ void menuCallback(Fl_Widget* w, void* v) /* -------------------------------------------------------------------------- */ -geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId) -: geChannel(X, Y, W, H, channelId) +geMidiChannel::geMidiChannel(int X, int Y, int W, int H, c::channel::Data d) +: geChannel(X, Y, W, H, d) +, m_data (d) { #if defined(WITH_VST) constexpr int delta = 6 * (G_GUI_UNIT + G_GUI_INNER_MARGIN); @@ -135,7 +134,7 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId) playButton = new geStatusButton (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm); arm = new geButton (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm); - mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId); + mainButton = new geMidiChannelButton(arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel); mute = new geStatusButton (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm); solo = new geStatusButton (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm); #if defined(WITH_VST) @@ -149,18 +148,15 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId) resizable(mainButton); - m::model::ChannelsLock l(m::model::channels); - const m::Channel& ch = m::model::get(m::model::channels, channelId); - #ifdef WITH_VST - fx->setStatus(ch.pluginIds.size() > 0); + fx->setStatus(m_channel.pluginIds.size() > 0); #endif playButton->callback(cb_playButton, (void*)this); playButton->when(FL_WHEN_CHANGED); // On keypress && on keyrelease arm->type(FL_TOGGLE_BUTTON); - arm->value(ch.armed); + arm->value(m_channel.a_isArmed()); arm->callback(cb_arm, (void*)this); #ifdef WITH_VST @@ -173,10 +169,9 @@ geMidiChannel::geMidiChannel(int X, int Y, int W, int H, ID channelId) solo->type(FL_TOGGLE_BUTTON); solo->callback(cb_solo, (void*)this); - mainButton->setKey(ch.key); mainButton->callback(cb_openMenu, (void*)this); - vol->value(ch.volume); + vol->value(m_channel.volume); vol->callback(cb_changeVol, (void*)this); size(w(), h()); // Force responsiveness @@ -195,7 +190,7 @@ void geMidiChannel::cb_openMenu(Fl_Widget* v, void* p) { ((geMidiChannel*)p)->cb void geMidiChannel::cb_playButton() { - v::dispatcher::dispatchTouch(this, playButton->value()); + v::dispatcher::dispatchTouch(*this, playButton->value()); } @@ -207,7 +202,7 @@ void geMidiChannel::cb_openMenu() Fl_Menu_Item rclick_menu[] = { {"Edit actions...", 0, menuCallback, (void*) Menu::EDIT_ACTIONS}, {"Clear actions", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS, FL_SUBMENU}, - {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL}, + {"All", 0, menuCallback, (void*) Menu::CLEAR_ACTIONS_ALL}, {0}, {"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT}, {"Setup MIDI input...", 0, menuCallback, (void*) Menu::SETUP_MIDI_INPUT}, @@ -220,11 +215,8 @@ void geMidiChannel::cb_openMenu() /* No 'clear actions' if there are no actions. */ - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - if (!c.hasActions) - rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate(); - }); + if (!m_data.hasActions) + rclick_menu[(int)Menu::CLEAR_ACTIONS].deactivate(); Fl_Menu_Button b(0, 0, 100, 50); b.box(G_CUSTOM_BORDER_BOX); diff --git a/src/gui/elems/mainWindow/keyboard/midiChannel.h b/src/gui/elems/mainWindow/keyboard/midiChannel.h index 0dd051d..c0cfed0 100644 --- a/src/gui/elems/mainWindow/keyboard/midiChannel.h +++ b/src/gui/elems/mainWindow/keyboard/midiChannel.h @@ -34,17 +34,13 @@ namespace giada { -namespace m -{ -class MidiChannel; -} namespace v { class geMidiChannel : public geChannel { public: - geMidiChannel(int x, int y, int w, int h, ID channelId); + geMidiChannel(int x, int y, int w, int h, c::channel::Data d); void resize(int x, int y, int w, int h) override; @@ -54,6 +50,8 @@ private: static void cb_openMenu(Fl_Widget* v, void* p); void cb_playButton(); void cb_openMenu(); + + c::channel::Data m_data; }; }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp b/src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp index b7c4dc1..9c5deb5 100644 --- a/src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp @@ -26,32 +26,16 @@ #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(c); - if (mc.name.empty()) - l = "-- MIDI --"; - else - l = mc.name.c_str(); - - if (mc.midiOut) - l += " (ch " + u::string::iToString(mc.midiOutChan + 1) + " out)"; - }); - - label(l.c_str()); } @@ -62,12 +46,25 @@ void geMidiChannelButton::refresh() { geChannelButton::refresh(); - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - if (m::recManager::isRecordingAction() && c.armed) - setActionRecordMode(); - }); + refreshLabel(); + + if (m_channel.a_isRecordingAction() && m_channel.a_isArmed()) + setActionRecordMode(); redraw(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMidiChannelButton::refreshLabel() +{ + std::string l = m_channel.name.empty() ? "-- MIDI --" : m_channel.name; + + if (m_channel.midi->a_isOutputEnabled()) + l += " (ch " + std::to_string(m_channel.midi->a_getFilter() + 1) + " out)"; + + label(l.c_str()); +} }} // giada::v:: diff --git a/src/gui/elems/mainWindow/keyboard/midiChannelButton.h b/src/gui/elems/mainWindow/keyboard/midiChannelButton.h index 712bfc7..45c702a 100644 --- a/src/gui/elems/mainWindow/keyboard/midiChannelButton.h +++ b/src/gui/elems/mainWindow/keyboard/midiChannelButton.h @@ -33,19 +33,19 @@ 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:: diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp index 2bfe916..1ed3cd6 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.cpp @@ -26,7 +26,8 @@ #include -#include "core/channels/sampleChannel.h" +#include "core/channels/channel.h" +#include "core/channels/samplePlayer.h" #include "core/model/model.h" #include "core/mixer.h" #include "core/conf.h" @@ -37,6 +38,7 @@ #include "core/recManager.h" #include "glue/io.h" #include "glue/channel.h" +#include "glue/events.h" #include "glue/recorder.h" #include "glue/storage.h" #include "utils/gui.h" @@ -98,57 +100,48 @@ enum class Menu void menuCallback(Fl_Widget* w, void* v) { - geSampleChannel* gch = static_cast(w); + const geSampleChannel* gch = static_cast(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(c).waveId; - inputMonitor = static_cast(c).inputMonitor; - }); - - Menu selectedItem = (Menu) (intptr_t) v; - - switch (selectedItem) { + switch ((Menu) (intptr_t) v) { case Menu::INPUT_MONITOR: { - c::channel::setInputMonitor(gch->channelId, !inputMonitor); + c::channel::setInputMonitor(data.id, !data.sample->a_getInputMonitor()); break; } case Menu::LOAD_SAMPLE: { gdWindow* w = new gdBrowserLoad("Browse sample", - m::conf::conf.samplePath.c_str(), c::storage::loadSample, gch->channelId); + m::conf::conf.samplePath.c_str(), c::storage::loadSample, data.id); u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER); break; } case Menu::EXPORT_SAMPLE: { gdWindow* w = new gdBrowserSave("Save sample", - m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, gch->channelId); + m::conf::conf.samplePath.c_str(), "", c::storage::saveSample, data.id); u::gui::openSubWindow(G_MainWin, w, WID_FILE_BROWSER); break; } case Menu::SETUP_KEYBOARD_INPUT: { - u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(gch->channelId), + u::gui::openSubWindow(G_MainWin, new gdKeyGrabber(data), WID_KEY_GRABBER); break; } case Menu::SETUP_MIDI_INPUT: { - u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(gch->channelId), + u::gui::openSubWindow(G_MainWin, new gdMidiInputChannel(data.id), WID_MIDI_INPUT); break; } case Menu::SETUP_MIDI_OUTPUT: { - u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(gch->channelId), + u::gui::openSubWindow(G_MainWin, new gdMidiOutputSampleCh(data.id), WID_MIDI_OUTPUT); break; } case Menu::EDIT_SAMPLE: { - u::gui::openSubWindow(G_MainWin, new gdSampleEditor(gch->channelId, waveId), + u::gui::openSubWindow(G_MainWin, new gdSampleEditor(data.id), WID_SAMPLE_EDITOR); break; } case Menu::EDIT_ACTIONS: { - u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(gch->channelId), + u::gui::openSubWindow(G_MainWin, new gdSampleActionEditor(data.id), WID_ACTION_EDITOR); break; } @@ -156,32 +149,32 @@ void menuCallback(Fl_Widget* w, void* v) case Menu::__END_CLEAR_ACTIONS_SUBMENU__: break; case Menu::CLEAR_ACTIONS_ALL: { - c::recorder::clearAllActions(gch->channelId); + c::recorder::clearAllActions(data.id); break; } case Menu::CLEAR_ACTIONS_VOLUME: { - c::recorder::clearVolumeActions(gch->channelId); + c::recorder::clearVolumeActions(data.id); break; } case Menu::CLEAR_ACTIONS_START_STOP: { - c::recorder::clearStartStopActions(gch->channelId); + c::recorder::clearStartStopActions(data.id); break; } case Menu::CLONE_CHANNEL: { - c::channel::cloneChannel(gch->channelId); + c::channel::cloneChannel(data.id); break; } case Menu::RENAME_CHANNEL: { - u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(gch->channelId), + u::gui::openSubWindow(G_MainWin, new gdChannelNameInput(data), WID_SAMPLE_NAME); break; } case Menu::FREE_CHANNEL: { - c::channel::freeChannel(gch->channelId); + c::channel::freeChannel(data.id); break; } case Menu::DELETE_CHANNEL: { - c::channel::deleteChannel(gch->channelId); + c::channel::deleteChannel(data.id); break; } } @@ -194,8 +187,8 @@ void menuCallback(Fl_Widget* w, void* v) /* -------------------------------------------------------------------------- */ -geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId) -: geChannel(X, Y, W, H, channelId) +geSampleChannel::geSampleChannel(int X, int Y, int W, int H, c::channel::Data d) +: geChannel(X, Y, W, H, d) { #if defined(WITH_VST) constexpr int delta = 9 * (G_GUI_UNIT + G_GUI_INNER_MARGIN); @@ -205,10 +198,10 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId) playButton = new geStatusButton (x(), y(), G_GUI_UNIT, G_GUI_UNIT, channelStop_xpm, channelPlay_xpm); arm = new geButton (playButton->x() + playButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, "", armOff_xpm, armOn_xpm); - status = new geChannelStatus (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, channelId); - mainButton = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, channelId); + status = new geChannelStatus (arm->x() + arm->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, H, m_channel); + mainButton = new geSampleChannelButton(status->x() + status->w() + G_GUI_INNER_MARGIN, y(), w() - delta, H, m_channel); readActions = new geStatusButton (mainButton->x() + mainButton->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, readActionOff_xpm, readActionOn_xpm, readActionDisabled_xpm); - modeBox = new geChannelMode (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, channelId); + modeBox = new geChannelMode (readActions->x() + readActions->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, m_channel); mute = new geStatusButton (modeBox->x() + modeBox->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, muteOff_xpm, muteOn_xpm); solo = new geStatusButton (mute->x() + mute->w() + G_GUI_INNER_MARGIN, y(), G_GUI_UNIT, G_GUI_UNIT, soloOff_xpm, soloOn_xpm); #if defined(WITH_VST) @@ -222,21 +215,15 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId) resizable(mainButton); - m::model::ChannelsLock l(m::model::channels); - const m::SampleChannel& ch = static_cast(m::model::get(m::model::channels, channelId)); - - modeBox->value(static_cast(ch.mode)); - modeBox->redraw(); - #ifdef WITH_VST - fx->setStatus(ch.pluginIds.size() > 0); + fx->setStatus(m_channel.pluginIds.size() > 0); #endif playButton->callback(cb_playButton, (void*)this); playButton->when(FL_WHEN_CHANGED); // On keypress && on keyrelease arm->type(FL_TOGGLE_BUTTON); - arm->value(ch.armed); + arm->value(m_channel.a_isArmed()); arm->callback(cb_arm, (void*)this); #ifdef WITH_VST @@ -249,13 +236,11 @@ geSampleChannel::geSampleChannel(int X, int Y, int W, int H, ID channelId) solo->type(FL_TOGGLE_BUTTON); solo->callback(cb_solo, (void*)this); - mainButton->setKey(ch.key); mainButton->callback(cb_openMenu, (void*)this); - readActions->setStatus(ch.readActions); readActions->callback(cb_readActions, (void*)this); - vol->value(ch.volume); + vol->value(m_channel.volume); vol->callback(cb_changeVol, (void*)this); size(w(), h()); // Force responsiveness @@ -275,7 +260,7 @@ void geSampleChannel::cb_readActions(Fl_Widget* v, void* p) { ((geSampleChannel* void geSampleChannel::cb_playButton() { - v::dispatcher::dispatchTouch(this, playButton->value()); + v::dispatcher::dispatchTouch(*this, playButton->value()); } @@ -284,19 +269,6 @@ void geSampleChannel::cb_playButton() void geSampleChannel::cb_openMenu() { - bool inputMonitor; - bool isEmptyOrMissing; - bool hasActions; - bool isAnyLoopMode; - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - const m::SampleChannel& sc = static_cast(c); - inputMonitor = sc.inputMonitor; - isEmptyOrMissing = sc.playStatus == ChannelStatus::EMPTY || sc.playStatus == ChannelStatus::MISSING; - hasActions = sc.hasActions; - isAnyLoopMode = sc.isAnyLoopMode(); - }); - /* If you're recording (input or actions) no menu is allowed; you can't do anything, especially deallocate the channel. */ @@ -305,7 +277,7 @@ void geSampleChannel::cb_openMenu() Fl_Menu_Item rclick_menu[] = { {"Input monitor", 0, menuCallback, (void*) Menu::INPUT_MONITOR, - FL_MENU_TOGGLE | FL_MENU_DIVIDER | (inputMonitor ? FL_MENU_VALUE : 0)}, + FL_MENU_TOGGLE | FL_MENU_DIVIDER | (m_channel.sample->a_getInputMonitor() ? FL_MENU_VALUE : 0)}, {"Load new sample...", 0, menuCallback, (void*) Menu::LOAD_SAMPLE}, {"Export sample to file...", 0, menuCallback, (void*) Menu::EXPORT_SAMPLE}, {"Setup keyboard input...", 0, menuCallback, (void*) Menu::SETUP_KEYBOARD_INPUT}, @@ -325,21 +297,20 @@ void geSampleChannel::cb_openMenu() {0} }; - if (isEmptyOrMissing) { + if (m_channel.sample->waveId == 0) { rclick_menu[(int) Menu::EXPORT_SAMPLE].deactivate(); rclick_menu[(int) Menu::EDIT_SAMPLE].deactivate(); rclick_menu[(int) Menu::FREE_CHANNEL].deactivate(); rclick_menu[(int) Menu::RENAME_CHANNEL].deactivate(); } - if (!hasActions) + if (!m_channel.hasActions) rclick_menu[(int) Menu::CLEAR_ACTIONS].deactivate(); - /* No 'clear start/stop actions' for those channels in loop mode: they cannot have start/stop actions. */ - if (isAnyLoopMode) + if (m_channel.sample->isLoop) rclick_menu[(int) Menu::CLEAR_ACTIONS_START_STOP].deactivate(); Fl_Menu_Button b(0, 0, 100, 50); @@ -360,7 +331,7 @@ void geSampleChannel::cb_openMenu() void geSampleChannel::cb_readActions() { - c::channel::toggleReadingActions(channelId); + c::events::toggleReadActionsChannel(m_channel.id, Thread::MAIN); } @@ -371,17 +342,14 @@ void geSampleChannel::refresh() { geChannel::refresh(); - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - if (c.hasData()) - status->redraw(); - if (c.hasActions) { - readActions->activate(); - readActions->setStatus(c.readActions); - } - else - readActions->deactivate(); - }); + if (m_channel.sample->waveId != 0) + status->redraw(); + if (m_channel.hasActions) { + readActions->activate(); + readActions->setStatus(m_channel.a_getReadActions()); + } + else + readActions->deactivate(); } diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannel.h b/src/gui/elems/mainWindow/keyboard/sampleChannel.h index 8e42f80..4a2ac71 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannel.h +++ b/src/gui/elems/mainWindow/keyboard/sampleChannel.h @@ -29,6 +29,7 @@ #define GE_SAMPLE_CHANNEL_H +#include "glue/channel.h" #include "channel.h" @@ -36,23 +37,19 @@ class geStatusButton; namespace giada { -namespace m -{ -class SampleChannel; -} namespace v { class geChannelMode; - class geSampleChannel : public geChannel { public: - geSampleChannel(int x, int y, int w, int h, ID channelId); + geSampleChannel(int x, int y, int w, int h, c::channel::Data d); void resize(int x, int y, int w, int h) override; void draw() override; + void refresh() override; geChannelMode* modeBox; diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp index 74aa1e5..d7d08cc 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp +++ b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp @@ -26,13 +26,6 @@ #include -#include "core/channels/sampleChannel.h" -#include "core/const.h" -#include "core/model/model.h" -#include "core/wave.h" -#include "core/mixer.h" -#include "core/recorder.h" -#include "core/recManager.h" #include "utils/string.h" #include "utils/fs.h" #include "glue/channel.h" @@ -48,33 +41,18 @@ extern giada::v::gdMainWindow* G_MainWin; namespace giada { namespace v { -geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, ID channelId) -: geChannelButton(x, y, w, h, channelId) +geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, const c::channel::Data& d) +: geChannelButton(x, y, w, h, d) { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - const m::SampleChannel& sc = static_cast(c); - - switch (sc.playStatus) { - case ChannelStatus::EMPTY: - label("-- no sample --"); - break; - case ChannelStatus::MISSING: - case ChannelStatus::WRONG: - label("* file not found! *"); - break; - default: - if (sc.name.empty()) { - m::model::onGet(m::model::waves, sc.waveId, [&](m::Wave& w) - { - label(w.getBasename(false).c_str()); - }); - } - else - label(sc.name.c_str()); - break; - } - }); + switch (m_channel.a_getPlayStatus()) { + case ChannelStatus::MISSING: + case ChannelStatus::WRONG: + label("* file not found! *"); + break; + default: + label(m_channel.sample->waveId == 0 ? "-- no sample --" : m_channel.name.c_str()); + break; + } } @@ -84,15 +62,12 @@ geSampleChannelButton::geSampleChannelButton(int x, int y, int w, int h, ID chan void geSampleChannelButton::refresh() { geChannelButton::refresh(); - - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - if (m::recManager::isRecordingInput() && c.armed) - setInputRecordMode(); - else - if (m::recManager::isRecordingAction() && c.hasData()) - setActionRecordMode(); - }); + + if (m_channel.a_isRecordingInput() && m_channel.a_isArmed()) + setInputRecordMode(); + else + if (m_channel.a_isRecordingAction() && m_channel.sample->waveId != 0 && !m_channel.sample->isLoop) + setActionRecordMode(); redraw(); } @@ -112,7 +87,7 @@ int geSampleChannelButton::handle(int e) break; } case FL_PASTE: { - c::channel::loadChannel(m_channelId, u::string::trim(u::fs::stripFileUrl(Fl::event_text()))); + c::channel::loadChannel(m_channel.id, u::string::trim(u::fs::stripFileUrl(Fl::event_text()))); ret = 1; break; } diff --git a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.h b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.h index e59cc08..01b36e7 100644 --- a/src/gui/elems/mainWindow/keyboard/sampleChannelButton.h +++ b/src/gui/elems/mainWindow/keyboard/sampleChannelButton.h @@ -33,17 +33,13 @@ 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; diff --git a/src/gui/elems/mainWindow/mainIO.cpp b/src/gui/elems/mainWindow/mainIO.cpp index d779a4d..fcd4a54 100644 --- a/src/gui/elems/mainWindow/mainIO.cpp +++ b/src/gui/elems/mainWindow/mainIO.cpp @@ -26,12 +26,10 @@ #include "core/const.h" -#include "core/model/model.h" #include "core/graphics.h" -#include "core/mixer.h" -#include "core/mixerHandler.h" -#include "core/pluginHost.h" +#include "glue/events.h" #include "glue/main.h" +#include "glue/channel.h" #include "utils/gui.h" #include "gui/elems/soundMeter.h" #include "gui/elems/basics/statusButton.h" @@ -48,13 +46,8 @@ namespace giada { namespace v { geMainIO::geMainIO(int x, int y) -: Fl_Pack(x, y, 396, G_GUI_UNIT) +: gePack(x, y, Direction::HORIZONTAL) { - type(Fl_Pack::HORIZONTAL); - spacing(G_GUI_INNER_MARGIN); - - begin(); - #if defined(WITH_VST) masterFxIn = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm); @@ -64,26 +57,32 @@ geMainIO::geMainIO(int x, int y) outMeter = new geSoundMeter (0, 0, 140, G_GUI_UNIT); outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT); masterFxOut = new geStatusButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, fxOff_xpm, fxOn_xpm); + add(masterFxIn); + add(inVol); + add(inMeter); + add(inToOut); + add(outMeter); + add(outVol); + add(masterFxOut); #else - inVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT); - inMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT); - outMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT); - outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT); + inVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT); + inMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT); + outMeter = new geSoundMeter(0, 0, 140, G_GUI_UNIT); + outVol = new geDial (0, 0, G_GUI_UNIT, G_GUI_UNIT); + add(inVol); + add(inMeter); + add(outMeter); + add(outVol); #endif - end(); - resizable(nullptr); // don't resize any widget outVol->callback(cb_outVol, (void*)this); inVol->callback(cb_inVol, (void*)this); - outVol->value(m::mh::getOutVol()); - inVol->value(m::mh::getInVol()); - #ifdef WITH_VST masterFxOut->callback(cb_masterFxOut, (void*)this); @@ -112,7 +111,7 @@ void geMainIO::cb_inToOut (Fl_Widget* v, void* p) { ((geMainIO*)p)->cb_inToOu void geMainIO::cb_outVol() { - c::main::setOutVol(outVol->value()); + c::events::setMasterOutVolume(outVol->value(), Thread::MAIN); } @@ -121,7 +120,7 @@ void geMainIO::cb_outVol() void geMainIO::cb_inVol() { - c::main::setInVol(inVol->value()); + c::events::setMasterInVolume(inVol->value(), Thread::MAIN); } @@ -144,7 +143,7 @@ void geMainIO::cb_masterFxIn() void geMainIO::cb_inToOut() { - m::mh::setInToOut(inToOut->value()); + c::main::setInToOut(inToOut->value()); } #endif @@ -155,13 +154,13 @@ void geMainIO::cb_inToOut() void geMainIO::setOutVol(float v) { - outVol->value(v); + outVol->value(v); } void geMainIO::setInVol(float v) { - inVol->value(v); + inVol->value(v); } @@ -172,13 +171,13 @@ void geMainIO::setInVol(float v) void geMainIO::setMasterFxOutFull(bool v) { - masterFxOut->setStatus(v); + masterFxOut->setStatus(v); } void geMainIO::setMasterFxInFull(bool v) { - masterFxIn->setStatus(v); + masterFxIn->setStatus(v); } #endif @@ -189,8 +188,8 @@ void geMainIO::setMasterFxInFull(bool v) void geMainIO::refresh() { - outMeter->mixerPeak = m::mixer::peakOut.load(); - inMeter->mixerPeak = m::mixer::peakIn.load(); + outMeter->mixerPeak = m_io.a_getMasterOutPeak(); + inMeter->mixerPeak = m_io.a_getMasterInPeak(); outMeter->redraw(); inMeter->redraw(); } @@ -201,20 +200,14 @@ void geMainIO::refresh() void geMainIO::rebuild() { - m::model::onGet(m::model::channels, m::mixer::MASTER_OUT_CHANNEL_ID, [&](m::Channel& c) - { - outVol->value(c.volume); -#ifdef WITH_VST - masterFxOut->setStatus(c.pluginIds.size() > 0); -#endif - }); + m_io = c::main::getIO(); - m::model::onGet(m::model::channels, m::mixer::MASTER_IN_CHANNEL_ID, [&](m::Channel& c) - { - inVol->value(c.volume); + outVol->value(m_io.masterOutVol); + inVol->value(m_io.masterInVol); #ifdef WITH_VST - masterFxIn->setStatus(c.pluginIds.size() > 0); + masterFxOut->setStatus(m_io.masterOutHasPlugins); + masterFxIn->setStatus(m_io.masterInHasPlugins); + inToOut->value(m_io.inToOut); #endif - }); } }} // giada::v:: diff --git a/src/gui/elems/mainWindow/mainIO.h b/src/gui/elems/mainWindow/mainIO.h index 479c86a..1f9a850 100644 --- a/src/gui/elems/mainWindow/mainIO.h +++ b/src/gui/elems/mainWindow/mainIO.h @@ -29,7 +29,8 @@ #define GE_MAIN_IO_H -#include +#include "gui/elems/basics/pack.h" +#include "glue/main.h" class geSoundMeter; @@ -43,7 +44,7 @@ class geButton; namespace giada { namespace v { -class geMainIO : public Fl_Pack +class geMainIO : public gePack { public: @@ -76,6 +77,8 @@ private: void cb_inToOut(); #endif + c::main::IO m_io; + geSoundMeter* outMeter; geSoundMeter* inMeter; geDial* outVol; diff --git a/src/gui/elems/mainWindow/mainMenu.cpp b/src/gui/elems/mainWindow/mainMenu.cpp index 0f84ba0..99d74be 100644 --- a/src/gui/elems/mainWindow/mainMenu.cpp +++ b/src/gui/elems/mainWindow/mainMenu.cpp @@ -27,8 +27,6 @@ #include #include -#include "core/channels/sampleChannel.h" -#include "core/channels/channel.h" #include "core/model/model.h" #include "core/const.h" #include "core/mixer.h" @@ -58,19 +56,16 @@ namespace giada { namespace v { geMainMenu::geMainMenu(int x, int y) -: Fl_Pack(x, y, 300, 20) +: gePack(x, y, Direction::HORIZONTAL) { - type(Fl_Pack::HORIZONTAL); - spacing(G_GUI_INNER_MARGIN); - - begin(); - file = new geButton(0, 0, 70, 21, "file"); edit = new geButton(0, 0, 70, 21, "edit"); config = new geButton(0, 0, 70, 21, "config"); about = new geButton(0, 0, 70, 21, "about"); - - end(); + add(file); + add(edit); + add(config); + add(about); resizable(nullptr); // don't resize any widget @@ -135,7 +130,7 @@ void geMainMenu::cb_file() } else if (strcmp(m->label(), "Close project") == 0) { - c::main::resetToInitState(/*createColumns=*/true); + c::main::closeProject(/*createColumns=*/true); } #ifndef NDEBUG else @@ -156,7 +151,7 @@ void geMainMenu::cb_file() void geMainMenu::cb_edit() { Fl_Menu_Item menu[] = { - {"Clear all samples"}, + {"Free all Sample channels"}, {"Clear all actions"}, {"Setup global MIDI input..."}, {0} @@ -177,7 +172,7 @@ void geMainMenu::cb_edit() const Fl_Menu_Item* m = menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, &b); if (!m) return; - if (strcmp(m->label(), "Clear all samples") == 0) + if (strcmp(m->label(), "Free all Sample channels") == 0) c::main::clearAllSamples(); else if (strcmp(m->label(), "Clear all actions") == 0) diff --git a/src/gui/elems/mainWindow/mainMenu.h b/src/gui/elems/mainWindow/mainMenu.h index 39e436b..9d114e2 100644 --- a/src/gui/elems/mainWindow/mainMenu.h +++ b/src/gui/elems/mainWindow/mainMenu.h @@ -29,7 +29,7 @@ #define GE_MAIN_MENU_H -#include +#include "gui/elems/basics/pack.h" class geButton; @@ -38,7 +38,7 @@ class geButton; namespace giada { namespace v { -class geMainMenu : public Fl_Pack +class geMainMenu : public gePack { public: diff --git a/src/gui/elems/mainWindow/mainTimer.cpp b/src/gui/elems/mainWindow/mainTimer.cpp index 0479c94..eb705ed 100644 --- a/src/gui/elems/mainWindow/mainTimer.cpp +++ b/src/gui/elems/mainWindow/mainTimer.cpp @@ -26,13 +26,10 @@ #include "core/const.h" -#include "core/model/model.h" -#include "core/kernelAudio.h" -#include "core/mixer.h" -#include "core/recManager.h" #include "core/graphics.h" #include "core/clock.h" #include "glue/main.h" +#include "glue/events.h" #include "utils/gui.h" #include "utils/string.h" #include "gui/elems/basics/button.h" @@ -50,27 +47,24 @@ namespace giada { namespace v { geMainTimer::geMainTimer(int x, int y) - : Fl_Group(x, y, 210, 20) +: geGroup(x, y) { - begin(); - - quantizer = new geChoice(x, y, 50, 20, "", false); - bpm = new geButton(quantizer->x()+quantizer->w()+4, y, 50, 20); - meter = new geButton(bpm->x()+bpm->w()+8, y, 50, 20); - multiplier = new geButton(meter->x()+meter->w()+4, y, 20, 20, "", multiplyOff_xpm, multiplyOn_xpm); - divider = new geButton(multiplier->x()+multiplier->w()+4, y, 20, 20, "", divideOff_xpm, divideOn_xpm); - - end(); + quantizer = new geChoice(0, 0, 50, 20, "", false); + bpm = new geButton(quantizer->x() + quantizer->w() + G_GUI_INNER_MARGIN, 0, 50, G_GUI_UNIT); + meter = new geButton(bpm->x() + bpm->w() + G_GUI_OUTER_MARGIN, 0, 50, G_GUI_UNIT); + multiplier = new geButton(meter->x() + meter->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", multiplyOff_xpm, multiplyOn_xpm); + divider = new geButton(multiplier->x() + multiplier->w() + G_GUI_INNER_MARGIN, 0, G_GUI_UNIT, G_GUI_UNIT, "", divideOff_xpm, divideOn_xpm); + add(quantizer); + add(bpm); + add(meter); + add(multiplier); + add(divider); resizable(nullptr); // don't resize any widget - bpm->copy_label(u::string::fToString(m::clock::getBpm(), 1).c_str()); bpm->callback(cb_bpm, (void*)this); - meter->callback(cb_meter, (void*)this); - - multiplier->callback(cb_multiplier, (void*)this); - + multiplier->callback(cb_multiplier, (void*)this); divider->callback(cb_divider, (void*)this); quantizer->add("off", 0, cb_quantizer, (void*)this); @@ -81,15 +75,6 @@ geMainTimer::geMainTimer(int x, int y) quantizer->add("1\\/6", 0, cb_quantizer, (void*)this); quantizer->add("1\\/8", 0, cb_quantizer, (void*)this); quantizer->value(0); // "off" by default - -#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) - - /* Can't change bpm from within Giada when using JACK. */ - - if (m::kernelAudio::getAPI() == G_SYS_API_JACK) - bpm->deactivate(); - -#endif } @@ -135,7 +120,7 @@ void geMainTimer::cb_quantizer() void geMainTimer::cb_multiplier() { - c::main::beatsMultiply(); + c::events::multiplyBeats(); } @@ -144,7 +129,7 @@ void geMainTimer::cb_multiplier() void geMainTimer::cb_divider() { - c::main::beatsDivide(); + c::events::divideBeats(); } @@ -153,7 +138,9 @@ void geMainTimer::cb_divider() void geMainTimer::refresh() { - if (m::recManager::isRecordingInput()) { + m_timer = c::main::getTimer(); + + if (m_timer.isRecordingInput) { bpm->deactivate(); meter->deactivate(); multiplier->deactivate(); @@ -163,8 +150,8 @@ void geMainTimer::refresh() /* Don't reactivate bpm when using JACK. It must stay disabled. */ #if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) - if (m::kernelAudio::getAPI() != G_SYS_API_JACK) - bpm->activate(); + if (m_timer.isUsingJack) + bpm->deactivate(); #else bpm->activate(); #endif @@ -180,12 +167,20 @@ void geMainTimer::refresh() void geMainTimer::rebuild() { - m::model::onGet(m::model::clock, [&](m::model::Clock& c) - { - setBpm(c.bpm); - setMeter(c.beats, c.bars); - setQuantizer(c.quantize); - }); + m_timer = c::main::getTimer(); + + setBpm(m_timer.bpm); + setMeter(m_timer.beats, m_timer.bars); + setQuantizer(m_timer.quantize); + +#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD) + + /* Can't change bpm from within Giada when using JACK. */ + + if (m_timer.isUsingJack) + bpm->deactivate(); + +#endif } diff --git a/src/gui/elems/mainWindow/mainTimer.h b/src/gui/elems/mainWindow/mainTimer.h index cd4b258..676b1fc 100644 --- a/src/gui/elems/mainWindow/mainTimer.h +++ b/src/gui/elems/mainWindow/mainTimer.h @@ -29,7 +29,8 @@ #define GE_MAIN_TIMER_H -#include +#include "glue/main.h" +#include "gui/elems/basics/group.h" class geButton; @@ -39,7 +40,7 @@ class geChoice; namespace giada { namespace v { -class geMainTimer : public Fl_Group +class geMainTimer : public geGroup { public: @@ -54,7 +55,7 @@ public: void setQuantizer(int q); /* setLock - Locks bpm, beter and multipliers. Used during audio recordings. */ + Locks bpm, meter and multipliers. Used during audio recordings. */ void setLock(bool v); @@ -71,6 +72,8 @@ private: void cb_multiplier(); void cb_divider(); + c::main::Timer m_timer; + geButton* bpm; geButton* meter; geChoice* quantizer; diff --git a/src/gui/elems/mainWindow/mainTransport.cpp b/src/gui/elems/mainWindow/mainTransport.cpp index 6e91586..cf7c680 100644 --- a/src/gui/elems/mainWindow/mainTransport.cpp +++ b/src/gui/elems/mainWindow/mainTransport.cpp @@ -28,12 +28,14 @@ #include "core/graphics.h" #include "core/conf.h" #include "core/clock.h" +#include "core/sequencer.h" #include "core/mixer.h" #include "core/mixerHandler.h" #include "core/recManager.h" #include "core/conf.h" #include "core/const.h" #include "glue/main.h" +#include "glue/events.h" #include "gui/elems/basics/button.h" #include "gui/elems/basics/box.h" #include "gui/elems/basics/statusButton.h" @@ -44,34 +46,39 @@ namespace giada { namespace v { geMainTransport::geMainTransport(int x, int y) -: Fl_Pack(x, y, 200, 25) +: gePack(x, y, Direction::HORIZONTAL) { - type(Fl_Pack::HORIZONTAL); - spacing(G_GUI_INNER_MARGIN); - rewind = new geButton (0, 0, 25, 25, "", rewindOff_xpm, rewindOn_xpm); play = new geStatusButton(0, 0, 25, 25, play_xpm, pause_xpm); - new geBox (0, 0, 10, 25); + spacer1 = new geBox (0, 0, 10, 25); recTriggerMode = new geButton (0, 0, 15, 25, "", recTriggerModeOff_xpm, recTriggerModeOn_xpm); recAction = new geStatusButton(0, 0, 25, 25, recOff_xpm, recOn_xpm); recInput = new geStatusButton(0, 0, 25, 25, inputRecOff_xpm, inputRecOn_xpm); - new geBox (0, 0, 10, 25); + spacer2 = new geBox (0, 0, 10, 25); metronome = new geStatusButton(0, 0, 15, 25, metronomeOff_xpm, metronomeOn_xpm); - + add(rewind); + add(play); + add(spacer1); + add(recTriggerMode); + add(recAction); + add(recInput); + add(spacer2); + add(metronome); + rewind->callback([](Fl_Widget* w, void* v) { - m::mh::rewindSequencer(); + c::events::rewindSequencer(Thread::MAIN); }); play->callback([](Fl_Widget* w, void* v) { - m::mh::toggleSequencer(); + c::events::toggleSequencer(Thread::MAIN); }); recAction->callback([](Fl_Widget* w, void* v) { - c::main::toggleActionRec(); + c::events::toggleActionRecording(); }); recInput->callback([](Fl_Widget* w, void* v) { - c::main::toggleInputRec(); + c::events::toggleInputRecording(); }); recTriggerMode->value(static_cast(m::conf::conf.recTriggerMode)); @@ -82,7 +89,7 @@ geMainTransport::geMainTransport(int x, int y) metronome->type(FL_TOGGLE_BUTTON); metronome->callback([](Fl_Widget* w, void* v) { - m::mixer::toggleMetronome(); + c::events::toggleMetronome(); }); } @@ -95,7 +102,6 @@ void geMainTransport::refresh() play->setStatus(m::clock::isRunning()); recAction->setStatus(m::recManager::isRecordingAction()); recInput->setStatus(m::recManager::isRecordingInput()); - metronome->setStatus(m::mixer::isMetronomeOn()); + metronome->setStatus(m::sequencer::isMetronomeOn()); } - }} // giada::v:: diff --git a/src/gui/elems/mainWindow/mainTransport.h b/src/gui/elems/mainWindow/mainTransport.h index dcf7a74..1338e39 100644 --- a/src/gui/elems/mainWindow/mainTransport.h +++ b/src/gui/elems/mainWindow/mainTransport.h @@ -29,17 +29,18 @@ #define GE_MAIN_TRANSPORT_H -#include +#include "gui/elems/basics/pack.h" class geButton; +class geBox; class geStatusButton; namespace giada { namespace v { -class geMainTransport : public Fl_Pack +class geMainTransport : public gePack { public: @@ -51,11 +52,11 @@ private: geButton* rewind; geStatusButton* play; - + geBox* spacer1; geButton* recTriggerMode; geStatusButton* recAction; geStatusButton* recInput; - + geBox* spacer2; geStatusButton* metronome; }; }} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearner.cpp b/src/gui/elems/midiIO/midiLearner.cpp new file mode 100644 index 0000000..a969606 --- /dev/null +++ b/src/gui/elems/midiIO/midiLearner.cpp @@ -0,0 +1,133 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#include "utils/string.h" +#include "gui/elems/basics/boxtypes.h" +#include "gui/elems/basics/box.h" +#include "gui/elems/basics/button.h" +#include "midiLearner.h" + + +namespace giada { +namespace v +{ +geMidiLearner::geMidiLearner(int X, int Y, int W, std::string l, int param) +: Fl_Group (X, Y, W, 20) +, onStartLearn(nullptr) +, onStopLearn (nullptr) +, onClearLearn(nullptr) +, m_param (param) +{ + begin(); + m_text = new geBox(x(), y(), 156, 20, l.c_str()); + m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20); + m_button = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn"); + end(); + + m_text->box(G_CUSTOM_BORDER_BOX); + m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + m_valueBtn->box(G_CUSTOM_BORDER_BOX); + m_valueBtn->callback(cb_value, (void*)this); + m_valueBtn->when(FL_WHEN_RELEASE); + + m_button->type(FL_TOGGLE_BUTTON); + m_button->callback(cb_button, (void*)this); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::cb_button(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->onLearn(); } +void geMidiLearner::cb_value(Fl_Widget* v, void* p) { ((geMidiLearner*)p)->onReset(); } + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::update(uint32_t value) +{ + std::string tmp = "(not set)"; + + if (value != 0x0) { + tmp = "0x" + u::string::iToString(value, /*hex=*/true); + tmp.pop_back(); // Remove last two digits, useless in MIDI messages + tmp.pop_back(); // Remove last two digits, useless in MIDI messages + } + + m_valueBtn->copy_label(tmp.c_str()); + m_button->value(0); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::activate() +{ + Fl_Group::activate(); + m_valueBtn->activate(); + m_button->activate(); +} + + +void geMidiLearner::deactivate() +{ + Fl_Group::deactivate(); + m_valueBtn->deactivate(); + m_button->deactivate(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::onLearn() const +{ + assert(onStartLearn != nullptr); + assert(onStopLearn != nullptr); + + if (m_button->value() == 1) + onStartLearn(m_param); + else + onStopLearn(); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearner::onReset() const +{ + assert(onClearLearn != nullptr); + + if (Fl::event_button() == FL_RIGHT_MOUSE) + onClearLearn(m_param); +} +}} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearner.h b/src/gui/elems/midiIO/midiLearner.h new file mode 100644 index 0000000..e03ceb2 --- /dev/null +++ b/src/gui/elems/midiIO/midiLearner.h @@ -0,0 +1,85 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_MIDI_LEARNER_H +#define GE_MIDI_LEARNER_H + + +#include +#include +#include + + +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 onStartLearn; + std::function onStopLearn; + std::function onClearLearn; + +protected: + + /* m_param + Parameter index to be learnt. */ + + int m_param; + + geBox* m_text; + geButton* m_valueBtn; + geButton* m_button; + +private: + + static void cb_button(Fl_Widget* v, void* p); + static void cb_value (Fl_Widget* v, void* p); + + void onLearn() const; + void onReset() const; + +}; +}} // giada::v:: + + +#endif diff --git a/src/gui/elems/midiIO/midiLearnerBase.cpp b/src/gui/elems/midiIO/midiLearnerBase.cpp deleted file mode 100644 index 1da5e87..0000000 --- a/src/gui/elems/midiIO/midiLearnerBase.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#include "utils/string.h" -#include "gui/elems/basics/boxtypes.h" -#include "gui/elems/basics/box.h" -#include "gui/elems/basics/button.h" -#include "midiLearnerBase.h" - - -namespace giada { -namespace v -{ -geMidiLearnerBase::geMidiLearnerBase(int X, int Y, int W, std::string l, int param, uint32_t value) -: Fl_Group (X, Y, W, 20), - m_param (param) -{ - begin(); - m_text = new geBox(x(), y(), 156, 20, l.c_str()); - m_valueBtn = new geButton(m_text->x()+m_text->w()+4, y(), 80, 20); - m_button = new geButton(m_valueBtn->x()+m_valueBtn->w()+4, y(), 40, 20, "learn"); - end(); - - m_text->box(G_CUSTOM_BORDER_BOX); - m_text->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); - - m_valueBtn->box(G_CUSTOM_BORDER_BOX); - m_valueBtn->callback(cb_value, (void*)this); - m_valueBtn->when(FL_WHEN_RELEASE); - - m_button->type(FL_TOGGLE_BUTTON); - m_button->callback(cb_button, (void*)this); - - update(value); -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerBase::cb_button(Fl_Widget* v, void* p) { ((geMidiLearnerBase*)p)->onLearn(); } -void geMidiLearnerBase::cb_value(Fl_Widget* v, void* p) { ((geMidiLearnerBase*)p)->onReset(); } - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerBase::update(uint32_t value) -{ - std::string tmp = "(not set)"; - - if (value != 0x0) { - tmp = "0x" + u::string::iToString(value, /*hex=*/true); - tmp.pop_back(); // Remove last two digits, useless in MIDI messages - tmp.pop_back(); // Remove last two digits, useless in MIDI messages - } - - m_valueBtn->copy_label(tmp.c_str()); - m_button->value(0); -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerBase::activate() -{ - Fl_Group::activate(); - m_valueBtn->activate(); - m_button->activate(); -} - - -void geMidiLearnerBase::deactivate() -{ - Fl_Group::deactivate(); - m_valueBtn->deactivate(); - m_button->deactivate(); -} -}} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearnerBase.h b/src/gui/elems/midiIO/midiLearnerBase.h deleted file mode 100644 index e0f3e73..0000000 --- a/src/gui/elems/midiIO/midiLearnerBase.h +++ /dev/null @@ -1,82 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef GE_MIDI_LEARNER_BASE_H -#define GE_MIDI_LEARNER_BASE_H - - -#include -#include - - -class geBox; -class geButton; - - -namespace giada { -namespace v -{ -class geMidiLearnerBase : public Fl_Group -{ -public: - - virtual ~geMidiLearnerBase() = default; - - virtual void refresh() = 0; - virtual void onLearn() = 0; - virtual void onReset() = 0; - - void activate(); - void deactivate(); - -protected: - - geMidiLearnerBase(int x, int y, int w, std::string l, int param, uint32_t value); - - /* update - Updates and repaints the label widget with value 'value'. */ - - void update(uint32_t value); - - /* m_param - Parameter index to be learnt. */ - - int m_param; - - geBox* m_text; - geButton* m_valueBtn; - geButton* m_button; - -private: - - static void cb_button(Fl_Widget* v, void* p); - static void cb_value (Fl_Widget* v, void* p); -}; -}} // giada::v:: - - -#endif diff --git a/src/gui/elems/midiIO/midiLearnerChannel.cpp b/src/gui/elems/midiIO/midiLearnerChannel.cpp deleted file mode 100644 index a7dd485..0000000 --- a/src/gui/elems/midiIO/midiLearnerChannel.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#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(c).midiInPitch); break; - case G_MIDI_IN_READ_ACTIONS : update(static_cast(c).midiInReadActions); break; - case G_MIDI_OUT_L_PLAYING : update(static_cast(c).midiOutLplaying); break; - case G_MIDI_OUT_L_MUTE : update(static_cast(c).midiOutLmute); break; - case G_MIDI_OUT_L_SOLO : update(static_cast(c).midiOutLsolo); break; - } - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerChannel::onLearn() -{ - if (m_button->value() == 1) - c::io::startChannelMidiLearn(m_param, m_channelId); - else - c::io::stopMidiLearn(); -} - - -void geMidiLearnerChannel::onReset() -{ - if (Fl::event_button() == FL_RIGHT_MOUSE) - c::io::clearChannelMidiLearn(m_param, m_channelId); -} -}} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearnerChannel.h b/src/gui/elems/midiIO/midiLearnerChannel.h deleted file mode 100644 index d22177a..0000000 --- a/src/gui/elems/midiIO/midiLearnerChannel.h +++ /dev/null @@ -1,55 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef GE_MIDI_LEARNER_CHANNEL_H -#define GE_MIDI_LEARNER_CHANNEL_H - - -#include "midiLearnerBase.h" - - -namespace giada { -namespace v -{ -class geMidiLearnerChannel : public geMidiLearnerBase -{ -public: - - geMidiLearnerChannel(int x, int y, int w, std::string l, int param, uint32_t value, ID channelId); - - void refresh() override; - void onLearn() override; - void onReset() override; - -private: - - ID m_channelId; -}; -}} // giada::v:: - - -#endif diff --git a/src/gui/elems/midiIO/midiLearnerMaster.cpp b/src/gui/elems/midiIO/midiLearnerMaster.cpp deleted file mode 100644 index cb4a86f..0000000 --- a/src/gui/elems/midiIO/midiLearnerMaster.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#include -#include "core/model/model.h" -#include "glue/io.h" -#include "gui/elems/basics/button.h" -#include "midiLearnerMaster.h" - - -namespace giada { -namespace v -{ -geMidiLearnerMaster::geMidiLearnerMaster(int X, int Y, int W, std::string l, int param, uint32_t value) -: geMidiLearnerBase(X, Y, W, l, param, value) -{ -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerMaster::refresh() -{ - m::model::onGet(m::model::midiIn, [&](const m::model::MidiIn& m) - { - switch (m_param) { - case G_MIDI_IN_REWIND : update(m.rewind); break; - case G_MIDI_IN_START_STOP : update(m.startStop); break; - case G_MIDI_IN_ACTION_REC : update(m.actionRec); break; - case G_MIDI_IN_INPUT_REC : update(m.inputRec); break; - case G_MIDI_IN_METRONOME : update(m.volumeIn); break; - case G_MIDI_IN_VOLUME_IN : update(m.volumeOut); break; - case G_MIDI_IN_VOLUME_OUT : update(m.beatDouble); break; - case G_MIDI_IN_BEAT_DOUBLE : update(m.beatHalf); break; - case G_MIDI_IN_BEAT_HALF : update(m.metronome); break; - } - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerMaster::onLearn() -{ - if (m_button->value() == 1) - c::io::startMasterMidiLearn(m_param); - else - c::io::stopMidiLearn(); -} - - -void geMidiLearnerMaster::onReset() -{ - if (Fl::event_button() == FL_RIGHT_MOUSE) - c::io::clearMasterMidiLearn(m_param); -} -}} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearnerMaster.h b/src/gui/elems/midiIO/midiLearnerMaster.h deleted file mode 100644 index 8bb2829..0000000 --- a/src/gui/elems/midiIO/midiLearnerMaster.h +++ /dev/null @@ -1,51 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifndef GE_MIDI_LEARNER_MASTER_H -#define GE_MIDI_LEARNER_MASTER_H - - -#include "midiLearnerBase.h" - - -namespace giada { -namespace v -{ -class geMidiLearnerMaster : public geMidiLearnerBase -{ -public: - - geMidiLearnerMaster(int x, int y, int w, std::string l, int param, uint32_t value); - - void refresh() override; - void onLearn() override; - void onReset() override; -}; -}} // giada::v:: - - -#endif diff --git a/src/gui/elems/midiIO/midiLearnerPack.cpp b/src/gui/elems/midiIO/midiLearnerPack.cpp new file mode 100644 index 0000000..05ebc80 --- /dev/null +++ b/src/gui/elems/midiIO/midiLearnerPack.cpp @@ -0,0 +1,92 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include +#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 s, std::function c) +{ + m_onStartLearn = s; + m_onClearLearn = c; +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearnerPack::addMidiLearner(std::string label, int param, bool visible) +{ + geMidiLearner* l = new geMidiLearner(0, 0, LEARNER_WIDTH, label, param); + + l->onStartLearn = m_onStartLearn; + l->onClearLearn = m_onClearLearn; + l->onStopLearn = [] () { c::io::stopMidiLearn(); }; + + add(l); + if (!visible) l->hide(); + learners.push_back(l); +} + + +/* -------------------------------------------------------------------------- */ + + +void geMidiLearnerPack::setEnabled(bool v) +{ + if (v) for (auto* l : learners) l->activate(); + else for (auto* l : learners) l->deactivate(); +} +}} // giada::v:: diff --git a/src/gui/elems/midiIO/midiLearnerPack.h b/src/gui/elems/midiIO/midiLearnerPack.h new file mode 100644 index 0000000..228a06c --- /dev/null +++ b/src/gui/elems/midiIO/midiLearnerPack.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine is free software: you can + * redistribute it and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Giada - Your Hardcore Loopmachine is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef GE_LEARNER_PACK_H +#define GE_LEARNER_PACK_H + + +#include +#include +#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, std::function); + void addMidiLearner(std::string label, int param, bool visible=true); + void setEnabled(bool v); + + std::vector learners; + +private: + + std::function m_onStartLearn; + std::function m_onClearLearn; +}; +}} // giada::v:: + + +#endif diff --git a/src/gui/elems/midiIO/midiLearnerPlugin.cpp b/src/gui/elems/midiIO/midiLearnerPlugin.cpp deleted file mode 100644 index a44b153..0000000 --- a/src/gui/elems/midiIO/midiLearnerPlugin.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#ifdef WITH_VST - - -#include -#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(m_param) < p.midiInParams.size()); - update(p.midiInParams[m_param]); - }); -} - - -/* -------------------------------------------------------------------------- */ - - -void geMidiLearnerPlugin::onLearn() -{ - if (m_button->value() == 1) - c::io::startPluginMidiLearn(m_param, m_pluginId); - else - c::io::stopMidiLearn(); -} - - -void geMidiLearnerPlugin::onReset() -{ - if (Fl::event_button() == FL_RIGHT_MOUSE) - c::io::clearPluginMidiLearn(m_param, m_pluginId); -} -}} // giada::v:: - - -#endif \ No newline at end of file diff --git a/src/gui/elems/midiIO/midiLearnerPlugin.h b/src/gui/elems/midiIO/midiLearnerPlugin.h deleted file mode 100644 index 482cbaf..0000000 --- a/src/gui/elems/midiIO/midiLearnerPlugin.h +++ /dev/null @@ -1,60 +0,0 @@ -/* ----------------------------------------------------------------------------- - * - * Giada - Your Hardcore Loopmachine - * - * ----------------------------------------------------------------------------- - * - * Copyright (C) 2010-2020 Giovanni A. Zuliani | Monocasual - * - * This file is part of Giada - Your Hardcore Loopmachine. - * - * Giada - Your Hardcore Loopmachine is free software: you can - * redistribute it and/or modify it under the terms of the GNU General - * Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * Giada - Your Hardcore Loopmachine is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Giada - Your Hardcore Loopmachine. If not, see - * . - * - * -------------------------------------------------------------------------- */ - - -#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 diff --git a/src/gui/elems/plugin/pluginElement.cpp b/src/gui/elems/plugin/pluginElement.cpp index 79489e3..0d861a8 100644 --- a/src/gui/elems/plugin/pluginElement.cpp +++ b/src/gui/elems/plugin/pluginElement.cpp @@ -30,8 +30,6 @@ #include #include -#include "core/channels/channel.h" -#include "core/model/model.h" #include "core/graphics.h" #include "core/pluginHost.h" #include "core/plugin.h" @@ -50,10 +48,9 @@ namespace giada { namespace v { -gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W) -: Fl_Pack (X, Y, W, G_GUI_UNIT), - m_channelId(channelId), - m_pluginId (pluginId) +gePluginElement::gePluginElement(int X, int Y, int W, c::plugin::Plugin data) +: Fl_Pack(X, Y, W, G_GUI_UNIT) +, m_plugin (data) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -66,38 +63,34 @@ gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W) remove = new geButton(0, 0, G_GUI_UNIT, G_GUI_UNIT, "", fxRemoveOff_xpm, fxRemoveOn_xpm); end(); - m::model::PluginsLock l(m::model::plugins); - - const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId); - remove->callback(cb_removePlugin, (void*)this); - if (!p.valid) { - button->copy_label(p.getUniqueId().c_str()); + if (!m_plugin.valid) { + button->copy_label(m_plugin.uniqueId.c_str()); button->deactivate(); bypass->deactivate(); shiftUp->deactivate(); shiftDown->deactivate(); return; } - button->copy_label(p.getName().c_str()); + button->copy_label(m_plugin.name.c_str()); button->callback(cb_openPluginWindow, (void*)this); program->callback(cb_setProgram, (void*)this); - for (int i=0; iadd(u::gui::removeFltkChars(p.getProgramName(i)).c_str()); + for (const auto& p : m_plugin.programs) + program->add(u::gui::removeFltkChars(p.name).c_str()); if (program->size() == 0) { program->add("-- no programs --\0"); program->deactivate(); } else - program->value(p.getCurrentProgram()); + program->value(m_plugin.currentProgram); bypass->callback(cb_setBypass, (void*)this); bypass->type(FL_TOGGLE_BUTTON); - bypass->value(p.isBypassed() ? 0 : 1); + bypass->value(m_plugin.isBypassed ? 0 : 1); shiftUp->callback(cb_shiftUp, (void*)this); shiftDown->callback(cb_shiftDown, (void*)this); @@ -109,7 +102,7 @@ gePluginElement::gePluginElement(ID pluginId, ID channelId, int X, int Y, int W) ID gePluginElement::getPluginId() const { - return m_pluginId; + return m_plugin.id; } @@ -131,7 +124,7 @@ void gePluginElement::cb_shiftUp() { const gdPluginList* parent = static_cast(window()); - c::plugin::swapPlugins(m_pluginId, parent->getPrevElement(*this).getPluginId(), m_channelId); + c::plugin::swapPlugins(m_plugin.id, parent->getPrevElement(*this).getPluginId(), m_plugin.channelId); } @@ -142,7 +135,7 @@ void gePluginElement::cb_shiftDown() { const gdPluginList* parent = static_cast(window()); - c::plugin::swapPlugins(m_pluginId, parent->getNextElement(*this).getPluginId(), m_channelId); + c::plugin::swapPlugins(m_plugin.id, parent->getNextElement(*this).getPluginId(), m_plugin.channelId); } @@ -155,8 +148,8 @@ void gePluginElement::cb_removePlugin() pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent window 'add plugin'.*/ - static_cast(window())->delSubWindow(m_pluginId + 1); - c::plugin::freePlugin(m_pluginId, m_channelId); + static_cast(window())->delSubWindow(m_plugin.id + 1); + c::plugin::freePlugin(m_plugin.id, m_plugin.channelId); } @@ -165,14 +158,10 @@ void gePluginElement::cb_removePlugin() void gePluginElement::cb_openPluginWindow() { - m::model::PluginsLock l(m::model::plugins); - - const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId); - /* The new pluginWindow has id = id_plugin + 1, because id=0 is reserved for the parent window 'add plugin'. */ - int pwid = m_pluginId + 1; + int pwid = m_plugin.id + 1; gdWindow* parent = static_cast(window()); gdWindow* child = parent->getChild(pwid); @@ -181,10 +170,10 @@ void gePluginElement::cb_openPluginWindow() child->show(); // Raise it to top } else { - if (p.hasEditor()) - child = new gdPluginWindowGUI(m_pluginId); + if (m_plugin.hasEditor) + child = new gdPluginWindowGUI(m_plugin); else - child = new gdPluginWindow(m_pluginId); + child = new gdPluginWindow(m_plugin); child->setId(pwid); parent->addSubWindow(child); } @@ -196,7 +185,7 @@ void gePluginElement::cb_openPluginWindow() void gePluginElement::cb_setBypass() { - c::plugin::toggleBypass(m_pluginId); + c::plugin::toggleBypass(m_plugin.id); } @@ -205,7 +194,7 @@ void gePluginElement::cb_setBypass() void gePluginElement::cb_setProgram() { - c::plugin::setProgram(m_pluginId, program->value()); + c::plugin::setProgram(m_plugin.id, program->value()); } }} // giada::v:: diff --git a/src/gui/elems/plugin/pluginElement.h b/src/gui/elems/plugin/pluginElement.h index b6ca89a..c9acb07 100644 --- a/src/gui/elems/plugin/pluginElement.h +++ b/src/gui/elems/plugin/pluginElement.h @@ -33,6 +33,7 @@ #include +#include "glue/plugin.h" class geChoice; @@ -47,7 +48,7 @@ class gePluginElement : public Fl_Pack { public: - gePluginElement(ID pluginId, ID channelId, int x, int y, int w); + gePluginElement(int x, int y, int w, c::plugin::Plugin); ID getPluginId() const; @@ -73,8 +74,7 @@ private: void cb_shiftDown(); void cb_setProgram(); - ID m_channelId; - ID m_pluginId; + c::plugin::Plugin m_plugin; }; }} // giada::v:: diff --git a/src/gui/elems/plugin/pluginParameter.cpp b/src/gui/elems/plugin/pluginParameter.cpp index c401a59..2eec8d8 100644 --- a/src/gui/elems/plugin/pluginParameter.cpp +++ b/src/gui/elems/plugin/pluginParameter.cpp @@ -28,10 +28,9 @@ #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" @@ -41,42 +40,38 @@ namespace giada { namespace v { -gePluginParameter::gePluginParameter(int paramIndex, ID pluginId, - int X, int Y, int W, int labelWidth) -: Fl_Group (X, Y, W, G_GUI_UNIT), - m_pluginId (pluginId), - m_paramIndex(paramIndex) +gePluginParameter::gePluginParameter(int X, int Y, int W, int labelWidth, const c::plugin::Param p) +: Fl_Group (X, Y, W, G_GUI_UNIT) +, m_param (p) { - m::model::PluginsLock l(m::model::plugins); - const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId); - begin(); const int VALUE_WIDTH = 100; m_label = new geBox(x(), y(), labelWidth, G_GUI_UNIT); - m_label->copy_label(p.getParameterName(m_paramIndex).c_str()); + m_label->copy_label(m_param.name.c_str()); m_label->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); m_slider = new geSlider(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN, y(), w()-(m_label->x()+m_label->w()+G_GUI_OUTER_MARGIN)-VALUE_WIDTH, G_GUI_UNIT); - m_slider->value(p.getParameter(m_paramIndex)); + m_slider->value(m_param.value); m_slider->callback(cb_setValue, (void*)this); m_value = new geBox(m_slider->x()+m_slider->w()+G_GUI_OUTER_MARGIN, y(), VALUE_WIDTH, G_GUI_UNIT); m_value->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); m_value->box(G_CUSTOM_BORDER_BOX); - + end(); + resizable(m_slider); - update(false); + update(m_param, false); } /* -------------------------------------------------------------------------- */ -void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); } +void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParameter*)p)->cb_setValue(); } /* -------------------------------------------------------------------------- */ @@ -84,7 +79,7 @@ void gePluginParameter::cb_setValue(Fl_Widget* v, void* p) { ((gePluginParamete void gePluginParameter::cb_setValue() { - c::plugin::setParameter(m_pluginId, m_paramIndex, m_slider->value(), + c::events::setPluginParameter(m_param.pluginId, m_param.index, m_slider->value(), /*gui=*/true); } @@ -92,18 +87,11 @@ void gePluginParameter::cb_setValue() /* -------------------------------------------------------------------------- */ -void gePluginParameter::update(bool changeSlider) +void gePluginParameter::update(const c::plugin::Param& p, bool changeSlider) { - m::model::PluginsLock l(m::model::plugins); - const m::Plugin& p = m::model::get(m::model::plugins, m_pluginId); - - std::string v = p.getParameterText(m_paramIndex) + " " + - p.getParameterLabel(m_paramIndex); - - m_value->copy_label(v.c_str()); - + m_value->copy_label(std::string(p.text + " " + p.label).c_str()); if (changeSlider) - m_slider->value(p.getParameter(m_paramIndex)); + m_slider->value(p.value); } }} // giada::v:: diff --git a/src/gui/elems/plugin/pluginParameter.h b/src/gui/elems/plugin/pluginParameter.h index e3b2aff..6c4d9d2 100644 --- a/src/gui/elems/plugin/pluginParameter.h +++ b/src/gui/elems/plugin/pluginParameter.h @@ -41,24 +41,27 @@ class geSlider; namespace giada { +namespace c { +namespace plugin +{ +struct Param; +}} namespace v { class gePluginParameter : public Fl_Group { public: - gePluginParameter(int paramIndex, ID pluginId, int x, int y, int w, - int labelWidth); + gePluginParameter(int x, int y, int w, int labelWidth, const c::plugin::Param); - void update(bool changeSlider); + void update(const c::plugin::Param& p, bool changeSlider); private: static void cb_setValue(Fl_Widget* v, void* p); void cb_setValue(); - ID m_pluginId; - int m_paramIndex; + const c::plugin::Param m_param; geBox* m_label; geSlider* m_slider; diff --git a/src/gui/elems/sampleEditor/boostTool.cpp b/src/gui/elems/sampleEditor/boostTool.cpp index 9cedcb5..bb2fb2b 100644 --- a/src/gui/elems/sampleEditor/boostTool.cpp +++ b/src/gui/elems/sampleEditor/boostTool.cpp @@ -26,7 +26,6 @@ #include -#include "core/channels/sampleChannel.h" #include "core/const.h" #include "core/waveFx.h" #include "glue/channel.h" diff --git a/src/gui/elems/sampleEditor/panTool.cpp b/src/gui/elems/sampleEditor/panTool.cpp index 6fb6934..ca646d6 100644 --- a/src/gui/elems/sampleEditor/panTool.cpp +++ b/src/gui/elems/sampleEditor/panTool.cpp @@ -26,11 +26,10 @@ #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/const.h" #include "core/waveFx.h" -#include "glue/channel.h" +#include "glue/events.h" #include "utils/gui.h" #include "utils/math.h" #include "utils/string.h" @@ -46,9 +45,9 @@ namespace giada { namespace v { -gePanTool::gePanTool(ID channelId, int x, int y) -: Fl_Pack (x, y, 200, G_GUI_UNIT), - m_channelId(channelId) +gePanTool::gePanTool(const c::sampleEditor::Data& d, int x, int y) +: Fl_Pack(x, y, 200, G_GUI_UNIT) +, m_data (nullptr) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -60,7 +59,7 @@ gePanTool::gePanTool(ID channelId, int x, int y) reset = new geButton(0, 0, 70, G_GUI_UNIT, "Reset"); end(); - dial->range(0.0f, 1.0f); + dial->range(0.0f, G_MAX_PAN); dial->callback(cb_panning, (void*)this); input->align(FL_ALIGN_RIGHT); @@ -68,31 +67,38 @@ gePanTool::gePanTool(ID channelId, int x, int y) input->cursor_color(FL_WHITE); reset->callback(cb_panReset, (void*)this); + + rebuild(d); } /* -------------------------------------------------------------------------- */ -void gePanTool::rebuild() +void gePanTool::rebuild(const c::sampleEditor::Data& d) { - float p; - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - p = static_cast(c).getPan(); - }); + m_data = &d; + update(m_data->pan); + +} - dial->value(p); - if (p < 0.5f) { - std::string tmp = u::string::iToString((int) ((-p * 200.0f) + 100.0f)) + " L"; +/* -------------------------------------------------------------------------- */ + + +void gePanTool::update(float v) +{ + dial->value(v); + + if (v < 0.5f) { + std::string tmp = u::string::iToString((int) ((-v * 200.0f) + 100.0f)) + " L"; input->value(tmp.c_str()); } else - if (p == 0.5) + if (v == 0.5) input->value("C"); else { - std::string tmp = u::string::iToString((int) ((p * 200.0f) - 100.0f)) + " R"; + std::string tmp = u::string::iToString((int) ((v * 200.0f) - 100.0f)) + " R"; input->value(tmp.c_str()); } } @@ -111,7 +117,7 @@ void gePanTool::cb_panReset(Fl_Widget* w, void* p) { ((gePanTool*)p)->cb_panRese void gePanTool::cb_panning() { - c::channel::setPan(m_channelId, dial->value()); + c::events::sendChannelPan(m_data->channelId, dial->value()); } @@ -120,7 +126,6 @@ void gePanTool::cb_panning() void gePanTool::cb_panReset() { - c::channel::setPan(m_channelId, 0.5f); + c::events::sendChannelPan(m_data->channelId, 0.5f); } - }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/panTool.h b/src/gui/elems/sampleEditor/panTool.h index 1adeca1..a8a39f5 100644 --- a/src/gui/elems/sampleEditor/panTool.h +++ b/src/gui/elems/sampleEditor/panTool.h @@ -45,9 +45,10 @@ class gePanTool : public Fl_Pack { public: - gePanTool(ID channelId, int x, int y); + gePanTool(const c::sampleEditor::Data& d, int x, int y); - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); + void update(float v); private: @@ -56,7 +57,7 @@ private: void cb_panning(); void cb_panReset(); - ID m_channelId; + const c::sampleEditor::Data* m_data; geBox* label; geDial* dial; diff --git a/src/gui/elems/sampleEditor/pitchTool.cpp b/src/gui/elems/sampleEditor/pitchTool.cpp index fb7e5cf..30052eb 100644 --- a/src/gui/elems/sampleEditor/pitchTool.cpp +++ b/src/gui/elems/sampleEditor/pitchTool.cpp @@ -27,12 +27,11 @@ #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/const.h" #include "core/graphics.h" #include "core/clock.h" -#include "glue/channel.h" +#include "glue/events.h" #include "utils/gui.h" #include "utils/string.h" #include "gui/dialogs/sampleEditor.h" @@ -46,9 +45,9 @@ namespace giada { namespace v { -gePitchTool::gePitchTool(ID channelId, int x, int y) -: Fl_Pack (x, y, 600, G_GUI_UNIT), - m_channelId(channelId) +gePitchTool::gePitchTool(const c::sampleEditor::Data& d, int x, int y) +: Fl_Pack(x, y, 600, G_GUI_UNIT) +, m_data (nullptr) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -77,21 +76,29 @@ gePitchTool::gePitchTool(ID channelId, int x, int y) pitchHalf->callback(cb_setPitchHalf, (void*)this); pitchDouble->callback(cb_setPitchDouble, (void*)this); pitchReset->callback(cb_resetPitch, (void*)this); + + rebuild(d); } /* -------------------------------------------------------------------------- */ -void gePitchTool::rebuild() +void gePitchTool::rebuild(const c::sampleEditor::Data& d) { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - float p = static_cast(c).getPitch(); - - dial->value(p); - input->value(u::string::fToString(p, 4).c_str()); // 4 digits - }); + m_data = &d; + update(m_data->pitch, /*isDial=*/false); +} + + +/* -------------------------------------------------------------------------- */ + + +void gePitchTool::update(float v, bool isDial) +{ + input->value(u::string::fToString(v, 4).c_str()); // 4 digits + if (!isDial) + dial->value(v); } @@ -112,7 +119,7 @@ void gePitchTool::cb_setPitchNum (Fl_Widget* w, void* p) { ((gePitchTool*)p)-> void gePitchTool::cb_setPitch() { - c::channel::setPitch(m_channelId, dial->value()); + c::events::setChannelPitch(m_data->channelId, dial->value(), Thread::MAIN); } @@ -121,7 +128,7 @@ void gePitchTool::cb_setPitch() void gePitchTool::cb_setPitchNum() { - c::channel::setPitch(m_channelId, atof(input->value())); + c::events::setChannelPitch(m_data->channelId, atof(input->value()), Thread::MAIN); } @@ -130,7 +137,7 @@ void gePitchTool::cb_setPitchNum() void gePitchTool::cb_setPitchHalf() { - c::channel::setPitch(m_channelId, dial->value()/2); + c::events::setChannelPitch(m_data->channelId, dial->value() / 2, Thread::MAIN); } @@ -139,7 +146,7 @@ void gePitchTool::cb_setPitchHalf() void gePitchTool::cb_setPitchDouble() { - c::channel::setPitch(m_channelId, dial->value()*2); + c::events::setChannelPitch(m_data->channelId, dial->value() * 2, Thread::MAIN); } @@ -148,13 +155,8 @@ void gePitchTool::cb_setPitchDouble() void gePitchTool::cb_setPitchToBar() { - Frame end; - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - end = static_cast(c).getEnd(); - }); - - c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInBar()); + c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInBar(), + Thread::MAIN); } @@ -163,13 +165,8 @@ void gePitchTool::cb_setPitchToBar() void gePitchTool::cb_setPitchToSong() { - Frame end; - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - end = static_cast(c).getEnd(); - }); - - c::channel::setPitch(m_channelId, end / (float) m::clock::getFramesInLoop()); + c::events::setChannelPitch(m_data->channelId, m_data->end / (float) m::clock::getFramesInLoop(), + Thread::MAIN); } @@ -178,7 +175,6 @@ void gePitchTool::cb_setPitchToSong() void gePitchTool::cb_resetPitch() { - c::channel::setPitch(m_channelId, G_DEFAULT_PITCH); + c::events::setChannelPitch(m_data->channelId, G_DEFAULT_PITCH, Thread::MAIN); } - }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/pitchTool.h b/src/gui/elems/sampleEditor/pitchTool.h index 10019c3..f520d2f 100644 --- a/src/gui/elems/sampleEditor/pitchTool.h +++ b/src/gui/elems/sampleEditor/pitchTool.h @@ -45,9 +45,10 @@ class gePitchTool : public Fl_Pack { public: - gePitchTool(ID channelId, int x, int y); + gePitchTool(const c::sampleEditor::Data& d, int x, int y); - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); + void update(float v, bool isDial=false); private: @@ -66,7 +67,7 @@ private: void cb_resetPitch(); void cb_setPitchNum(); - ID m_channelId; + const c::sampleEditor::Data* m_data; geBox* label; geDial* dial; diff --git a/src/gui/elems/sampleEditor/rangeTool.cpp b/src/gui/elems/sampleEditor/rangeTool.cpp index 6dbd341..9c800c4 100644 --- a/src/gui/elems/sampleEditor/rangeTool.cpp +++ b/src/gui/elems/sampleEditor/rangeTool.cpp @@ -27,7 +27,6 @@ #include #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/wave.h" #include "glue/channel.h" @@ -45,10 +44,9 @@ namespace giada { namespace v { -geRangeTool::geRangeTool(ID channelId, ID waveId, int x, int y) -: Fl_Pack (x, y, 280, G_GUI_UNIT), - m_channelId(channelId), - m_waveId (waveId) +geRangeTool::geRangeTool(const c::sampleEditor::Data& d, int x, int y) +: Fl_Pack(x, y, 280, G_GUI_UNIT) +, m_data (nullptr) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -69,25 +67,33 @@ geRangeTool::geRangeTool(ID channelId, ID waveId, int x, int y) m_end->callback(cb_setChanPos, this); m_reset->callback(cb_resetStartEnd, this); + + rebuild(d); } /* -------------------------------------------------------------------------- */ -void geRangeTool::rebuild() +void geRangeTool::rebuild(const c::sampleEditor::Data& d) { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - m_begin->value(std::to_string(static_cast(c).getBegin()).c_str()); - m_end->value(std::to_string(static_cast(c).getEnd()).c_str()); - }); + m_data = &d; + update(m_data->begin, m_data->end); } /* -------------------------------------------------------------------------- */ +void geRangeTool::update(Frame begin, Frame end) +{ + m_begin->value(std::to_string(begin).c_str()); + m_end->value(std::to_string(end).c_str()); +} + +/* -------------------------------------------------------------------------- */ + + void geRangeTool::cb_setChanPos (Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_setChanPos(); } void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->cb_resetStartEnd(); } @@ -97,7 +103,7 @@ void geRangeTool::cb_resetStartEnd(Fl_Widget* w, void* p) { ((geRangeTool*)p)->c void geRangeTool::cb_setChanPos() { - c::sampleEditor::setBeginEnd(m_channelId, atoi(m_begin->value()), atoi(m_end->value())); + c::sampleEditor::setBeginEnd(m_data->channelId, atoi(m_begin->value()), atoi(m_end->value())); } @@ -106,13 +112,7 @@ void geRangeTool::cb_setChanPos() void geRangeTool::cb_resetStartEnd() { - Frame waveSize; - m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) - { - waveSize = w.getSize(); - }); - - c::sampleEditor::setBeginEnd(m_channelId, 0, waveSize - 1); + c::sampleEditor::setBeginEnd(m_data->channelId, 0, m_data->waveSize - 1); } }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/rangeTool.h b/src/gui/elems/sampleEditor/rangeTool.h index 02c2b2f..6ec75fd 100644 --- a/src/gui/elems/sampleEditor/rangeTool.h +++ b/src/gui/elems/sampleEditor/rangeTool.h @@ -44,9 +44,10 @@ class geRangeTool : public Fl_Pack { public: - geRangeTool(ID channelId, ID waveId, int x, int y); + geRangeTool(const c::sampleEditor::Data& d, int x, int y); - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); + void update(Frame begin, Frame end); private: @@ -55,8 +56,7 @@ private: void cb_setChanPos(); void cb_resetStartEnd(); - ID m_channelId; - ID m_waveId; + const c::sampleEditor::Data* m_data; geBox* m_label; geInput* m_begin; diff --git a/src/gui/elems/sampleEditor/shiftTool.cpp b/src/gui/elems/sampleEditor/shiftTool.cpp index 6b7cffb..2634395 100644 --- a/src/gui/elems/sampleEditor/shiftTool.cpp +++ b/src/gui/elems/sampleEditor/shiftTool.cpp @@ -27,7 +27,6 @@ #include #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/const.h" #include "utils/gui.h" @@ -44,10 +43,9 @@ namespace giada { namespace v { -geShiftTool::geShiftTool(ID channelId, ID waveId, int x, int y) -: Fl_Pack (x, y, 300, G_GUI_UNIT), - m_channelId(channelId), - m_waveId (waveId) +geShiftTool::geShiftTool(const c::sampleEditor::Data& d, int x, int y) +: Fl_Pack(x, y, 300, G_GUI_UNIT) +, m_data (nullptr) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -63,6 +61,8 @@ geShiftTool::geShiftTool(ID channelId, ID waveId, int x, int y) m_shift->callback(cb_setShift, (void*)this); m_reset->callback(cb_reset, (void*)this); + + rebuild(d); } @@ -94,12 +94,19 @@ void geShiftTool::cb_reset() /* -------------------------------------------------------------------------- */ -void geShiftTool::rebuild() +void geShiftTool::rebuild(const c::sampleEditor::Data& d) +{ + m_data = &d; + update(m_data->shift); +} + + +/* -------------------------------------------------------------------------- */ + + +void geShiftTool::update(Frame shift) { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - m_shift->value(std::to_string(static_cast(c).shift).c_str()); - }); + m_shift->value(std::to_string(shift).c_str()); } @@ -108,6 +115,6 @@ void geShiftTool::rebuild() void geShiftTool::shift(int f) { - c::sampleEditor::shift(m_channelId, m_waveId, f); + c::sampleEditor::shift(m_data->channelId, m_data->waveId, f); } }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/shiftTool.h b/src/gui/elems/sampleEditor/shiftTool.h index aa26d06..2113517 100644 --- a/src/gui/elems/sampleEditor/shiftTool.h +++ b/src/gui/elems/sampleEditor/shiftTool.h @@ -44,9 +44,10 @@ class geShiftTool : public Fl_Pack { public: - geShiftTool(ID channelId, ID waveId, int x, int y); + geShiftTool(const c::sampleEditor::Data& d, int x, int y); - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); + void update(Frame shift); private: @@ -57,8 +58,7 @@ private: void shift(int f); - ID m_channelId; - ID m_waveId; + const c::sampleEditor::Data* m_data; geBox* m_label; geInput* m_shift; diff --git a/src/gui/elems/sampleEditor/volumeTool.cpp b/src/gui/elems/sampleEditor/volumeTool.cpp index 81ac99e..4819815 100644 --- a/src/gui/elems/sampleEditor/volumeTool.cpp +++ b/src/gui/elems/sampleEditor/volumeTool.cpp @@ -28,10 +28,8 @@ #include #include #include -#include "core/channels/sampleChannel.h" #include "core/const.h" -#include "core/model/model.h" -#include "glue/channel.h" +#include "glue/events.h" #include "utils/gui.h" #include "utils/math.h" #include "utils/string.h" @@ -46,9 +44,9 @@ namespace giada { namespace v { -geVolumeTool::geVolumeTool(ID channelId, int X, int Y) -: Fl_Pack (X, Y, 150, G_GUI_UNIT), - m_channelId(channelId) +geVolumeTool::geVolumeTool(const c::sampleEditor::Data& d, int X, int Y) +: Fl_Pack(X, Y, 150, G_GUI_UNIT) +, m_data (nullptr) { type(Fl_Pack::HORIZONTAL); spacing(G_GUI_INNER_MARGIN); @@ -63,23 +61,33 @@ geVolumeTool::geVolumeTool(ID channelId, int X, int Y) dial->callback(cb_setVolume, (void*)this); input->callback(cb_setVolumeNum, (void*)this); + + rebuild(d); } /* -------------------------------------------------------------------------- */ -void geVolumeTool::rebuild() +void geVolumeTool::rebuild(const c::sampleEditor::Data& d) { - float volume; - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) { volume = c.volume; }); + m_data = &d; + update(m_data->volume, /*isDial=*/false); +} + + +/* -------------------------------------------------------------------------- */ + +void geVolumeTool::update(float v, bool isDial) +{ std::string tmp = "-inf"; - float dB = u::math::linearToDB(volume); + float dB = u::math::linearToDB(v); if (dB > -INFINITY) tmp = u::string::fToString(dB, 2); // 2 digits input->value(tmp.c_str()); - dial->value(volume); + if (!isDial) + dial->value(v); } @@ -95,7 +103,7 @@ void geVolumeTool::cb_setVolumeNum(Fl_Widget* w, void* p) { ((geVolumeTool*)p)-> void geVolumeTool::cb_setVolume() { - c::channel::setVolume(m_channelId, dial->value(), false, true); + c::events::setChannelVolume(m_data->channelId, dial->value(), Thread::MAIN); } @@ -104,8 +112,7 @@ void geVolumeTool::cb_setVolume() void geVolumeTool::cb_setVolumeNum() { - float value = pow(10, (atof(input->value()) / 20)); // linear = 10^(dB/20) - c::channel::setVolume(m_channelId, value, false, true); + c::events::setChannelVolume(m_data->channelId, u::math::dBtoLinear(atof(input->value())), + Thread::MAIN); } - }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/volumeTool.h b/src/gui/elems/sampleEditor/volumeTool.h index b719607..15276a2 100644 --- a/src/gui/elems/sampleEditor/volumeTool.h +++ b/src/gui/elems/sampleEditor/volumeTool.h @@ -44,22 +44,24 @@ class geVolumeTool : public Fl_Pack { public: - geVolumeTool(ID channelId, int x, int y); + geVolumeTool(const c::sampleEditor::Data& d, int x, int y); - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); + void update(float v, bool isDial=false); private: - ID m_channelId; + static void cb_setVolume(Fl_Widget* w, void* p); + static void cb_setVolumeNum(Fl_Widget* w, void* p); + void cb_setVolume(); + void cb_setVolumeNum(); + + const c::sampleEditor::Data* m_data; geBox* label; geDial* dial; geInput* input; - static void cb_setVolume (Fl_Widget* w, void* p); - static void cb_setVolumeNum(Fl_Widget* w, void* p); - void cb_setVolume (); - void cb_setVolumeNum(); }; }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/waveTools.cpp b/src/gui/elems/sampleEditor/waveTools.cpp index ceaf1e6..4e28e0f 100644 --- a/src/gui/elems/sampleEditor/waveTools.cpp +++ b/src/gui/elems/sampleEditor/waveTools.cpp @@ -28,7 +28,6 @@ #include #include #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/waveFx.h" #include "core/const.h" @@ -68,9 +67,9 @@ void menuCallback_(Fl_Widget* w, void* v) { const geWaveTools* wt = static_cast(w); - ID channelId = wt->channelId; - size_t waveId = wt->waveId; - Menu selectedItem = (Menu) (intptr_t) v; + ID channelId = wt->getChannelData().channelId; + ID waveId = wt->getChannelData().waveId; + Menu selectedItem = (Menu) (intptr_t) v; int a = wt->waveform->getSelectionA(); int b = wt->waveform->getSelectionB(); @@ -89,28 +88,28 @@ void menuCallback_(Fl_Widget* w, void* v) c::sampleEditor::trim(channelId, waveId, a, b); break; case Menu::SILENCE: - c::sampleEditor::silence(waveId, a, b); + c::sampleEditor::silence(channelId, waveId, a, b); break; case Menu::REVERSE: - c::sampleEditor::reverse(waveId, a, b); + c::sampleEditor::reverse(channelId, waveId, a, b); break; case Menu::NORMALIZE: - c::sampleEditor::normalizeHard(waveId, a, b); + c::sampleEditor::normalize(channelId, waveId, a, b); break; case Menu::FADE_IN: - c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_IN); + c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::IN); break; case Menu::FADE_OUT: - c::sampleEditor::fade(waveId, a, b, m::wfx::FADE_OUT); + c::sampleEditor::fade(channelId, waveId, a, b, m::wfx::Fade::OUT); break; case Menu::SMOOTH_EDGES: - c::sampleEditor::smoothEdges(waveId, a, b); + c::sampleEditor::smoothEdges(channelId, waveId, a, b); break; case Menu::SET_BEGIN_END: - c::sampleEditor::setBeginEnd(waveId, a, b); + c::sampleEditor::setBeginEnd(channelId, a, b); break; case Menu::TO_NEW_CHANNEL: - c::sampleEditor::toNewChannel(waveId, a, b); + c::sampleEditor::toNewChannel(channelId, waveId, a, b); break; } } @@ -122,10 +121,9 @@ void menuCallback_(Fl_Widget* w, void* v) /* -------------------------------------------------------------------------- */ -geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h) -: Fl_Scroll(x, y, w, h, nullptr), - channelId(channelId), - waveId(waveId) +geWaveTools::geWaveTools(int x, int y, int w, int h) +: Fl_Scroll(x, y, w, h, nullptr) +, m_data (nullptr) { type(Fl_Scroll::HORIZONTAL_ALWAYS); hscrollbar.color(G_COLOR_GREY_2); @@ -133,7 +131,7 @@ geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h) hscrollbar.labelcolor(G_COLOR_LIGHT_1); hscrollbar.slider(G_CUSTOM_BORDER_BOX); - waveform = new v::geWaveform(channelId, waveId, x, y, w, h-24); + waveform = new v::geWaveform(x, y, w, h-24); } @@ -141,9 +139,10 @@ geWaveTools::geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h) /* -------------------------------------------------------------------------- */ -void geWaveTools::rebuild() +void geWaveTools::rebuild(const c::sampleEditor::Data& d) { - waveform->rebuild(); + m_data = &d; + waveform->rebuild(d); } @@ -152,11 +151,8 @@ void geWaveTools::rebuild() void geWaveTools::refresh() { - m::model::onGet(m::model::channels, channelId, [&](m::Channel& c) - { - if (c.isPreview()) - waveform->redraw(); - }); + if (m_data->a_getPreviewStatus() == ChannelStatus::PLAY) + waveform->redraw(); } @@ -169,7 +165,7 @@ void geWaveTools::resize(int x, int y, int w, int h) if (this->w() == w || (this->w() != w && this->h() != h)) { // vertical or both resize waveform->resize(x, y, waveform->w(), h-24); - waveform->rebuild(); + waveform->rebuild(*m_data); } if (this->w() > waveform->w()) diff --git a/src/gui/elems/sampleEditor/waveTools.h b/src/gui/elems/sampleEditor/waveTools.h index ba8eb0a..d920137 100644 --- a/src/gui/elems/sampleEditor/waveTools.h +++ b/src/gui/elems/sampleEditor/waveTools.h @@ -40,7 +40,7 @@ class geWaveTools : public Fl_Scroll { public: - geWaveTools(ID channelId, ID waveId, int x, int y, int w, int h); + geWaveTools(int x, int y, int w, int h); void resize(int x, int y, int w, int h) override; int handle(int e) override; @@ -49,7 +49,7 @@ public: Updates the waveform by realloc-ing new data (i.e. when the waveform has changed). */ - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); /* refresh Redraws the waveform, called by the video thread. This is meant to be called @@ -57,15 +57,16 @@ public: method is smart enough to skip painting if the channel is stopped. */ void refresh(); + + const c::sampleEditor::Data& getChannelData() const { return *m_data; } v::geWaveform* waveform; - ID channelId; - ID waveId; - private: void openMenu(); + + const c::sampleEditor::Data* m_data; }; }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/waveform.cpp b/src/gui/elems/sampleEditor/waveform.cpp index 1ad02a8..aec4f9a 100644 --- a/src/gui/elems/sampleEditor/waveform.cpp +++ b/src/gui/elems/sampleEditor/waveform.cpp @@ -29,7 +29,6 @@ #include #include #include -#include "core/channels/sampleChannel.h" #include "core/model/model.h" #include "core/wave.h" #include "core/conf.h" @@ -48,22 +47,21 @@ namespace giada { namespace v { -geWaveform::geWaveform(ID channelId, ID waveId, int x, int y, int w, int h) -: Fl_Widget (x, y, w, h, nullptr), - m_selection {}, - m_channelId (channelId), - m_waveId (waveId), - m_chanStart (0), - m_chanStartLit(false), - m_chanEnd (0), - m_chanEndLit (false), - m_pushed (false), - m_dragged (false), - m_resizedA (false), - m_resizedB (false), - m_ratio (0.0f) +geWaveform::geWaveform(int x, int y, int w, int h) +: Fl_Widget (x, y, w, h, nullptr) +, m_selection {} +, m_data (nullptr) +, m_chanStart (0) +, m_chanStartLit(false) +, m_chanEnd (0) +, m_chanEndLit (false) +, m_pushed (false) +, m_dragged (false) +, m_resizedA (false) +, m_resizedB (false) +, m_ratio (0.0f) { - m_data.size = w; + m_waveform.size = w; m_grid.snap = m::conf::conf.sampleEditorGridOn; m_grid.level = m::conf::conf.sampleEditorGridVal; @@ -75,9 +73,9 @@ geWaveform::geWaveform(ID channelId, ID waveId, int x, int y, int w, int h) void geWaveform::clearData() { - m_data.sup.clear(); - m_data.inf.clear(); - m_data.size = 0; + m_waveform.sup.clear(); + m_waveform.inf.clear(); + m_waveform.size = 0; m_grid.points.clear(); } @@ -87,9 +85,10 @@ void geWaveform::clearData() int geWaveform::alloc(int datasize, bool force) { - m::model::WavesLock lock(m::model::waves); + /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */ - const m::Wave& wave = m::model::get(m::model::waves, m_waveId); + m::model::WavesLock l(m::model::waves); + const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId); m_ratio = wave.getSize() / (float) datasize; @@ -100,16 +99,16 @@ int geWaveform::alloc(int datasize, bool force) m_ratio = 1; } - if (datasize == m_data.size && !force) + if (datasize == m_waveform.size && !force) return 0; clearData(); - m_data.size = datasize; - m_data.sup.resize(m_data.size); - m_data.inf.resize(m_data.size); + m_waveform.size = datasize; + m_waveform.sup.resize(m_waveform.size); + m_waveform.inf.resize(m_waveform.size); - u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_data.size, m_ratio); + u::log::print("[geWaveform::alloc] %d pixels, %f m_ratio\n", m_waveform.size, m_ratio); int offset = h() / 2; int zero = y() + offset; // center, zero amplitude (-inf dB) @@ -122,7 +121,7 @@ int geWaveform::alloc(int datasize, bool force) /* Resampling the waveform, hardcore way. Many thanks to http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html */ - for (int i = 0; i < m_data.size; i++) { + for (int i = 0; i < m_waveform.size; i++) { /* Scan the original waveform in chunks [pc, pn]. */ @@ -156,13 +155,13 @@ int geWaveform::alloc(int datasize, bool force) m_grid.points.push_back(k); } - m_data.sup[i] = zero - (peaksup * offset); - m_data.inf[i] = zero - (peakinf * offset); + m_waveform.sup[i] = zero - (peaksup * offset); + m_waveform.inf[i] = zero - (peakinf * offset); // avoid window overflow - if (m_data.sup[i] < y()) m_data.sup[i] = y(); - if (m_data.inf[i] > y()+h()-1) m_data.inf[i] = y()+h()-1; + if (m_waveform.sup[i] < y()) m_waveform.sup[i] = y(); + if (m_waveform.inf[i] > y()+h()-1) m_waveform.inf[i] = y()+h()-1; } recalcPoints(); @@ -175,11 +174,8 @@ int geWaveform::alloc(int datasize, bool force) void geWaveform::recalcPoints() { - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - m_chanStart = static_cast(c).getBegin(); - m_chanEnd = static_cast(c).getEnd(); - }); + m_chanStart = m_data->begin; + m_chanEnd = m_data->end; } @@ -215,10 +211,10 @@ void geWaveform::drawWaveform(int from, int to) fl_color(G_COLOR_BLACK); for (int i=from; i= m_data.size) + if (i >= m_waveform.size) break; - fl_line(i+x(), zero, i+x(), m_data.sup[i]); - fl_line(i+x(), zero, i+x(), m_data.inf[i]); + fl_line(i+x(), zero, i+x(), m_waveform.sup[i]); + fl_line(i+x(), zero, i+x(), m_waveform.inf[i]); } } @@ -286,13 +282,7 @@ void geWaveform::drawStartEndPoints() void geWaveform::drawPlayHead() { - float tp; - m::model::onGet(m::model::channels, m_channelId, [&](m::Channel& c) - { - tp = static_cast(c).trackerPreview; - }); - - int p = frameToPixel(tp) + x(); + int p = frameToPixel(m_data->a_getPreviewTracker()) + x(); fl_color(G_COLOR_LIGHT_2); fl_line(p, y() + 1, p, y() + h() - 2); } @@ -303,8 +293,8 @@ void geWaveform::drawPlayHead() void geWaveform::draw() { - assert(m_data.sup.size() > 0); - assert(m_data.inf.size() > 0); + assert(m_waveform.sup.size() > 0); + assert(m_waveform.inf.size() > 0); fl_rectf(x(), y(), w(), h(), G_COLOR_GREY_2); // blank canvas @@ -332,9 +322,10 @@ void geWaveform::draw() int geWaveform::handle(int e) { - m::model::WavesLock lock(m::model::waves); - - const m::Wave& wave = m::model::get(m::model::waves, m_waveId); + /* TODO - geWaveform needs better isolation from m::. Refactoring needed. */ + + m::model::WavesLock l(m::model::waves); + const m::Wave& wave = m::model::get(m::model::waves, m_data->waveId); m_mouseX = pixelToFrame(Fl::event_x() - x()); m_mouseY = pixelToFrame(Fl::event_y() - y()); @@ -346,7 +337,7 @@ int geWaveform::handle(int e) static_cast(window())->cb_togglePreview(); else if (Fl::event_key() == FL_BackSpace) - c::sampleEditor::rewindPreview(m_channelId); + c::sampleEditor::setPreviewTracker(m_data->begin); return 1; } @@ -378,7 +369,7 @@ int geWaveform::handle(int e) case FL_RELEASE: { - c::sampleEditor::setPlayHead(m_channelId, m_mouseX); + c::sampleEditor::setPreviewTracker(m_mouseX); /* If selection has been done (m_dragged or resized), make sure that point A is always lower than B. */ @@ -389,7 +380,7 @@ int geWaveform::handle(int e) /* Handle begin/end markers interaction. */ if (m_chanStartLit || m_chanEndLit) - c::sampleEditor::setBeginEnd(m_channelId, m_chanStart, m_chanEnd); + c::sampleEditor::setBeginEnd(m_data->channelId, m_chanStart, m_chanEnd); m_pushed = false; m_dragged = false; @@ -572,13 +563,10 @@ bool geWaveform::mouseOnSelectionB() const int geWaveform::pixelToFrame(int p) const { - Frame waveSize; - m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); }); - if (p <= 0) return 0; - if (p > m_data.size) - return waveSize - 1; + if (p > m_waveform.size) + return m_data->waveSize - 1; return p * m_ratio; } @@ -600,7 +588,7 @@ void geWaveform::fixSelection() if (m_selection.a > m_selection.b) // inverted m_selection std::swap(m_selection.a, m_selection.b); - c::sampleEditor::setPlayHead(m_channelId, m_selection.a); + c::sampleEditor::setPreviewTracker(m_selection.a); } @@ -619,10 +607,10 @@ void geWaveform::clearSelection() void geWaveform::setZoom(Zoom z) { - if (!alloc(z == Zoom::IN ? m_data.size * G_GUI_ZOOM_FACTOR : m_data.size / G_GUI_ZOOM_FACTOR)) + if (!alloc(z == Zoom::IN ? m_waveform.size * G_GUI_ZOOM_FACTOR : m_waveform.size / G_GUI_ZOOM_FACTOR)) return; - size(m_data.size, h()); + size(m_waveform.size, h()); /* Zoom to cursor. */ @@ -668,10 +656,11 @@ void geWaveform::stretchToWindow() /* -------------------------------------------------------------------------- */ -void geWaveform::rebuild() +void geWaveform::rebuild(const c::sampleEditor::Data& d) { + m_data = &d; clearSelection(); - alloc(m_data.size, /*force=*/true); + alloc(m_waveform.size, /*force=*/true); redraw(); } @@ -692,7 +681,7 @@ void geWaveform::setGridLevel(int l) { m_grid.points.clear(); m_grid.level = l; - alloc(m_data.size, true); // force alloc + alloc(m_waveform.size, true); // force alloc redraw(); } @@ -711,7 +700,7 @@ bool geWaveform::isSelected() const void geWaveform::setSnap(bool v) { m_grid.snap = v; } bool geWaveform::getSnap() const { return m_grid.snap; } -int geWaveform::getSize() const { return m_data.size; } +int geWaveform::getSize() const { return m_waveform.size; } /* -------------------------------------------------------------------------- */ @@ -723,11 +712,8 @@ int geWaveform::getSelectionB() const { return m_selection.b; } void geWaveform::selectAll() { - Frame waveSize; - m::model::onGet(m::model::waves, m_waveId, [&](m::Wave& w) { waveSize = w.getSize(); }); - m_selection.a = 0; - m_selection.b = waveSize - 1; + m_selection.b = m_data->waveSize - 1; redraw(); } }} // giada::v:: diff --git a/src/gui/elems/sampleEditor/waveform.h b/src/gui/elems/sampleEditor/waveform.h index d36691b..4a928b9 100644 --- a/src/gui/elems/sampleEditor/waveform.h +++ b/src/gui/elems/sampleEditor/waveform.h @@ -48,7 +48,7 @@ public: #endif enum class Zoom { IN, OUT }; - geWaveform(ID channelId, ID waveId, int x, int y, int w, int h); + geWaveform(int x, int y, int w, int h); void draw() override; int handle(int e) override; @@ -82,7 +82,7 @@ public: /* rebuild Redraws the waveform. */ - void rebuild(); + void rebuild(const c::sampleEditor::Data& d); /* setGridLevel Sets a new frequency level for the grid. 0 means disabled. */ @@ -99,7 +99,7 @@ public: /* setWaveId Call this when the Wave ID has changed (e.g. after a reload). */ - void setWaveId(ID id) { m_waveId = id; }; + void setWaveId(ID id) { /* TODO m_waveId = id;*/}; private: @@ -125,7 +125,7 @@ private: std::vector sup; // upper part of the waveform std::vector inf; // lower part of the waveform int size; // width of the waveform to draw (in pixel) - } m_data; + } m_waveform; struct { @@ -188,8 +188,7 @@ private: int alloc(int datasize, bool force=false); - ID m_channelId; - ID m_waveId; + const c::sampleEditor::Data* m_data; int m_chanStart; bool m_chanStartLit; diff --git a/src/gui/updater.cpp b/src/gui/updater.cpp index 5db4c1f..c1fc884 100644 --- a/src/gui/updater.cpp +++ b/src/gui/updater.cpp @@ -40,7 +40,7 @@ void update(void* p) { if (m::model::waves.changed.load() == true || m::model::actions.changed.load() == true || - m::model::channels.changed.load() == true) + m::model::channels.changed.load() == true) { u::gui::rebuild(); m::model::waves.changed.store(false); diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp index d207f5f..a0fa5bd 100644 --- a/src/utils/gui.cpp +++ b/src/utils/gui.cpp @@ -33,7 +33,6 @@ #elif defined(__linux__) || defined(__FreeBSD__) #include #endif -#include "core/channels/channel.h" #include "core/mixer.h" #include "core/mixerHandler.h" #include "core/clock.h" @@ -154,7 +153,7 @@ void updateStaticWidgets() G_MainWin->mainTimer->setMeter(clock::getBeats(), clock::getBars()); G_MainWin->mainTimer->setBpm(clock::getBpm()); - G_MainWin->mainTimer->setQuantizer(clock::getQuantize()); + G_MainWin->mainTimer->setQuantizer(clock::getQuantizerValue()); } diff --git a/src/utils/math.h b/src/utils/math.h index 5084021..ce45677 100644 --- a/src/utils/math.h +++ b/src/utils/math.h @@ -59,30 +59,6 @@ TO map(TI x, TI b, TO z) { return (x / (double) b) * z; } - - -/* -------------------------------------------------------------------------- */ - -/* bound (1) -Returns 'def' if 'x' is outside the range ('min', 'max'). */ - -template -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 -T bound(T x, T min, T max) -{ - if (x < min) return min; - if (x > max) return max; - return x; -} }}} // giada::u::math:: diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 7e22758..4d7757c 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -89,7 +89,7 @@ std::string trim(const std::string& s) std::string replace(std::string in, const std::string& search, const std::string& replace) { - size_t pos = 0; + std::size_t pos = 0; while ((pos = in.find(search, pos)) != std::string::npos) { in.replace(pos, search.length(), replace); pos += replace.length(); @@ -109,7 +109,7 @@ std::string format(const char* format, ...) into account). */ va_start(args, format); - size_t size = vsnprintf(nullptr, 0, format, args) + 1; + std::size_t size = vsnprintf(nullptr, 0, format, args) + 1; va_end(args); /* Create a new temporary char array to hold the new expanded std::string. */ @@ -134,8 +134,8 @@ std::vector split(std::string in, std::string sep) std::vector 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); diff --git a/src/utils/vector.h b/src/utils/vector.h index 583d7de..4f6a785 100644 --- a/src/utils/vector.h +++ b/src/utils/vector.h @@ -39,19 +39,12 @@ namespace u { namespace vector { template -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 -size_t indexOfIf(T& v, P p) -{ - return std::distance(v.begin(), std::find_if(v.begin(), v.end(), p)); -} - - /* -------------------------------------------------------------------------- */ diff --git a/tests/audioBuffer.cpp b/tests/audioBuffer.cpp index b9d1499..189f416 100644 --- a/tests/audioBuffer.cpp +++ b/tests/audioBuffer.cpp @@ -87,13 +87,6 @@ TEST_CASE("AudioBuffer") REQUIRE(buffer[1024][0] == 2048.0f); } - SECTION("test copy frame") - { - buffer.copyFrame(16, &data[32]); - REQUIRE(buffer[16][0] == 32.0f); - REQUIRE(buffer[16][1] == 33.0f); - } - delete[] data; } } diff --git a/tests/sampleChannel.cpp b/tests/sampleChannel.cpp deleted file mode 100644 index dcb59f3..0000000 --- a/tests/sampleChannel.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "../src/core/channels/sampleChannel.h" -#include "../src/core/model/model.h" -#include - - -TEST_CASE("sampleChannel") -{ - using namespace giada; - using namespace giada::m; - - const int BUFFER_SIZE = 1024; - const int WAVE_SIZE = 50000; - - std::vector 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(false, BUFFER_SIZE, 1, 1)); - - model::onSwap(model::channels, 1, [&](Channel& c) - { - static_cast(c).pushWave(1, WAVE_SIZE); - }); - - model::channels.lock(); - SampleChannel& ch = static_cast(*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 */ -} diff --git a/tests/waveFx.cpp b/tests/waveFx.cpp index bc8bf21..ca25d01 100644 --- a/tests/waveFx.cpp +++ b/tests/waveFx.cpp @@ -118,8 +118,8 @@ TEST_CASE("waveFx") int a = 47; int b = 500; - wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_IN); - wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::FADE_OUT); + wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::IN); + wfx::fade(getWave(WAVE_STEREO_ID).id, a, b, wfx::Fade::OUT); REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[0] == 0.0f); REQUIRE(getWave(WAVE_STEREO_ID).getFrame(a)[1] == 0.0f); @@ -128,8 +128,8 @@ TEST_CASE("waveFx") SECTION("test fade (mono)") { - wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_IN); - wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::FADE_OUT); + wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::IN); + wfx::fade(getWave(WAVE_MONO_ID).id, a, b, wfx::Fade::OUT); REQUIRE(getWave(WAVE_MONO_ID).getFrame(a)[0] == 0.0f); REQUIRE(getWave(WAVE_MONO_ID).getFrame(b)[0] == 0.0f);