From: IOhannes m zmölnig
Date: Tue, 2 Feb 2021 21:14:14 +0000 (+0100)
Subject: New upstream version 0.17.1+ds1
X-Git-Tag: archive/raspbian/0.20.1+ds1-1+rpi1~1^2~12^2~6
X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=105695688f96a173e31948c7bda62fc53eddc926;p=giada.git
New upstream version 0.17.1+ds1
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..0a5118b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,515 @@
+# ------------------------------------------------------------------------------
+# Preliminary setup
+# ------------------------------------------------------------------------------
+
+cmake_minimum_required(VERSION 3.12)
+
+# CMAKE_OSX_DEPLOYMENT_TARGET should be set prior to the first project() or
+# enable_language() command invocation because it may influence configuration
+# of the toolchain and flags.
+# Also, see https://stackoverflow.com/questions/34208360/cmake-seems-to-ignore-cmake-osx-deployment-target
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version")
+endif()
+
+# ------------------------------------------------------------------------------
+# Project
+# ------------------------------------------------------------------------------
+
+project(giada LANGUAGES CXX)
+
+# ------------------------------------------------------------------------------
+# Lists definition
+#
+# SOURCES - contains the source files
+# PREPROCESSOR_DEFS - preprocessor definitions
+# INCLUDE_DIRS - include directories (e.g. -I)
+# COMPILER_OPTIONS - additional flags for the compiler
+# LIBRARIES - external dependencies to link
+# COMPILER_FEATURES - e.g. C++17
+# TARGET_PROPERTIES - additional properties for the target 'giada'.
+# ------------------------------------------------------------------------------
+
+list(APPEND SOURCES
+ src/main.cpp
+ src/core/midiDispatcher.cpp
+ src/core/midiMapConf.cpp
+ src/core/midiEvent.cpp
+ src/core/audioBuffer.cpp
+ src/core/quantizer.cpp
+ src/core/conf.cpp
+ src/core/kernelAudio.cpp
+ src/core/mixerHandler.cpp
+ src/core/sequencer.cpp
+ src/core/init.cpp
+ src/core/wave.cpp
+ src/core/waveFx.cpp
+ src/core/kernelMidi.cpp
+ src/core/graphics.cpp
+ src/core/patch.cpp
+ src/core/recorderHandler.cpp
+ src/core/recorder.cpp
+ src/core/mixer.cpp
+ src/core/clock.cpp
+ src/core/waveManager.cpp
+ src/core/recManager.cpp
+ src/core/midiLearnParam.cpp
+ src/core/plugins/pluginHost.cpp
+ src/core/plugins/pluginManager.cpp
+ src/core/plugins/plugin.cpp
+ src/core/plugins/pluginState.cpp
+ src/core/channels/state.cpp
+ src/core/channels/sampleActionRecorder.cpp
+ src/core/channels/midiActionRecorder.cpp
+ src/core/channels/waveReader.cpp
+ src/core/channels/midiController.cpp
+ src/core/channels/sampleController.cpp
+ src/core/channels/samplePlayer.cpp
+ src/core/channels/audioReceiver.cpp
+ src/core/channels/midiLighter.cpp
+ src/core/channels/midiLearner.cpp
+ src/core/channels/midiSender.cpp
+ src/core/channels/midiReceiver.cpp
+ src/core/channels/channel.cpp
+ src/core/channels/channelManager.cpp
+ src/core/model/model.cpp
+ src/core/model/storage.cpp
+ src/core/idManager.cpp
+ src/glue/events.cpp
+ src/glue/main.cpp
+ src/glue/io.cpp
+ src/glue/storage.cpp
+ src/glue/channel.cpp
+ src/glue/plugin.cpp
+ src/glue/recorder.cpp
+ src/glue/sampleEditor.cpp
+ src/glue/actionEditor.cpp
+ src/gui/dialogs/window.cpp
+ src/gui/dispatcher.cpp
+ src/gui/updater.cpp
+ src/gui/model.cpp
+ src/gui/dialogs/keyGrabber.cpp
+ src/gui/dialogs/about.cpp
+ src/gui/dialogs/mainWindow.cpp
+ src/gui/dialogs/beatsInput.cpp
+ src/gui/dialogs/warnings.cpp
+ src/gui/dialogs/bpmInput.cpp
+ src/gui/dialogs/channelNameInput.cpp
+ src/gui/dialogs/config.cpp
+ src/gui/dialogs/devInfo.cpp
+ src/gui/dialogs/pluginList.cpp
+ src/gui/dialogs/pluginWindow.cpp
+ src/gui/dialogs/sampleEditor.cpp
+ src/gui/dialogs/pluginWindowGUI.cpp
+ src/gui/dialogs/pluginChooser.cpp
+ src/gui/dialogs/actionEditor/baseActionEditor.cpp
+ src/gui/dialogs/actionEditor/sampleActionEditor.cpp
+ src/gui/dialogs/actionEditor/midiActionEditor.cpp
+ src/gui/dialogs/browser/browserBase.cpp
+ src/gui/dialogs/browser/browserDir.cpp
+ src/gui/dialogs/browser/browserLoad.cpp
+ src/gui/dialogs/browser/browserSave.cpp
+ src/gui/dialogs/midiIO/midiOutputBase.cpp
+ src/gui/dialogs/midiIO/midiOutputSampleCh.cpp
+ src/gui/dialogs/midiIO/midiOutputMidiCh.cpp
+ src/gui/dialogs/midiIO/midiInputBase.cpp
+ src/gui/dialogs/midiIO/midiInputChannel.cpp
+ src/gui/dialogs/midiIO/midiInputMaster.cpp
+ src/gui/elems/midiIO/midiLearner.cpp
+ src/gui/elems/midiIO/midiLearnerPack.cpp
+ src/gui/elems/browser.cpp
+ src/gui/elems/soundMeter.cpp
+ src/gui/elems/plugin/pluginBrowser.cpp
+ src/gui/elems/plugin/pluginParameter.cpp
+ src/gui/elems/plugin/pluginElement.cpp
+ src/gui/elems/sampleEditor/waveform.cpp
+ src/gui/elems/sampleEditor/waveTools.cpp
+ src/gui/elems/sampleEditor/volumeTool.cpp
+ src/gui/elems/sampleEditor/boostTool.cpp
+ src/gui/elems/sampleEditor/panTool.cpp
+ src/gui/elems/sampleEditor/pitchTool.cpp
+ src/gui/elems/sampleEditor/rangeTool.cpp
+ src/gui/elems/sampleEditor/shiftTool.cpp
+ src/gui/elems/actionEditor/baseActionEditor.cpp
+ src/gui/elems/actionEditor/baseAction.cpp
+ src/gui/elems/actionEditor/envelopeEditor.cpp
+ src/gui/elems/actionEditor/velocityEditor.cpp
+ src/gui/elems/actionEditor/envelopePoint.cpp
+ src/gui/elems/actionEditor/pianoRoll.cpp
+ src/gui/elems/actionEditor/noteEditor.cpp
+ src/gui/elems/actionEditor/pianoItem.cpp
+ src/gui/elems/actionEditor/sampleActionEditor.cpp
+ src/gui/elems/actionEditor/sampleAction.cpp
+ src/gui/elems/actionEditor/gridTool.cpp
+ src/gui/elems/mainWindow/mainIO.cpp
+ src/gui/elems/mainWindow/mainMenu.cpp
+ src/gui/elems/mainWindow/mainTimer.cpp
+ src/gui/elems/mainWindow/mainTransport.cpp
+ src/gui/elems/mainWindow/beatMeter.cpp
+ src/gui/elems/mainWindow/keyboard/channelMode.cpp
+ src/gui/elems/mainWindow/keyboard/channelButton.cpp
+ src/gui/elems/mainWindow/keyboard/channelStatus.cpp
+ src/gui/elems/mainWindow/keyboard/keyboard.cpp
+ src/gui/elems/mainWindow/keyboard/column.cpp
+ src/gui/elems/mainWindow/keyboard/sampleChannel.cpp
+ src/gui/elems/mainWindow/keyboard/midiChannel.cpp
+ src/gui/elems/mainWindow/keyboard/channel.cpp
+ src/gui/elems/mainWindow/keyboard/sampleChannelButton.cpp
+ src/gui/elems/mainWindow/keyboard/midiChannelButton.cpp
+ src/gui/elems/config/tabMisc.cpp
+ src/gui/elems/config/tabMidi.cpp
+ src/gui/elems/config/tabAudio.cpp
+ src/gui/elems/config/tabBehaviors.cpp
+ src/gui/elems/config/tabPlugins.cpp
+ src/gui/elems/basics/scroll.cpp
+ src/gui/elems/basics/pack.cpp
+ src/gui/elems/basics/group.cpp
+ src/gui/elems/basics/scrollPack.cpp
+ src/gui/elems/basics/boxtypes.cpp
+ src/gui/elems/basics/statusButton.cpp
+ src/gui/elems/basics/button.cpp
+ src/gui/elems/basics/resizerBar.cpp
+ src/gui/elems/basics/input.cpp
+ src/gui/elems/basics/liquidScroll.cpp
+ src/gui/elems/basics/choice.cpp
+ src/gui/elems/basics/dial.cpp
+ src/gui/elems/basics/box.cpp
+ src/gui/elems/basics/slider.cpp
+ src/gui/elems/basics/progress.cpp
+ src/gui/elems/basics/check.cpp
+ src/gui/elems/basics/radio.cpp
+ src/utils/log.cpp
+ src/utils/time.cpp
+ src/utils/math.cpp
+ src/utils/gui.cpp
+ src/utils/fs.cpp
+ src/utils/ver.cpp
+ src/utils/string.cpp
+ src/deps/rtaudio/RtAudio.cpp)
+
+list(APPEND PREPROCESSOR_DEFS)
+list(APPEND INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/src)
+list(APPEND COMPILER_OPTIONS)
+list(APPEND LIBRARIES)
+list(APPEND COMPILER_FEATURES cxx_std_17)
+list(APPEND TARGET_PROPERTIES)
+
+# ------------------------------------------------------------------------------
+# Detect OS
+# ------------------------------------------------------------------------------
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ set(OS_LINUX 1)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ set(OS_WINDOWS 1)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ set(OS_MACOS 1)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ set(OS_FREEBSD 1)
+else()
+ message(FATAL_ERROR "Unsupported platform '${CMAKE_SYSTEM_NAME}', quitting.")
+endif()
+
+# ------------------------------------------------------------------------------
+# Compiler warnings
+# ------------------------------------------------------------------------------
+
+if(DEFINED OS_WINDOWS)
+ list(APPEND COMPILER_OPTIONS /W4)
+else()
+ list(APPEND COMPILER_OPTIONS -Wall -Wextra -Wpedantic)
+endif()
+
+# ------------------------------------------------------------------------------
+# Options
+# ------------------------------------------------------------------------------
+
+option(WITH_VST2 "Enable VST2 support." OFF)
+option(WITH_VST3 "Enable VST3 support." OFF)
+option(WITH_TESTS "Include the test suite." OFF)
+
+if(WITH_TESTS)
+ list(APPEND PREPROCESSOR_DEFS
+ WITH_TESTS
+ TEST_RESOURCES_DIR="${CMAKE_SOURCE_DIR}/tests/resources/")
+endif()
+
+if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+ list(APPEND PREPROCESSOR_DEFS NDEBUG)
+endif()
+
+# ------------------------------------------------------------------------------
+# Dependencies
+# ------------------------------------------------------------------------------
+
+# Threads (system)
+
+find_package(Threads REQUIRED)
+list(APPEND LIBRARIES ${Threads_LIBRARY})
+
+# RtMidi
+
+find_package(RtMidi CONFIG)
+if (RtMidi_FOUND)
+ list(APPEND LIBRARIES RtMidi::rtmidi)
+else()
+ # Fallback to find_library mode (in case rtmidi is too old).
+ find_library(LIBRARY_RTMIDI NAMES rtmidi)
+ list(APPEND LIBRARIES ${LIBRARY_RTMIDI})
+
+ if (NOT LIBRARY_RTMIDI)
+ message(FATAL_ERROR "Can't find RtMidi, aborting.")
+ endif()
+
+ # RtMidi header path may vary accross OSes, so a fix is needed.
+ # TODO - Find a way to avoid this additional step
+
+ find_path(LIBRARY_RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES rtmidi)
+ list(APPEND INCLUDE_DIRS ${LIBRARY_RTMIDI_INCLUDE_DIR})
+endif()
+
+message("RtMidi library found in " ${RtMidi_DIR})
+
+# FLTK
+
+set(FLTK_SKIP_FLUID TRUE) # Don't search for FLTK's fluid
+set(FLTK_SKIP_OPENGL TRUE) # Don't search for FLTK's OpenGL
+find_package(FLTK CONFIG REQUIRED)
+list(APPEND LIBRARIES fltk fltk_gl fltk_forms fltk_images)
+message("FLTK library found in " ${FLTK_DIR})
+
+# Libsndfile
+
+find_package(SndFile CONFIG)
+if (SndFile_FOUND)
+ list(APPEND LIBRARIES SndFile::sndfile)
+ message("Libsndfile library found in " ${SndFile_DIR})
+else()
+ # Fallback to find_library mode (in case libsndfile is too old).
+ find_library(LIBRARY_SNDFILE NAMES sndfile libsndfile libsndfile-1)
+
+ if (NOT LIBRARY_SNDFILE)
+ message(FATAL_ERROR "Can't find libsndfile, aborting.")
+ endif()
+
+ list(APPEND LIBRARIES ${LIBRARY_SNDFILE})
+ message("Libsndfile library found in " ${LIBRARY_SNDFILE})
+
+ # Find optional dependencies.
+
+ find_library(LIBRARY_FLAC NAMES flac FLAC)
+ find_library(LIBRARY_OGG NAMES ogg)
+ find_library(LIBRARY_OPUS NAMES opus libopus)
+ find_library(LIBRARY_VORBIS NAMES vorbis)
+ find_library(LIBRARY_VORBISENC NAMES vorbisenc)
+
+ if(LIBRARY_FLAC)
+ list(APPEND LIBRARIES ${LIBRARY_FLAC})
+ endif()
+ if(LIBRARY_OGG)
+ list(APPEND LIBRARIES ${LIBRARY_OGG})
+ endif()
+ if(LIBRARY_OPUS)
+ list(APPEND LIBRARIES ${LIBRARY_OPUS})
+ endif()
+ if(LIBRARY_VORBIS)
+ list(APPEND LIBRARIES ${LIBRARY_VORBIS})
+ endif()
+ if(LIBRARY_VORBISENC)
+ list(APPEND LIBRARIES ${LIBRARY_VORBISENC})
+ endif()
+endif()
+
+# Libsamplerate
+# TODO - new libsamplerate now provides CMake targets. Update it!
+
+find_library(LIBRARY_SAMPLERATE
+ NAMES samplerate libsamplerate libsamplerate-0 liblibsamplerate-0
+ PATHS ${_VCPKG_ROOT_DIR}/installed/${VCPKG_TARGET_TRIPLET})
+list(APPEND LIBRARIES ${LIBRARY_SAMPLERATE})
+message("Libsamplerate library found in " ${LIBRARY_SAMPLERATE})
+
+# Catch (if tests enabled)
+
+if (WITH_TESTS)
+
+ find_package(Catch2 CONFIG REQUIRED)
+ list(APPEND LIBRARIES Catch2::Catch2)
+ message("Catch2 library found in " ${Catch2_DIR})
+
+endif()
+
+# ------------------------------------------------------------------------------
+# Conditional checks for different platforms.
+# ------------------------------------------------------------------------------
+
+if(DEFINED OS_LINUX)
+
+ find_package(X11 REQUIRED)
+ find_package(ALSA REQUIRED)
+ find_library(LIBRARY_PULSE NAMES pulse REQUIRED)
+ find_library(LIBRARY_PULSE_SIMPLE NAMES pulse-simple REQUIRED)
+ find_library(LIBRARY_FONTCONFIG NAMES fontconfig REQUIRED)
+ find_library(LIBRARY_JACK NAMES jack REQUIRED)
+ list(APPEND LIBRARIES
+ ${X11_LIBRARIES} ${X11_Xrender_LIB} ${X11_Xft_LIB} ${X11_Xfixes_LIB}
+ ${X11_Xinerama_LIB} ${X11_Xcursor_LIB} ${X11_Xpm_LIB} ${LIBRARY_PULSE}
+ ${LIBRARY_PULSE_SIMPLE} ${LIBRARY_FONTCONFIG} ${LIBRARY_JACK}
+ ${CMAKE_DL_LIBS} ${ALSA_LIBRARIES} pthread stdc++fs)
+
+ list(APPEND PREPROCESSOR_DEFS
+ WITH_AUDIO_JACK
+ __LINUX_ALSA__
+ __LINUX_PULSE__
+ __UNIX_JACK__)
+
+elseif(DEFINED OS_WINDOWS)
+
+ list(APPEND LIBRARIES dsound)
+
+ list(APPEND SOURCES
+ src/deps/rtaudio/include/asio.h
+ src/deps/rtaudio/include/asio.cpp
+ src/deps/rtaudio/include/asiosys.h
+ src/deps/rtaudio/include/asiolist.h
+ src/deps/rtaudio/include/asiolist.cpp
+ src/deps/rtaudio/include/asiodrivers.h
+ src/deps/rtaudio/include/asiodrivers.cpp
+ src/deps/rtaudio/include/iasiothiscallresolver.h
+ src/deps/rtaudio/include/iasiothiscallresolver.cpp
+ src/ext/resource.rc)
+
+ list(APPEND INCLUDE_DIRS
+ src/deps/rtaudio/include)
+
+ list(APPEND PREPROCESSOR_DEFS
+ __WINDOWS_ASIO__
+ __WINDOWS_WASAPI__
+ __WINDOWS_DS__)
+
+elseif(DEFINED OS_MACOS)
+
+ find_library(CORE_AUDIO_LIBRARY CoreAudio REQUIRED)
+ find_library(CORE_MIDI_LIBRARY CoreMIDI REQUIRED)
+ find_library(COCOA_LIBRARY Cocoa REQUIRED)
+ find_library(CARBON_LIBRARY Carbon REQUIRED)
+ find_library(CORE_FOUNDATION_LIBRARY CoreFoundation REQUIRED)
+ find_library(ACCELERATE_LIBRARY Accelerate REQUIRED)
+ find_library(WEBKIT_LIBRARY WebKit REQUIRED)
+ find_library(QUARZ_CORE_LIBRARY QuartzCore REQUIRED)
+ find_library(IOKIT_LIBRARY IOKit REQUIRED)
+ list(APPEND LIBRARIES
+ ${CORE_AUDIO_LIBRARY} ${CORE_MIDI_LIBRARY} ${COCOA_LIBRARY}
+ ${CARBON_LIBRARY} ${CORE_FOUNDATION_LIBRARY} ${ACCELERATE_LIBRARY}
+ ${WEBKIT_LIBRARY} ${QUARZ_CORE_LIBRARY} ${IOKIT_LIBRARY})
+
+ list(APPEND SOURCES
+ src/utils/cocoa.mm
+ src/utils/cocoa.h)
+
+ # TODO: why??
+ list(APPEND INCLUDE_DIRS
+ "/usr/local/include")
+
+ list(APPEND PREPROCESSOR_DEFS
+ __MACOSX_CORE__)
+
+elseif (DEFINED OS_FREEBSD)
+
+ find_package(X11 REQUIRED)
+ find_library(LIBRARY_PULSE NAMES pulse REQUIRED)
+ find_library(LIBRARY_PULSE_SIMPLE NAMES pulse-simple REQUIRED)
+ find_library(LIBRARY_FONTCONFIG NAMES fontconfig REQUIRED)
+ find_library(LIBRARY_JACK NAMES jack REQUIRED)
+ list(APPEND LIBRARIES
+ ${X11_LIBRARIES} ${X11_Xrender_LIB} ${X11_Xft_LIB} ${X11_Xfixes_LIB}
+ ${X11_Xinerama_LIB} ${X11_Xcursor_LIB} ${X11_Xpm_LIB} ${LIBRARY_PULSE}
+ ${LIBRARY_PULSE_SIMPLE} ${LIBRARY_FONTCONFIG} ${LIBRARY_JACK}
+ ${CMAKE_DL_LIBS} pthread)
+
+ list(APPEND PREPROCESSOR_DEFS
+ __LINUX_PULSE__
+ __UNIX_JACK__)
+
+endif()
+
+# ------------------------------------------------------------------------------
+# Extra parameters if compiled with VST.
+# ------------------------------------------------------------------------------
+
+if(WITH_VST2 OR WITH_VST3)
+
+ list(APPEND SOURCES
+ src/deps/juce/modules/juce_audio_basics/juce_audio_basics.cpp
+ src/deps/juce/modules/juce_audio_processors/juce_audio_processors.cpp
+ src/deps/juce/modules/juce_core/juce_core.cpp
+ src/deps/juce/modules/juce_data_structures/juce_data_structures.cpp
+ src/deps/juce/modules/juce_events/juce_events.cpp
+ src/deps/juce/modules/juce_graphics/juce_graphics.cpp
+ src/deps/juce/modules/juce_gui_basics/juce_gui_basics.cpp
+ src/deps/juce/modules/juce_gui_extra/juce_gui_extra.cpp)
+
+ list(APPEND INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/src/deps/juce/modules
+ ${CMAKE_SOURCE_DIR}/src/deps/vst3sdk)
+
+ if(DEFINED OS_LINUX)
+ find_package(Freetype REQUIRED)
+ list(APPEND LIBRARIES ${FREETYPE_LIBRARIES})
+ list(APPEND INCLUDE_DIRS ${FREETYPE_INCLUDE_DIRS})
+ endif()
+
+ list(APPEND PREPROCESSOR_DEFS
+ WITH_VST
+ JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1
+ JUCE_MODULE_AVAILABLE_juce_gui_basics=1
+ JUCE_STANDALONE_APPLICATION=1
+ JUCE_PLUGINHOST_AU=0
+ JUCE_WEB_BROWSER=0
+ JUCE_USE_CURL=0)
+
+ if(WITH_VST2)
+ list(APPEND PREPROCESSOR_DEFS
+ WITH_VST2
+ JUCE_PLUGINHOST_VST=1)
+ endif()
+ if(WITH_VST3)
+ list(APPEND PREPROCESSOR_DEFS
+ WITH_VST3
+ JUCE_PLUGINHOST_VST3=1)
+ endif()
+
+endif()
+
+# ------------------------------------------------------------------------------
+# Finalize 'giada' target (main executable).
+# ------------------------------------------------------------------------------
+
+add_executable(giada)
+target_compile_features(giada PRIVATE ${COMPILER_FEATURES})
+target_sources(giada PRIVATE ${SOURCES})
+target_compile_definitions(giada PRIVATE ${PREPROCESSOR_DEFS})
+target_include_directories(giada PRIVATE ${INCLUDE_DIRS})
+target_link_libraries(giada PRIVATE ${LIBRARIES})
+target_compile_options(giada PRIVATE ${COMPILER_OPTIONS})
+
+# ------------------------------------------------------------------------------
+# Install rules
+# ------------------------------------------------------------------------------
+
+install(TARGETS giada DESTINATION ${CMAKE_INSTALL_PREFIX})
+
+# ------------------------------------------------------------------------------
+# Extra
+# ------------------------------------------------------------------------------
+
+# Enable static linking of the MSVC runtime library on Windows
+# TODO - move this into the 'if windows' conditional (needs smarter list first)
+
+if(DEFINED OS_WINDOWS)
+ set_target_properties(giada PROPERTIES
+ MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
+endif()
diff --git a/ChangeLog b/ChangeLog
index 655967f..6cb225b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -11,6 +11,70 @@
--------------------------------------------------------------------------------
+0.17.1 --- 2021 . 02 . 01
+- Better CMake dependency management
+- Add CMake install rules (#422)
+- Switch to GitHub Actions for CI and release builds (#440)
+- Remove hardcored 'test' folder in test suite (#432)
+- Make sure macOS minimum target is set to 10.14 (#444)
+- Fix crash when restarting after setting jack as an audio server (#409, #368)
+- Fix crash when clicking "Cancel" button in Browser dialog (#430)
+- Fix wrong action ID mapping when cloning a channel (#426)
+- Fix scrambled MIDI bindings (#427)
+
+
+0.17.0 --- 2020 . 11 . 15
+- Add CMake build system
+- VST3 support
+- Show descriptive plug-in names in Plug-in List Window
+- Resizable plug-in list
+- New persistence mechanism for Plug-ins state
+- Improved text truncation for small buttons and text boxes
+- Beautify Sample Editor window
+- Resizable plug-in list window
+- Show descriptive plug-in name in plug-in list
+- Update JUCE, version 6.0.4
+- Update Catch2 to version 2.13.2
+- Replace old filesystem functions in fs.h with std::filesystem
+- Add VST3 SDK as git submodule
+- Set minimum macOS version to 10.14
+- Statically link the MSVC runtime library on Windows
+- Avoid crash on opening plug-in list with invalid plug-ins
+- Rewind sample channels in loop.once.bar mode on bar, if still playing (fix #403)
+- Modernize log::print() function to handle std::string arguments (PR #402)
+- Fix playStatus logic for ending sample channels in loop-once-bar mode (#404)
+- Fix shrinking beats that could glitch the output (#361)
+
+
+0.16.4 --- 2020 . 09. 19
+- Support for mono inputs
+- Overdub mode for Sample Channels with optional overdub protection
+- Disable record-on-signal mode when sequencer is running
+- Shift + [click on R button] kills action reading when "Treat one-shot channels
+ with actions as loops" option is on
+- Start MIDI channels automatically after action recording session
+- Fix wrong sample rate conversion when project rate != system rate
+- Fix Wrong begin/end sample markers when loading a project with
+ samplerate != system.samplerate
+- Fix wrong MIDI learn mapping for master parameters
+- Fix BPM button disabled after audio recording session
+
+
+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..ded463a 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,20 +26,26 @@ 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 \
src/core/kernelAudio.cpp \
- src/core/pluginHost.h \
- src/core/pluginHost.cpp \
- src/core/pluginManager.h \
- src/core/pluginManager.cpp \
+ src/core/plugins/pluginHost.h \
+ src/core/plugins/pluginHost.cpp \
+ src/core/plugins/pluginManager.h \
+ src/core/plugins/pluginManager.cpp \
+ src/core/plugins/plugin.h \
+ src/core/plugins/plugin.cpp \
+ src/core/plugins/pluginState.h \
+ src/core/plugins/pluginState.cpp \
src/core/mixerHandler.h \
src/core/mixerHandler.cpp \
+ src/core/sequencer.h \
+ src/core/sequencer.cpp \
src/core/init.h \
src/core/init.cpp \
- src/core/plugin.h \
- src/core/plugin.cpp \
src/core/wave.h \
src/core/wave.cpp \
src/core/waveFx.h \
@@ -47,6 +54,7 @@ sourcesCore = \
src/core/kernelMidi.cpp \
src/core/graphics.h \
src/core/graphics.cpp \
+ src/core/midiLearnParam.cpp \
src/core/patch.h \
src/core/patch.cpp \
src/core/recorderHandler.h \
@@ -61,28 +69,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 +183,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 +225,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,20 +275,16 @@ 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 \
- src/gui/elems/basics/baseButton.cpp \
src/gui/elems/basics/statusButton.h \
src/gui/elems/basics/statusButton.cpp \
src/gui/elems/basics/button.h \
@@ -303,7 +325,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 +335,7 @@ sourcesTests = \
tests/utils.cpp \
tests/recorder.cpp \
tests/waveFx.cpp \
- tests/audioBuffer.cpp \
- tests/sampleChannel.cpp
-
+ tests/audioBuffer.cpp
if WITH_VST
sourcesExtra += \
@@ -411,7 +431,7 @@ cppFlags += -D__MACOSX_CORE__
cxxFlags += -ObjC++
ldAdd += -lsndfile -lfltk -lrtmidi -lsamplerate -lm -lpthread \
- -lFLAC -logg -lvorbis -lvorbisenc
+ -lFLAC -logg -lvorbis -lvorbisenc -lopus
ldFlags += -framework CoreAudio -framework Cocoa -framework Carbon \
-framework CoreMIDI -framework CoreFoundation -framework Accelerate \
@@ -423,7 +443,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..4d1978c 100644
--- a/README.md
+++ b/README.md
@@ -3,36 +3,40 @@
-Giada - Your Hardcore Loop Machine | Official website: giadamusic.com | Travis CI status:
+Giada - Your Hardcore Loop Machine | Official website: giadamusic.com |
## 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! â¦â¦â¦
-
+
## 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/extras/giada-logo.svg b/extras/giada-logo.svg
new file mode 100644
index 0000000..17e6885
--- /dev/null
+++ b/extras/giada-logo.svg
@@ -0,0 +1,17 @@
+
+
+image/svg+xml
diff --git a/extras/giada.icns b/extras/giada.icns
new file mode 100644
index 0000000..627e605
Binary files /dev/null and b/extras/giada.icns differ
diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh
new file mode 100755
index 0000000..aff5570
--- /dev/null
+++ b/scripts/create_source_tarball.sh
@@ -0,0 +1,130 @@
+#!/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 \
+ --shallow-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/.dirstamp b/src/core/.dirstamp
deleted file mode 100644
index e69de29..0000000
diff --git a/src/core/audioBuffer.cpp b/src/core/audioBuffer.cpp
index ddb9024..483e3c4 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,10 +34,29 @@ 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,49 @@ 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)
{
+ assert(channels <= NUM_CHANS);
+
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 +134,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,8 +142,10 @@ void AudioBuffer::free()
/* -------------------------------------------------------------------------- */
-void AudioBuffer::setData(float* data, int size, int channels)
+void AudioBuffer::setData(float* data, Frame size, int channels)
{
+ assert(channels <= NUM_CHANS);
+
m_data = data;
m_size = size;
m_channels = channels;
@@ -96,6 +157,8 @@ void AudioBuffer::setData(float* data, int size, int channels)
void AudioBuffer::moveData(AudioBuffer& b)
{
+ assert(b.countChannels() <= NUM_CHANS);
+
free();
m_data = b[0];
m_size = b.countFrames();
@@ -107,20 +170,52 @@ void AudioBuffer::moveData(AudioBuffer& b)
/* -------------------------------------------------------------------------- */
-void AudioBuffer::copyFrame(int frame, float* values)
+void AudioBuffer::copyData(const float* data, Frame frames, int channels, int offset)
{
assert(m_data != nullptr);
- memcpy(m_data + (frame * m_channels), values, m_channels * sizeof(float));
+ assert(frames <= m_size - offset);
+
+ if (channels < NUM_CHANS) // i.e. one channel, mono
+ for (int i = offset, k = 0; i < m_size; i++, k++)
+ for (int j = 0; j < countChannels(); j++)
+ (*this)[i][j] = data[k];
+ else
+ if (channels == NUM_CHANS)
+ std::copy_n(data, frames * channels, m_data + (offset * channels));
+ else
+ assert(false);
+}
+
+
+void AudioBuffer::copyData(const AudioBuffer& b, float gain)
+{
+ copyData(b[0], b.countFrames(), b.countChannels());
+ 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(b.countChannels() <= NUM_CHANS);
+
+ 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..67247aa 100644
--- a/src/core/audioBuffer.h
+++ b/src/core/audioBuffer.h
@@ -1,16 +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_AUDIO_BUFFER_H
#define G_AUDIO_BUFFER_H
+#include
+#include "core/types.h"
+
+
namespace giada {
namespace m
{
-/* TODO - this class needs a serious modern C++ lifting */
+/* AudioBuffer
+A class that holds a buffer filled with audio data. NOTE: currently it only
+supports 2 channels (stereo). Give it a mono stream and it will convert it to
+stereo. Give it a multichannel stream and it will throw an assertion. */
+
class AudioBuffer
{
public:
+
+ static constexpr int NUM_CHANS = 2;
+
+ using Pan = std::array;
+
+ /* AudioBuffer (1)
+ Creates an empty (and invalid) audio buffer. */
AudioBuffer();
+
+ /* AudioBuffer (2)
+ Creates an audio buffer and allocates memory for size * channels frames. */
+
+ AudioBuffer(Frame size, int channels);
+
+ AudioBuffer(const AudioBuffer& o);
~AudioBuffer();
/* operator []
@@ -26,35 +75,49 @@ 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
+ /* copyData (1)
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. */
+ starting from frame 'offset'. The new data MUST NOT contain more than
+ NUM_CHANS channels. If channels < NUM_CHANS, they will be spread over the
+ stereo buffer. */
+
+ void copyData(const float* data, Frame frames, int channels=NUM_CHANS, int offset=0);
- void copyData(const float* data, int frames, int offset=0);
+ /* copyData (2)
+ Copies buffer 'b' onto this one. If 'b' has less channels than this one,
+ they will be spread over the current ones. Buffer 'b' MUST NOT contain more
+ channels than this one. */
- /* 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 AudioBuffer& b, float gain=1.0f);
- void copyFrame(int frame, float* values);
+ /* addData
+ Merges audio data from buffer 'b' onto this one. Applies optional gain and
+ pan if needed. */
+
+ 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
+ Views '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. */
+ Moves data held by 'b' into this buffer. Then 'b' becomes an empty buffer.
+ TODO - add move constructor instead! */
void moveData(AudioBuffer& b);
@@ -62,12 +125,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..dd222e6
--- /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, const conf::Conf& conf)
+: state (std::make_unique(conf))
+, 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..443bd3c
--- /dev/null
+++ b/src/core/channels/audioReceiver.h
@@ -0,0 +1,75 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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 conf
+{
+struct Conf;
+}
+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*, const conf::Conf&);
+ 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..8c7845b 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/plugins/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, const conf::Conf& conf)
+: 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(), conf);
+ 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(p, 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,118 @@ 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;
+ if (m_type != ChannelType::SAMPLE)
+ return false;
+
+ bool armed = state->armed.load();
+ bool hasWave = samplePlayer->hasWave();
+ bool isProtected = audioReceiver->state->overdubProtection.load();
+ bool canOverdub = !hasWave || (hasWave && !isProtected);
+
+ return armed && canOverdub;
+}
+
+
+bool Channel::canActionRec() const
+{
+ return hasWave() && !samplePlayer->state->isAnyLoopMode();
}
+
+bool Channel::hasWave() const
+{
+ return m_type == ChannelType::SAMPLE && samplePlayer->hasWave();
+}
}} // giada::m::
diff --git a/src/core/channels/channel.h b/src/core/channels/channel.h
index a6d4e5f..5e7b912 100644
--- a/src/core/channels/channel.h
+++ b/src/core/channels/channel.h
@@ -29,247 +29,102 @@
#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
+namespace conf
+{
+struct Conf;
+}
+class Channel final
{
public:
- virtual ~Channel() {};
-
- /* 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. */
-
- virtual Channel* clone() const = 0;
-
- /* load
- Loads persistence data into an existing channel. Used for built-in channels
- such as masters and preview. */
-
- virtual void load(const patch::Channel& p) {}
-
- /* parseEvents
- Prepares channel for rendering. This is called on each frame. */
-
- virtual void parseEvents(mixer::FrameEvents fe) {};
-
- /* 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; };
+ Channel(ChannelType t, ID id, ID columnId, Frame bufferSize, const conf::Conf& c);
+ Channel(const Channel&);
+ Channel(const patch::Channel& p, Frame bufferSize);
+ Channel(Channel&&) = default;
+ Channel& operator=(const Channel&) = delete;
+ Channel& operator=(Channel&&) = delete;
+ ~Channel() = default;
- virtual bool recordStart(bool canQuantize) { return true; };
- virtual bool recordKill() { return true; };
- virtual void recordStop() {};
+ /* parse
+ Parses live events. */
- virtual void startReadingActions(bool treatRecsAsLoops,
- bool recsStopOnChanHalt) {};
- virtual void stopReadingActions(bool running, bool treatRecsAsLoops,
- bool recsStopOnChanHalt) {};
+ void parse(const mixer::EventBuffer& e, bool audible) const;
- virtual void stopInputRec(int globalFrame) {};
+ /* advance
+ Processes static events (e.g. actions) in the current block. */
- /* receiveMidi
- Receives and processes midi messages from external devices. */
+ void advance(Frame bufferSize) const;
- virtual void receiveMidi(const MidiEvent& midiEvent) {};
+ /* render
+ Renders audio data to I/O buffers. */
+
+ void render(AudioBuffer* out, AudioBuffer* in, bool audible) const;
- /* calcPanning
- Given an audio channel (stereo: 0 or 1) computes the current panning value. */
+ bool isInternal() const;
+ bool isMuted() const;
+ bool canInputRec() const;
+ bool canActionRec() const;
+ bool hasWave() const;
+ ID getColumnId() const;
+ ChannelType getType() const;
+
+ ID id;
- 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..bbf3bce 100644
--- a/src/core/channels/channelManager.cpp
+++ b/src/core/channels/channelManager.cpp
@@ -27,19 +27,18 @@
#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"
#include "core/wave.h"
#include "core/waveManager.h"
-#include "core/pluginHost.h"
-#include "core/pluginManager.h"
-#include "core/plugin.h"
+#include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
+#include "core/plugins/plugin.h"
#include "core/action.h"
#include "core/recorderHandler.h"
#include "channelManager.h"
@@ -69,24 +68,11 @@ void init()
/* -------------------------------------------------------------------------- */
-std::unique_ptr create(ChannelType type, int bufferSize,
- bool inputMonitorOn, ID columnId)
+std::unique_ptr create(ChannelType type, ID columnId, const conf::Conf& conf)
{
- 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
+ std::unique_ptr ch = std::make_unique(type,
+ channelId_.get(), columnId, kernelAudio::getRealBufSize(), conf);
- assert(ch != nullptr);
return ch;
}
@@ -96,22 +82,10 @@ 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);
+ ID id = channelId_.get();
+ ch->id = id;
+ ch->state->id = id;
return ch;
}
@@ -121,19 +95,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,60 +107,58 @@ 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.getValue();
+ pc.midiInKeyRel = c.midiLearner.state->keyRelease.getValue();
+ pc.midiInKill = c.midiLearner.state->kill.getValue();
+ pc.midiInArm = c.midiLearner.state->arm.getValue();
+ pc.midiInVolume = c.midiLearner.state->volume.getValue();
+ pc.midiInMute = c.midiLearner.state->mute.getValue();
+ pc.midiInSolo = c.midiLearner.state->solo.getValue();
+ pc.midiInReadActions = c.midiLearner.state->readActions.getValue();
+ pc.midiInPitch = c.midiLearner.state->pitch.getValue();
+ pc.midiOutL = c.midiLighter.state->enabled.load();
+ pc.midiOutLplaying = c.midiLighter.state->playing.getValue();
+ pc.midiOutLmute = c.midiLighter.state->mute.getValue();
+ pc.midiOutLsolo = c.midiLighter.state->solo.getValue();
+
+ 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();
+ pc.overdubProtection = c.audioReceiver->state->overdubProtection.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;
}
-}}}; // giada::m::channelManager
+}}} // giada::m::channelManager
diff --git a/src/core/channels/channelManager.h b/src/core/channels/channelManager.h
index 4229a04..d7eb6f4 100644
--- a/src/core/channels/channelManager.h
+++ b/src/core/channels/channelManager.h
@@ -36,13 +36,16 @@
namespace giada {
namespace m
{
+namespace conf
+{
+struct Conf;
+}
namespace patch
{
struct Channel;
}
-class Channel;
-class SampleChannel;
-class MidiChannel;
+class Channel;
+struct ChannelState;
namespace channelManager
{
/* init
@@ -53,8 +56,7 @@ void init();
/* create (1)
Creates a new Channel from scratch. */
-std::unique_ptr create(ChannelType type, int bufferSize,
- bool inputMonitorOn, ID columnId);
+std::unique_ptr create(ChannelType type, ID columnId, const conf::Conf& conf);
/* create (2)
Creates a new Channel given an existing one (i.e. clone). */
@@ -66,7 +68,7 @@ Creates a new Channel given the patch raw data and vice versa. */
std::unique_ptr deserializeChannel(const patch::Channel& c, int bufferSize);
const patch::Channel serializeChannel(const Channel& c);
-}}}; // giada::m::channelManager
+}}} // giada::m::channelManager
#endif
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..7d27433
--- /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..55dcd03
--- /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..a82a9c0
--- /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.getValue();
+ uint32_t l_mute = state->mute.getValue();
+ uint32_t l_solo = state->solo.getValue();
+
+ 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..2afa74a
--- /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/plugins/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..a546556
--- /dev/null
+++ b/src/core/channels/sampleActionRecorder.cpp
@@ -0,0 +1,253 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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;
+
+ case mixer::EventType::CHANNEL_KILL_READ_ACTIONS:
+ killReadActions(); 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::killReadActions() const
+{
+ /* Killing Read Actions, i.e. shift + click on 'R' button is meaninful only
+ when the conf::treatRecsAsLoops is true. */
+
+ if (!conf::conf.treatRecsAsLoops)
+ return;
+ m_channelState->recStatus.store(ChannelStatus::OFF);
+ m_channelState->readActions.store(false);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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..b6bf451
--- /dev/null
+++ b/src/core/channels/sampleActionRecorder.h
@@ -0,0 +1,77 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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;
+ void killReadActions() 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..d6055b2
--- /dev/null
+++ b/src/core/channels/sampleController.cpp
@@ -0,0 +1,414 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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)
+ 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 ||
+ mode == SamplePlayerMode::LOOP_ONCE_BAR))
+ 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..1fc5723
--- /dev/null
+++ b/src/core/channels/samplePlayer.cpp
@@ -0,0 +1,257 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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;
+
+ 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 << ")" <<
+ ", tracker=" << tracker <<
+ ", 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, float samplerateRatio)
+{
+ m_waveReader.wave = &w;
+ m_waveId = w.id;
+
+ if (samplerateRatio != 1.0f) {
+ Frame begin = state->begin.load();
+ Frame end = state->end.load();
+ Frame shift = state->shift.load();
+ state->begin.store(begin * samplerateRatio);
+ state->end.store(end * samplerateRatio);
+ state->shift.store(shift * samplerateRatio);
+ }
+}
+
+
+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..2ddfbc5
--- /dev/null
+++ b/src/core/channels/samplePlayer.h
@@ -0,0 +1,117 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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. The
+ ratio is used to adjust begin/end points in case of patch vs. conf sample
+ rate mismatch. */
+
+ void setWave(const Wave& w, float samplerateRatio);
+
+ /* 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..b3dbd0e
--- /dev/null
+++ b/src/core/channels/state.cpp
@@ -0,0 +1,318 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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/conf.h"
+#include "core/patch.h"
+#include "state.h"
+
+
+namespace giada {
+namespace m
+{
+MidiLearnerState::MidiLearnerState()
+: enabled (true)
+, filter (0)
+{
+}
+
+
+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)
+, keyRelease (o.keyRelease)
+, kill (o.kill)
+, arm (o.arm)
+, volume (o.volume)
+, mute (o.mute)
+, solo (o.solo)
+, readActions (o.readActions)
+, pitch (o.pitch)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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)
+{
+}
+
+
+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)
+, mute (o.mute)
+, solo (o.solo)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
+
+
+MidiSenderState::MidiSenderState()
+: enabled(false)
+, 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(const conf::Conf& c)
+: inputMonitor (c.inputMonitorDefaultOn)
+, overdubProtection(c.overdubProtectionDefaultOn)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const patch::Channel& p)
+: inputMonitor (p.inputMonitor)
+, overdubProtection(p.overdubProtection)
+{
+}
+
+
+AudioReceiverState::AudioReceiverState(const AudioReceiverState& o)
+: inputMonitor (o.inputMonitor.load())
+, overdubProtection(o.overdubProtection.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..9e69e2c
--- /dev/null
+++ b/src/core/channels/state.h
@@ -0,0 +1,237 @@
+/* -----------------------------------------------------------------------------
+ *
+ * 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"
+#include "core/midiLearnParam.h"
+#ifdef WITH_VST
+#include "deps/juce-config.h"
+#endif
+
+
+namespace giada {
+namespace m {
+namespace conf
+{
+struct Conf;
+}
+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. */
+
+ MidiLearnParam keyPress;
+ MidiLearnParam keyRelease;
+ MidiLearnParam kill;
+ MidiLearnParam arm;
+ MidiLearnParam volume;
+ MidiLearnParam mute;
+ MidiLearnParam solo;
+ MidiLearnParam readActions; // Sample Channels only
+ MidiLearnParam 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. */
+
+ MidiLearnParam playing;
+ MidiLearnParam mute;
+ MidiLearnParam 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(const conf::Conf& c);
+ AudioReceiverState(const patch::Channel& p);
+ AudioReceiverState(const AudioReceiverState& o);
+
+ std::atomic inputMonitor;
+ std::atomic overdubProtection;
+};
+
+
+/* -------------------------------------------------------------------------- */
+
+
+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..9f57174
--- /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, G_MAX_IO_CHANS, 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..2dd28ef 100644
--- a/src/core/clock.cpp
+++ b/src/core/clock.cpp
@@ -28,19 +28,19 @@
#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"
-namespace giada {
-namespace m {
-namespace clock
+namespace giada::m::clock
{
namespace
{
@@ -48,7 +48,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,8 +62,7 @@ int midiTCseconds_ = 0;
int midiTCminutes_ = 0;
int midiTChours_ = 0;
-
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+#ifdef WITH_AUDIO_JACK
kernelAudio::JackState jackStatePrev_;
#endif
@@ -69,15 +74,15 @@ Updates bpm, frames, beats and so on. Private version. */
void recomputeFrames_(model::Clock& c)
{
- c.framesInLoop = (conf::conf.samplerate * (60.0f / c.bpm)) * c.beats;
- c.framesInBar = c.framesInLoop / (float) c.bars;
- c.framesInBeat = c.framesInLoop / (float) c.beats;
+ c.framesInLoop = static_cast((conf::conf.samplerate * (60.0f / c.bpm)) * c.beats);
+ c.framesInBar = static_cast(c.framesInLoop / (float) c.bars);
+ c.framesInBeat = static_cast(c.framesInLoop / (float) c.beats);
c.framesInSeq = c.framesInBeat * G_MAX_BEATS;
if (c.quantize != 0)
- quanto_ = c.framesInBeat / c.quantize;
+ quantizerStep_ = c.framesInBeat / c.quantize;
}
-}; // {anonymous}
+} // {anonymous}
/* -------------------------------------------------------------------------- */
@@ -87,7 +92,7 @@ void recomputeFrames_(model::Clock& c)
void init(int sampleRate, float midiTCfps)
{
- midiTCrate_ = (sampleRate / midiTCfps) * G_MAX_IO_CHANS; // stereo values
+ midiTCrate_ = static_cast((sampleRate / midiTCfps) * G_MAX_IO_CHANS); // stereo values
model::onSwap(model::clock, [&](model::Clock& c)
{
@@ -131,7 +136,7 @@ bool isActive()
bool quantoHasPassed()
{
- return currentFrame_.load() % quanto_ == 0;
+ return clock::getQuantizerValue() != 0 && currentFrame_.load() % quantizerStep_ == 0;
}
@@ -172,16 +177,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 +189,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)
{
@@ -247,8 +243,7 @@ void incrCurrentFrame()
if (c->status == ClockStatus::WAITING) {
int f = currentFrameWait_.load() + 1;
- if (f >= c->framesInLoop)
- f = 0;
+ f %= c->framesInLoop;
currentFrameWait_.store(f);
return;
}
@@ -256,13 +251,8 @@ void incrCurrentFrame()
int f = currentFrame_.load() + 1;
int b = currentBeat_.load();
- if (f >= c->framesInLoop) {
- f = 0;
- b = 0;
- }
- else
- if (f % c->framesInBeat == 0) // If is on beat
- b++;
+ f %= c->framesInLoop;
+ b = f / c->framesInBeat;
currentFrame_.store(f);
currentBeat_.store(b);
@@ -380,39 +370,43 @@ 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);
}
/* -------------------------------------------------------------------------- */
-#if defined(G_OS_LINUX) || defined(G_OS_FREEBSD)
+#ifdef WITH_AUDIO_JACK
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 (jackState.running != jackStatePrev_.running) {
- if (jackState.running) {
- if (!isRunning())
- mh::startSequencer();
+ if (jackStateCurr != jackStatePrev_) {
+
+ 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 +420,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 +427,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; }
-}}}; // giada::m::clock::
+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..92d1d1b 100644
--- a/src/core/clock.h
+++ b/src/core/clock.h
@@ -32,9 +32,7 @@
#include "types.h"
-namespace giada {
-namespace m {
-namespace clock
+namespace giada::m::clock
{
void init(int sampleRate, float midiTCfps);
@@ -53,7 +51,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 +64,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);
@@ -106,7 +109,7 @@ bool isOnFirstBeat();
void rewind();
void setStatus(ClockStatus s);
-}}}; // giada::m::clock::
+} // giada::m::clock::
#endif
diff --git a/src/core/conf.cpp b/src/core/conf.cpp
index 7d8b000..769c936 100644
--- a/src/core/conf.cpp
+++ b/src/core/conf.cpp
@@ -53,6 +53,16 @@ std::string confDirPath_ = "";
/* -------------------------------------------------------------------------- */
+void sanitize_()
+{
+ conf.soundDeviceOut = std::max(0, conf.soundDeviceOut);
+ conf.channelsOut = std::max(0, conf.channelsOut);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
/* createConfigFolder
Creates local folder where to put the configuration file. Path differs from OS
to OS. */
@@ -81,8 +91,7 @@ int createConfigFolder_()
#endif
}
-
-}; // {anonymous}
+} // {anonymous}
/* -------------------------------------------------------------------------- */
@@ -130,86 +139,89 @@ bool read()
nl::json j = nl::json::parse(ifs);
- conf.logMode = j.value(CONF_KEY_LOG_MODE, conf.logMode);
- conf.soundSystem = j.value(CONF_KEY_SOUND_SYSTEM, conf.soundSystem);
- conf.soundDeviceOut = j.value(CONF_KEY_SOUND_DEVICE_OUT, conf.soundDeviceOut);
- conf.soundDeviceIn = j.value(CONF_KEY_SOUND_DEVICE_IN, conf.soundDeviceIn);
- conf.channelsOut = j.value(CONF_KEY_CHANNELS_OUT, conf.channelsOut);
- conf.channelsIn = j.value(CONF_KEY_CHANNELS_IN, conf.channelsIn);
- conf.samplerate = j.value(CONF_KEY_SAMPLERATE, conf.samplerate);
- conf.buffersize = j.value(CONF_KEY_BUFFER_SIZE, conf.buffersize);
- conf.limitOutput = j.value(CONF_KEY_LIMIT_OUTPUT, conf.limitOutput);
- conf.rsmpQuality = j.value(CONF_KEY_RESAMPLE_QUALITY, conf.rsmpQuality);
- conf.midiSystem = j.value(CONF_KEY_MIDI_SYSTEM, conf.midiSystem);
- conf.midiPortOut = j.value(CONF_KEY_MIDI_PORT_OUT, conf.midiPortOut);
- conf.midiPortIn = j.value(CONF_KEY_MIDI_PORT_IN, conf.midiPortIn);
- conf.midiMapPath = j.value(CONF_KEY_MIDIMAP_PATH, conf.midiMapPath);
- conf.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap);
- conf.midiSync = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync);
- conf.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps);
- conf.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);
- conf.pluginPath = j.value(CONF_KEY_PLUGINS_PATH, conf.pluginPath);
- conf.patchPath = j.value(CONF_KEY_PATCHES_PATH, conf.patchPath);
- conf.samplePath = j.value(CONF_KEY_SAMPLES_PATH, conf.samplePath);
- conf.mainWindowX = j.value(CONF_KEY_MAIN_WINDOW_X, conf.mainWindowX);
- conf.mainWindowY = j.value(CONF_KEY_MAIN_WINDOW_Y, conf.mainWindowY);
- conf.mainWindowW = j.value(CONF_KEY_MAIN_WINDOW_W, conf.mainWindowW);
- conf.mainWindowH = j.value(CONF_KEY_MAIN_WINDOW_H, conf.mainWindowH);
- conf.browserX = j.value(CONF_KEY_BROWSER_X, conf.browserX);
- conf.browserY = j.value(CONF_KEY_BROWSER_Y, conf.browserY);
- conf.browserW = j.value(CONF_KEY_BROWSER_W, conf.browserW);
- conf.browserH = j.value(CONF_KEY_BROWSER_H, conf.browserH);
- conf.browserPosition = j.value(CONF_KEY_BROWSER_POSITION, conf.browserPosition);
- conf.browserLastPath = j.value(CONF_KEY_BROWSER_LAST_PATH, conf.browserLastPath);
- conf.browserLastValue = j.value(CONF_KEY_BROWSER_LAST_VALUE, conf.browserLastValue);
- conf.actionEditorX = j.value(CONF_KEY_ACTION_EDITOR_X, conf.actionEditorX);
- conf.actionEditorY = j.value(CONF_KEY_ACTION_EDITOR_Y, conf.actionEditorY);
- conf.actionEditorW = j.value(CONF_KEY_ACTION_EDITOR_W, conf.actionEditorW);
- conf.actionEditorH = j.value(CONF_KEY_ACTION_EDITOR_H, conf.actionEditorH);
- conf.actionEditorZoom = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, conf.actionEditorZoom);
- conf.actionEditorGridVal = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, conf.actionEditorGridVal);
- conf.actionEditorGridOn = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, conf.actionEditorGridOn);
- conf.sampleEditorX = j.value(CONF_KEY_SAMPLE_EDITOR_X, conf.sampleEditorX);
- conf.sampleEditorY = j.value(CONF_KEY_SAMPLE_EDITOR_Y, conf.sampleEditorY);
- conf.sampleEditorW = j.value(CONF_KEY_SAMPLE_EDITOR_W, conf.sampleEditorW);
- conf.sampleEditorH = j.value(CONF_KEY_SAMPLE_EDITOR_H, conf.sampleEditorH);
- conf.sampleEditorGridVal = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, conf.sampleEditorGridVal);
- conf.sampleEditorGridOn = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, conf.sampleEditorGridOn);
- conf.pianoRollY = j.value(CONF_KEY_PIANO_ROLL_Y, conf.pianoRollY);
- conf.pianoRollH = j.value(CONF_KEY_PIANO_ROLL_H, conf.pianoRollH);
- conf.sampleActionEditorH = j.value(CONF_KEY_SAMPLE_ACTION_EDITOR_H, conf.sampleActionEditorH);
- conf.velocityEditorH = j.value(CONF_KEY_VELOCITY_EDITOR_H, conf.velocityEditorH);
- conf.envelopeEditorH = j.value(CONF_KEY_ENVELOPE_EDITOR_H, conf.envelopeEditorH);
- conf.pluginListX = j.value(CONF_KEY_PLUGIN_LIST_X, conf.pluginListX);
- conf.pluginListY = j.value(CONF_KEY_PLUGIN_LIST_Y, conf.pluginListY);
- conf.midiInputX = j.value(CONF_KEY_MIDI_INPUT_X, conf.midiInputX);
- conf.midiInputY = j.value(CONF_KEY_MIDI_INPUT_Y, conf.midiInputY);
- conf.midiInputW = j.value(CONF_KEY_MIDI_INPUT_W, conf.midiInputW);
- conf.midiInputH = j.value(CONF_KEY_MIDI_INPUT_H, conf.midiInputH);
- conf.recTriggerMode = j.value(CONF_KEY_REC_TRIGGER_MODE, conf.recTriggerMode);
- conf.recTriggerLevel = j.value(CONF_KEY_REC_TRIGGER_LEVEL, conf.recTriggerLevel);
- conf.midiInEnabled = j.value(CONF_KEY_MIDI_IN, conf.midiInEnabled);
- conf.midiInFilter = j.value(CONF_KEY_MIDI_IN_FILTER, conf.midiInFilter);
- conf.midiInRewind = j.value(CONF_KEY_MIDI_IN_REWIND, conf.midiInRewind);
- conf.midiInStartStop = j.value(CONF_KEY_MIDI_IN_START_STOP, conf.midiInStartStop);
- conf.midiInActionRec = j.value(CONF_KEY_MIDI_IN_ACTION_REC, conf.midiInActionRec);
- conf.midiInInputRec = j.value(CONF_KEY_MIDI_IN_INPUT_REC, conf.midiInInputRec);
- conf.midiInMetronome = j.value(CONF_KEY_MIDI_IN_METRONOME, conf.midiInMetronome);
- conf.midiInVolumeIn = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, conf.midiInVolumeIn);
- conf.midiInVolumeOut = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, conf.midiInVolumeOut);
- conf.midiInBeatDouble = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, conf.midiInBeatDouble);
- conf.midiInBeatHalf = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, conf.midiInBeatHalf);
+ conf.logMode = j.value(CONF_KEY_LOG_MODE, conf.logMode);
+ conf.soundSystem = j.value(CONF_KEY_SOUND_SYSTEM, conf.soundSystem);
+ conf.soundDeviceOut = j.value(CONF_KEY_SOUND_DEVICE_OUT, conf.soundDeviceOut);
+ conf.soundDeviceIn = j.value(CONF_KEY_SOUND_DEVICE_IN, conf.soundDeviceIn);
+ conf.channelsOut = j.value(CONF_KEY_CHANNELS_OUT, conf.channelsOut);
+ conf.channelsInCount = j.value(CONF_KEY_CHANNELS_IN_COUNT, conf.channelsInCount);
+ conf.channelsInStart = j.value(CONF_KEY_CHANNELS_IN_START, conf.channelsInStart);
+ conf.samplerate = j.value(CONF_KEY_SAMPLERATE, conf.samplerate);
+ conf.buffersize = j.value(CONF_KEY_BUFFER_SIZE, conf.buffersize);
+ conf.limitOutput = j.value(CONF_KEY_LIMIT_OUTPUT, conf.limitOutput);
+ conf.rsmpQuality = j.value(CONF_KEY_RESAMPLE_QUALITY, conf.rsmpQuality);
+ conf.midiSystem = j.value(CONF_KEY_MIDI_SYSTEM, conf.midiSystem);
+ conf.midiPortOut = j.value(CONF_KEY_MIDI_PORT_OUT, conf.midiPortOut);
+ conf.midiPortIn = j.value(CONF_KEY_MIDI_PORT_IN, conf.midiPortIn);
+ conf.midiMapPath = j.value(CONF_KEY_MIDIMAP_PATH, conf.midiMapPath);
+ conf.lastFileMap = j.value(CONF_KEY_LAST_MIDIMAP, conf.lastFileMap);
+ conf.midiSync = j.value(CONF_KEY_MIDI_SYNC, conf.midiSync);
+ conf.midiTCfps = j.value(CONF_KEY_MIDI_TC_FPS, conf.midiTCfps);
+ conf.chansStopOnSeqHalt = j.value(CONF_KEY_CHANS_STOP_ON_SEQ_HALT, conf.chansStopOnSeqHalt);
+ conf.treatRecsAsLoops = j.value(CONF_KEY_TREAT_RECS_AS_LOOPS, conf.treatRecsAsLoops);
+ conf.inputMonitorDefaultOn = j.value(CONF_KEY_INPUT_MONITOR_DEFAULT_ON, conf.inputMonitorDefaultOn);
+ conf.overdubProtectionDefaultOn = j.value(CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON, conf.overdubProtectionDefaultOn);
+ conf.pluginPath = j.value(CONF_KEY_PLUGINS_PATH, conf.pluginPath);
+ conf.patchPath = j.value(CONF_KEY_PATCHES_PATH, conf.patchPath);
+ conf.samplePath = j.value(CONF_KEY_SAMPLES_PATH, conf.samplePath);
+ conf.mainWindowX = j.value(CONF_KEY_MAIN_WINDOW_X, conf.mainWindowX);
+ conf.mainWindowY = j.value(CONF_KEY_MAIN_WINDOW_Y, conf.mainWindowY);
+ conf.mainWindowW = j.value(CONF_KEY_MAIN_WINDOW_W, conf.mainWindowW);
+ conf.mainWindowH = j.value(CONF_KEY_MAIN_WINDOW_H, conf.mainWindowH);
+ conf.browserX = j.value(CONF_KEY_BROWSER_X, conf.browserX);
+ conf.browserY = j.value(CONF_KEY_BROWSER_Y, conf.browserY);
+ conf.browserW = j.value(CONF_KEY_BROWSER_W, conf.browserW);
+ conf.browserH = j.value(CONF_KEY_BROWSER_H, conf.browserH);
+ conf.browserPosition = j.value(CONF_KEY_BROWSER_POSITION, conf.browserPosition);
+ conf.browserLastPath = j.value(CONF_KEY_BROWSER_LAST_PATH, conf.browserLastPath);
+ conf.browserLastValue = j.value(CONF_KEY_BROWSER_LAST_VALUE, conf.browserLastValue);
+ conf.actionEditorX = j.value(CONF_KEY_ACTION_EDITOR_X, conf.actionEditorX);
+ conf.actionEditorY = j.value(CONF_KEY_ACTION_EDITOR_Y, conf.actionEditorY);
+ conf.actionEditorW = j.value(CONF_KEY_ACTION_EDITOR_W, conf.actionEditorW);
+ conf.actionEditorH = j.value(CONF_KEY_ACTION_EDITOR_H, conf.actionEditorH);
+ conf.actionEditorZoom = j.value(CONF_KEY_ACTION_EDITOR_ZOOM, conf.actionEditorZoom);
+ conf.actionEditorGridVal = j.value(CONF_KEY_ACTION_EDITOR_GRID_VAL, conf.actionEditorGridVal);
+ conf.actionEditorGridOn = j.value(CONF_KEY_ACTION_EDITOR_GRID_ON, conf.actionEditorGridOn);
+ conf.sampleEditorX = j.value(CONF_KEY_SAMPLE_EDITOR_X, conf.sampleEditorX);
+ conf.sampleEditorY = j.value(CONF_KEY_SAMPLE_EDITOR_Y, conf.sampleEditorY);
+ conf.sampleEditorW = j.value(CONF_KEY_SAMPLE_EDITOR_W, conf.sampleEditorW);
+ conf.sampleEditorH = j.value(CONF_KEY_SAMPLE_EDITOR_H, conf.sampleEditorH);
+ conf.sampleEditorGridVal = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_VAL, conf.sampleEditorGridVal);
+ conf.sampleEditorGridOn = j.value(CONF_KEY_SAMPLE_EDITOR_GRID_ON, conf.sampleEditorGridOn);
+ conf.pianoRollY = j.value(CONF_KEY_PIANO_ROLL_Y, conf.pianoRollY);
+ conf.pianoRollH = j.value(CONF_KEY_PIANO_ROLL_H, conf.pianoRollH);
+ conf.sampleActionEditorH = j.value(CONF_KEY_SAMPLE_ACTION_EDITOR_H, conf.sampleActionEditorH);
+ conf.velocityEditorH = j.value(CONF_KEY_VELOCITY_EDITOR_H, conf.velocityEditorH);
+ conf.envelopeEditorH = j.value(CONF_KEY_ENVELOPE_EDITOR_H, conf.envelopeEditorH);
+ conf.pluginListX = j.value(CONF_KEY_PLUGIN_LIST_X, conf.pluginListX);
+ conf.pluginListY = j.value(CONF_KEY_PLUGIN_LIST_Y, conf.pluginListY);
+ conf.midiInputX = j.value(CONF_KEY_MIDI_INPUT_X, conf.midiInputX);
+ conf.midiInputY = j.value(CONF_KEY_MIDI_INPUT_Y, conf.midiInputY);
+ conf.midiInputW = j.value(CONF_KEY_MIDI_INPUT_W, conf.midiInputW);
+ conf.midiInputH = j.value(CONF_KEY_MIDI_INPUT_H, conf.midiInputH);
+ conf.recTriggerMode = j.value(CONF_KEY_REC_TRIGGER_MODE, conf.recTriggerMode);
+ conf.recTriggerLevel = j.value(CONF_KEY_REC_TRIGGER_LEVEL, conf.recTriggerLevel);
+ conf.midiInEnabled = j.value(CONF_KEY_MIDI_IN, conf.midiInEnabled);
+ conf.midiInFilter = j.value(CONF_KEY_MIDI_IN_FILTER, conf.midiInFilter);
+ conf.midiInRewind = j.value(CONF_KEY_MIDI_IN_REWIND, conf.midiInRewind);
+ conf.midiInStartStop = j.value(CONF_KEY_MIDI_IN_START_STOP, conf.midiInStartStop);
+ conf.midiInActionRec = j.value(CONF_KEY_MIDI_IN_ACTION_REC, conf.midiInActionRec);
+ conf.midiInInputRec = j.value(CONF_KEY_MIDI_IN_INPUT_REC, conf.midiInInputRec);
+ conf.midiInMetronome = j.value(CONF_KEY_MIDI_IN_METRONOME, conf.midiInMetronome);
+ conf.midiInVolumeIn = j.value(CONF_KEY_MIDI_IN_VOLUME_IN, conf.midiInVolumeIn);
+ conf.midiInVolumeOut = j.value(CONF_KEY_MIDI_IN_VOLUME_OUT, conf.midiInVolumeOut);
+ conf.midiInBeatDouble = j.value(CONF_KEY_MIDI_IN_BEAT_DOUBLE, conf.midiInBeatDouble);
+ conf.midiInBeatHalf = j.value(CONF_KEY_MIDI_IN_BEAT_HALF, conf.midiInBeatHalf);
#ifdef WITH_VST
- conf.pluginChooserX = j.value(CONF_KEY_PLUGIN_CHOOSER_X, conf.pluginChooserX);
- conf.pluginChooserY = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, conf.pluginChooserY);
- conf.pluginChooserW = j.value(CONF_KEY_PLUGIN_CHOOSER_W, conf.pluginChooserW);
- conf.pluginChooserH = j.value(CONF_KEY_PLUGIN_CHOOSER_H, conf.pluginChooserH);
- conf.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, conf.pluginSortMethod);
+ conf.pluginChooserX = j.value(CONF_KEY_PLUGIN_CHOOSER_X, conf.pluginChooserX);
+ conf.pluginChooserY = j.value(CONF_KEY_PLUGIN_CHOOSER_Y, conf.pluginChooserY);
+ conf.pluginChooserW = j.value(CONF_KEY_PLUGIN_CHOOSER_W, conf.pluginChooserW);
+ conf.pluginChooserH = j.value(CONF_KEY_PLUGIN_CHOOSER_H, conf.pluginChooserH);
+ conf.pluginSortMethod = j.value(CONF_KEY_PLUGIN_SORT_METHOD, conf.pluginSortMethod);
#endif
+ sanitize_();
+
return true;
}
@@ -224,85 +236,86 @@ bool write()
nl::json j;
- j[CONF_KEY_HEADER] = "GIADACFG";
- j[CONF_KEY_LOG_MODE] = conf.logMode;
- j[CONF_KEY_SOUND_SYSTEM] = conf.soundSystem;
- j[CONF_KEY_SOUND_DEVICE_OUT] = conf.soundDeviceOut;
- j[CONF_KEY_SOUND_DEVICE_IN] = conf.soundDeviceIn;
- j[CONF_KEY_CHANNELS_OUT] = conf.channelsOut;
- j[CONF_KEY_CHANNELS_IN] = conf.channelsIn;
- j[CONF_KEY_SAMPLERATE] = conf.samplerate;
- j[CONF_KEY_BUFFER_SIZE] = conf.buffersize;
- j[CONF_KEY_LIMIT_OUTPUT] = conf.limitOutput;
- j[CONF_KEY_RESAMPLE_QUALITY] = conf.rsmpQuality;
- j[CONF_KEY_MIDI_SYSTEM] = conf.midiSystem;
- j[CONF_KEY_MIDI_PORT_OUT] = conf.midiPortOut;
- j[CONF_KEY_MIDI_PORT_IN] = conf.midiPortIn;
- j[CONF_KEY_MIDIMAP_PATH] = conf.midiMapPath;
- j[CONF_KEY_LAST_MIDIMAP] = conf.lastFileMap;
- j[CONF_KEY_MIDI_SYNC] = conf.midiSync;
- j[CONF_KEY_MIDI_TC_FPS] = conf.midiTCfps;
- j[CONF_KEY_MIDI_IN] = conf.midiInEnabled;
- j[CONF_KEY_MIDI_IN_FILTER] = conf.midiInFilter;
- j[CONF_KEY_MIDI_IN_REWIND] = conf.midiInRewind;
- j[CONF_KEY_MIDI_IN_START_STOP] = conf.midiInStartStop;
- j[CONF_KEY_MIDI_IN_ACTION_REC] = conf.midiInActionRec;
- j[CONF_KEY_MIDI_IN_INPUT_REC] = conf.midiInInputRec;
- j[CONF_KEY_MIDI_IN_METRONOME] = conf.midiInMetronome;
- j[CONF_KEY_MIDI_IN_VOLUME_IN] = conf.midiInVolumeIn;
- j[CONF_KEY_MIDI_IN_VOLUME_OUT] = conf.midiInVolumeOut;
- j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = conf.midiInBeatDouble;
- j[CONF_KEY_MIDI_IN_BEAT_HALF] = conf.midiInBeatHalf;
- j[CONF_KEY_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;
- j[CONF_KEY_PLUGINS_PATH] = conf.pluginPath;
- j[CONF_KEY_PATCHES_PATH] = conf.patchPath;
- j[CONF_KEY_SAMPLES_PATH] = conf.samplePath;
- j[CONF_KEY_MAIN_WINDOW_X] = conf.mainWindowX;
- j[CONF_KEY_MAIN_WINDOW_Y] = conf.mainWindowY;
- j[CONF_KEY_MAIN_WINDOW_W] = conf.mainWindowW;
- j[CONF_KEY_MAIN_WINDOW_H] = conf.mainWindowH;
- j[CONF_KEY_BROWSER_X] = conf.browserX;
- j[CONF_KEY_BROWSER_Y] = conf.browserY;
- j[CONF_KEY_BROWSER_W] = conf.browserW;
- j[CONF_KEY_BROWSER_H] = conf.browserH;
- j[CONF_KEY_BROWSER_POSITION] = conf.browserPosition;
- j[CONF_KEY_BROWSER_LAST_PATH] = conf.browserLastPath;
- j[CONF_KEY_BROWSER_LAST_VALUE] = conf.browserLastValue;
- j[CONF_KEY_ACTION_EDITOR_X] = conf.actionEditorX;
- j[CONF_KEY_ACTION_EDITOR_Y] = conf.actionEditorY;
- j[CONF_KEY_ACTION_EDITOR_W] = conf.actionEditorW;
- j[CONF_KEY_ACTION_EDITOR_H] = conf.actionEditorH;
- j[CONF_KEY_ACTION_EDITOR_ZOOM] = conf.actionEditorZoom;
- j[CONF_KEY_ACTION_EDITOR_GRID_VAL] = conf.actionEditorGridVal;
- j[CONF_KEY_ACTION_EDITOR_GRID_ON] = conf.actionEditorGridOn;
- j[CONF_KEY_SAMPLE_EDITOR_X] = conf.sampleEditorX;
- j[CONF_KEY_SAMPLE_EDITOR_Y] = conf.sampleEditorY;
- j[CONF_KEY_SAMPLE_EDITOR_W] = conf.sampleEditorW;
- j[CONF_KEY_SAMPLE_EDITOR_H] = conf.sampleEditorH;
- j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL] = conf.sampleEditorGridVal;
- j[CONF_KEY_SAMPLE_EDITOR_GRID_ON] = conf.sampleEditorGridOn;
- j[CONF_KEY_PIANO_ROLL_Y] = conf.pianoRollY;
- j[CONF_KEY_PIANO_ROLL_H] = conf.pianoRollH;
- j[CONF_KEY_SAMPLE_ACTION_EDITOR_H] = conf.sampleActionEditorH;
- j[CONF_KEY_VELOCITY_EDITOR_H] = conf.velocityEditorH;
- j[CONF_KEY_ENVELOPE_EDITOR_H] = conf.envelopeEditorH;
- j[CONF_KEY_PLUGIN_LIST_X] = conf.pluginListX;
- j[CONF_KEY_PLUGIN_LIST_Y] = conf.pluginListY;
- j[CONF_KEY_MIDI_INPUT_X] = conf.midiInputX;
- j[CONF_KEY_MIDI_INPUT_Y] = conf.midiInputY;
- j[CONF_KEY_MIDI_INPUT_W] = conf.midiInputW;
- j[CONF_KEY_MIDI_INPUT_H] = conf.midiInputH;
- j[CONF_KEY_REC_TRIGGER_MODE] = static_cast(conf.recTriggerMode);
- j[CONF_KEY_REC_TRIGGER_LEVEL] = conf.recTriggerLevel;
+ j[CONF_KEY_HEADER] = "GIADACFG";
+ j[CONF_KEY_LOG_MODE] = conf.logMode;
+ j[CONF_KEY_SOUND_SYSTEM] = conf.soundSystem;
+ j[CONF_KEY_SOUND_DEVICE_OUT] = conf.soundDeviceOut;
+ j[CONF_KEY_SOUND_DEVICE_IN] = conf.soundDeviceIn;
+ j[CONF_KEY_CHANNELS_OUT] = conf.channelsOut;
+ j[CONF_KEY_CHANNELS_IN_COUNT] = conf.channelsInCount;
+ j[CONF_KEY_CHANNELS_IN_START] = conf.channelsInStart;
+ j[CONF_KEY_SAMPLERATE] = conf.samplerate;
+ j[CONF_KEY_BUFFER_SIZE] = conf.buffersize;
+ j[CONF_KEY_LIMIT_OUTPUT] = conf.limitOutput;
+ j[CONF_KEY_RESAMPLE_QUALITY] = conf.rsmpQuality;
+ j[CONF_KEY_MIDI_SYSTEM] = conf.midiSystem;
+ j[CONF_KEY_MIDI_PORT_OUT] = conf.midiPortOut;
+ j[CONF_KEY_MIDI_PORT_IN] = conf.midiPortIn;
+ j[CONF_KEY_MIDIMAP_PATH] = conf.midiMapPath;
+ j[CONF_KEY_LAST_MIDIMAP] = conf.lastFileMap;
+ j[CONF_KEY_MIDI_SYNC] = conf.midiSync;
+ j[CONF_KEY_MIDI_TC_FPS] = conf.midiTCfps;
+ j[CONF_KEY_MIDI_IN] = conf.midiInEnabled;
+ j[CONF_KEY_MIDI_IN_FILTER] = conf.midiInFilter;
+ j[CONF_KEY_MIDI_IN_REWIND] = conf.midiInRewind;
+ j[CONF_KEY_MIDI_IN_START_STOP] = conf.midiInStartStop;
+ j[CONF_KEY_MIDI_IN_ACTION_REC] = conf.midiInActionRec;
+ j[CONF_KEY_MIDI_IN_INPUT_REC] = conf.midiInInputRec;
+ j[CONF_KEY_MIDI_IN_METRONOME] = conf.midiInMetronome;
+ j[CONF_KEY_MIDI_IN_VOLUME_IN] = conf.midiInVolumeIn;
+ j[CONF_KEY_MIDI_IN_VOLUME_OUT] = conf.midiInVolumeOut;
+ j[CONF_KEY_MIDI_IN_BEAT_DOUBLE] = conf.midiInBeatDouble;
+ j[CONF_KEY_MIDI_IN_BEAT_HALF] = conf.midiInBeatHalf;
+ j[CONF_KEY_CHANS_STOP_ON_SEQ_HALT] = conf.chansStopOnSeqHalt;
+ j[CONF_KEY_TREAT_RECS_AS_LOOPS] = conf.treatRecsAsLoops;
+ j[CONF_KEY_INPUT_MONITOR_DEFAULT_ON] = conf.inputMonitorDefaultOn;
+ j[CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON] = conf.overdubProtectionDefaultOn;
+ j[CONF_KEY_PLUGINS_PATH] = conf.pluginPath;
+ j[CONF_KEY_PATCHES_PATH] = conf.patchPath;
+ j[CONF_KEY_SAMPLES_PATH] = conf.samplePath;
+ j[CONF_KEY_MAIN_WINDOW_X] = conf.mainWindowX;
+ j[CONF_KEY_MAIN_WINDOW_Y] = conf.mainWindowY;
+ j[CONF_KEY_MAIN_WINDOW_W] = conf.mainWindowW;
+ j[CONF_KEY_MAIN_WINDOW_H] = conf.mainWindowH;
+ j[CONF_KEY_BROWSER_X] = conf.browserX;
+ j[CONF_KEY_BROWSER_Y] = conf.browserY;
+ j[CONF_KEY_BROWSER_W] = conf.browserW;
+ j[CONF_KEY_BROWSER_H] = conf.browserH;
+ j[CONF_KEY_BROWSER_POSITION] = conf.browserPosition;
+ j[CONF_KEY_BROWSER_LAST_PATH] = conf.browserLastPath;
+ j[CONF_KEY_BROWSER_LAST_VALUE] = conf.browserLastValue;
+ j[CONF_KEY_ACTION_EDITOR_X] = conf.actionEditorX;
+ j[CONF_KEY_ACTION_EDITOR_Y] = conf.actionEditorY;
+ j[CONF_KEY_ACTION_EDITOR_W] = conf.actionEditorW;
+ j[CONF_KEY_ACTION_EDITOR_H] = conf.actionEditorH;
+ j[CONF_KEY_ACTION_EDITOR_ZOOM] = conf.actionEditorZoom;
+ j[CONF_KEY_ACTION_EDITOR_GRID_VAL] = conf.actionEditorGridVal;
+ j[CONF_KEY_ACTION_EDITOR_GRID_ON] = conf.actionEditorGridOn;
+ j[CONF_KEY_SAMPLE_EDITOR_X] = conf.sampleEditorX;
+ j[CONF_KEY_SAMPLE_EDITOR_Y] = conf.sampleEditorY;
+ j[CONF_KEY_SAMPLE_EDITOR_W] = conf.sampleEditorW;
+ j[CONF_KEY_SAMPLE_EDITOR_H] = conf.sampleEditorH;
+ j[CONF_KEY_SAMPLE_EDITOR_GRID_VAL] = conf.sampleEditorGridVal;
+ j[CONF_KEY_SAMPLE_EDITOR_GRID_ON] = conf.sampleEditorGridOn;
+ j[CONF_KEY_PIANO_ROLL_Y] = conf.pianoRollY;
+ j[CONF_KEY_PIANO_ROLL_H] = conf.pianoRollH;
+ j[CONF_KEY_SAMPLE_ACTION_EDITOR_H] = conf.sampleActionEditorH;
+ j[CONF_KEY_VELOCITY_EDITOR_H] = conf.velocityEditorH;
+ j[CONF_KEY_ENVELOPE_EDITOR_H] = conf.envelopeEditorH;
+ j[CONF_KEY_PLUGIN_LIST_X] = conf.pluginListX;
+ j[CONF_KEY_PLUGIN_LIST_Y] = conf.pluginListY;
+ j[CONF_KEY_MIDI_INPUT_X] = conf.midiInputX;
+ j[CONF_KEY_MIDI_INPUT_Y] = conf.midiInputY;
+ j[CONF_KEY_MIDI_INPUT_W] = conf.midiInputW;
+ j[CONF_KEY_MIDI_INPUT_H] = conf.midiInputH;
+ j[CONF_KEY_REC_TRIGGER_MODE] = static_cast(conf.recTriggerMode);
+ j[CONF_KEY_REC_TRIGGER_LEVEL] = conf.recTriggerLevel;
#ifdef WITH_VST
- j[CONF_KEY_PLUGIN_CHOOSER_X] = conf.pluginChooserX;
- j[CONF_KEY_PLUGIN_CHOOSER_Y] = conf.pluginChooserY;
- j[CONF_KEY_PLUGIN_CHOOSER_W] = conf.pluginChooserW;
- j[CONF_KEY_PLUGIN_CHOOSER_H] = conf.pluginChooserH;
- j[CONF_KEY_PLUGIN_SORT_METHOD] = conf.pluginSortMethod;
+ j[CONF_KEY_PLUGIN_CHOOSER_X] = conf.pluginChooserX;
+ j[CONF_KEY_PLUGIN_CHOOSER_Y] = conf.pluginChooserY;
+ j[CONF_KEY_PLUGIN_CHOOSER_W] = conf.pluginChooserW;
+ j[CONF_KEY_PLUGIN_CHOOSER_H] = conf.pluginChooserH;
+ j[CONF_KEY_PLUGIN_SORT_METHOD] = conf.pluginSortMethod;
#endif
std::ofstream ofs(confFilePath_);
@@ -314,4 +327,4 @@ bool write()
ofs << j;
return true;
}
-}}}; // giada::m::conf::
\ No newline at end of file
+}}} // giada::m::conf::
\ No newline at end of file
diff --git a/src/core/conf.h b/src/core/conf.h
index c750c7b..91ca9d3 100644
--- a/src/core/conf.h
+++ b/src/core/conf.h
@@ -41,16 +41,17 @@ namespace conf
{
struct Conf
{
- int logMode = LOG_MODE_MUTE;
- int soundSystem = G_DEFAULT_SOUNDSYS;
- int soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT;
- int soundDeviceIn = G_DEFAULT_SOUNDDEV_IN;
- int channelsOut = 0;
- int channelsIn = 0;
- int samplerate = G_DEFAULT_SAMPLERATE;
- int buffersize = G_DEFAULT_BUFSIZE;
- bool limitOutput = false;
- int rsmpQuality = 0;
+ int logMode = LOG_MODE_MUTE;
+ int soundSystem = G_DEFAULT_SOUNDSYS;
+ int soundDeviceOut = G_DEFAULT_SOUNDDEV_OUT;
+ int soundDeviceIn = G_DEFAULT_SOUNDDEV_IN;
+ int channelsOut = 0;
+ int channelsInCount = 0;
+ int channelsInStart = 0;
+ int samplerate = G_DEFAULT_SAMPLERATE;
+ int buffersize = G_DEFAULT_BUFSIZE;
+ bool limitOutput = false;
+ int rsmpQuality = 0;
int midiSystem = 0;
int midiPortOut = G_DEFAULT_MIDI_PORT_OUT;
@@ -60,10 +61,10 @@ struct Conf
int midiSync = MIDI_SYNC_NONE;
float midiTCfps = 25.0f;
- bool recsStopOnChanHalt = false;
- bool chansStopOnSeqHalt = false;
- bool treatRecsAsLoops = false;
- bool inputMonitorDefaultOn = false;
+ bool chansStopOnSeqHalt = false;
+ bool treatRecsAsLoops = false;
+ bool inputMonitorDefaultOn = false;
+ bool overdubProtectionDefaultOn = false;
std::string pluginPath;
std::string patchPath;
@@ -151,6 +152,6 @@ extern Conf conf;
void init();
bool read();
bool write();
-}}}; // giada::m::conf::
+}}} // giada::m::conf::
#endif
diff --git a/src/core/const.h b/src/core/const.h
index 8547fbe..0e3ede2 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.17.1";
constexpr int G_VERSION_MAJOR = 0;
-constexpr int G_VERSION_MINOR = 16;
-constexpr int G_VERSION_PATCH = 2;
+constexpr int G_VERSION_MINOR = 17;
+constexpr int G_VERSION_PATCH = 1;
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.0f; // 30 fps
+constexpr float G_GUI_PLUGIN_RATE = 1 / 30.0f; // 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,36 +276,27 @@ 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 = static_cast(0xB0 << 24);
+constexpr uint32_t G_MIDI_ALL_NOTES_OFF = (G_MIDI_CONTROLLER) | (0x7B << 16);
/* system common / real-time messages. Single bytes */
-#define MIDI_SYSEX 0xF0
-#define MIDI_MTC_QUARTER 0xF1
-#define MIDI_POSITION_PTR 0xF2
-#define MIDI_CLOCK 0xF8
-#define MIDI_START 0xFA
-#define MIDI_CONTINUE 0xFB
-#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
-};
+constexpr int MIDI_SYSEX = 0xF0;
+constexpr int MIDI_MTC_QUARTER = 0xF1;
+constexpr int MIDI_POSITION_PTR = 0xF2;
+constexpr int MIDI_CLOCK = 0xF8;
+constexpr int MIDI_START = 0xFA;
+constexpr int MIDI_CONTINUE = 0xFB;
+constexpr int MIDI_STOP = 0xFC;
+constexpr int MIDI_EOX = 0xF7; // end of sysex
/* midi sync constants */
-#define MIDI_SYNC_NONE 0x00
-#define MIDI_SYNC_CLOCK_M 0x01 // master
-#define MIDI_SYNC_CLOCK_S 0x02 // slave
-#define MIDI_SYNC_MTC_M 0x04 // master
-#define MIDI_SYNC_MTC_S 0x08 // slave
+constexpr int MIDI_SYNC_NONE = 0x00;
+constexpr int MIDI_SYNC_CLOCK_M = 0x01; // master
+constexpr int MIDI_SYNC_CLOCK_S = 0x02; // slave
+constexpr int MIDI_SYNC_MTC_M = 0x04; // master
+constexpr int MIDI_SYNC_MTC_S = 0x08; // slave
/* JSON patch keys */
@@ -344,6 +352,7 @@ constexpr auto PATCH_KEY_CHANNEL_HAS_ACTIONS = "has_actions";
constexpr auto PATCH_KEY_CHANNEL_READ_ACTIONS = "read_actions";
constexpr auto PATCH_KEY_CHANNEL_PITCH = "pitch";
constexpr auto PATCH_KEY_CHANNEL_INPUT_MONITOR = "input_monitor";
+constexpr auto PATCH_KEY_CHANNEL_OVERDUB_PROTECTION = "overdub_protection";
constexpr auto PATCH_KEY_CHANNEL_MIDI_IN_READ_ACTIONS = "midi_in_read_actions";
constexpr auto PATCH_KEY_CHANNEL_MIDI_IN_PITCH = "midi_in_pitch";
constexpr auto PATCH_KEY_CHANNEL_MIDI_OUT = "midi_out";
@@ -363,6 +372,7 @@ constexpr auto PATCH_KEY_PLUGIN_ID = "id";
constexpr auto PATCH_KEY_PLUGIN_PATH = "path";
constexpr auto PATCH_KEY_PLUGIN_BYPASS = "bypass";
constexpr auto PATCH_KEY_PLUGIN_PARAMS = "params";
+constexpr auto PATCH_KEY_PLUGIN_STATE = "state";
constexpr auto PATCH_KEY_PLUGIN_MIDI_IN_PARAMS = "midi_in_params";
constexpr auto PATCH_KEY_COLUMN_ID = "id";
constexpr auto PATCH_KEY_COLUMN_WIDTH = "width";
@@ -376,85 +386,86 @@ constexpr auto G_PATCH_KEY_ACTION_NEXT = "next";
/* JSON config keys */
-constexpr auto CONF_KEY_HEADER = "header";
-constexpr auto CONF_KEY_LOG_MODE = "log_mode";
-constexpr auto CONF_KEY_SOUND_SYSTEM = "sound_system";
-constexpr auto CONF_KEY_SOUND_DEVICE_IN = "sound_device_in";
-constexpr auto CONF_KEY_SOUND_DEVICE_OUT = "sound_device_out";
-constexpr auto CONF_KEY_CHANNELS_IN = "channels_in";
-constexpr auto CONF_KEY_CHANNELS_OUT = "channels_out";
-constexpr auto CONF_KEY_SAMPLERATE = "samplerate";
-constexpr auto CONF_KEY_BUFFER_SIZE = "buffer_size";
-constexpr auto CONF_KEY_DELAY_COMPENSATION = "delay_compensation";
-constexpr auto CONF_KEY_LIMIT_OUTPUT = "limit_output";
-constexpr auto CONF_KEY_RESAMPLE_QUALITY = "resample_quality";
-constexpr auto CONF_KEY_MIDI_SYSTEM = "midi_system";
-constexpr auto CONF_KEY_MIDI_PORT_OUT = "midi_port_out";
-constexpr auto CONF_KEY_MIDI_PORT_IN = "midi_port_in";
-constexpr auto CONF_KEY_MIDIMAP_PATH = "midimap_path";
-constexpr auto CONF_KEY_LAST_MIDIMAP = "last_midimap";
-constexpr auto CONF_KEY_MIDI_SYNC = "midi_sync";
-constexpr auto CONF_KEY_MIDI_TC_FPS = "midi_tc_fps";
-constexpr auto CONF_KEY_MIDI_IN = "midi_in";
-constexpr auto CONF_KEY_MIDI_IN_FILTER = "midi_in_filter";
-constexpr auto CONF_KEY_MIDI_IN_REWIND = "midi_in_rewind";
-constexpr auto CONF_KEY_MIDI_IN_START_STOP = "midi_in_start_stop";
-constexpr auto CONF_KEY_MIDI_IN_ACTION_REC = "midi_in_action_rec";
-constexpr auto CONF_KEY_MIDI_IN_INPUT_REC = "midi_in_input_rec";
-constexpr auto CONF_KEY_MIDI_IN_METRONOME = "midi_in_metronome";
-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";
-constexpr auto CONF_KEY_PLUGINS_PATH = "plugins_path";
-constexpr auto CONF_KEY_PATCHES_PATH = "patches_path";
-constexpr auto CONF_KEY_SAMPLES_PATH = "samples_path";
-constexpr auto CONF_KEY_MAIN_WINDOW_X = "main_window_x";
-constexpr auto CONF_KEY_MAIN_WINDOW_Y = "main_window_y";
-constexpr auto CONF_KEY_MAIN_WINDOW_W = "main_window_w";
-constexpr auto CONF_KEY_MAIN_WINDOW_H = "main_window_h";
-constexpr auto CONF_KEY_BROWSER_X = "browser_x";
-constexpr auto CONF_KEY_BROWSER_Y = "browser_y";
-constexpr auto CONF_KEY_BROWSER_W = "browser_w";
-constexpr auto CONF_KEY_BROWSER_H = "browser_h";
-constexpr auto CONF_KEY_BROWSER_POSITION = "browser_position";
-constexpr auto CONF_KEY_BROWSER_LAST_PATH = "browser_last_path";
-constexpr auto CONF_KEY_BROWSER_LAST_VALUE = "browser_last_value";
-constexpr auto CONF_KEY_ACTION_EDITOR_X = "action_editor_x";
-constexpr auto CONF_KEY_ACTION_EDITOR_Y = "action_editor_y";
-constexpr auto CONF_KEY_ACTION_EDITOR_W = "action_editor_w";
-constexpr auto CONF_KEY_ACTION_EDITOR_H = "action_editor_h";
-constexpr auto CONF_KEY_ACTION_EDITOR_ZOOM = "action_editor_zoom";
-constexpr auto CONF_KEY_ACTION_EDITOR_GRID_VAL = "action_editor_grid_val";
-constexpr auto CONF_KEY_ACTION_EDITOR_GRID_ON = "action_editor_grid_on";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_X = "sample_editor_x";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_Y = "sample_editor_y";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_W = "sample_editor_w";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_H = "sample_editor_h";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_VAL = "sample_editor_grid_val";
-constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_ON = "sample_editor_grid_on";
-constexpr auto CONF_KEY_PIANO_ROLL_Y = "piano_roll_y";
-constexpr auto CONF_KEY_PIANO_ROLL_H = "piano_roll_h";
-constexpr auto CONF_KEY_SAMPLE_ACTION_EDITOR_H = "sample_action_editor_h";
-constexpr auto CONF_KEY_VELOCITY_EDITOR_H = "velocity_editor_h";
-constexpr auto CONF_KEY_ENVELOPE_EDITOR_H = "envelope_editor_h";
-constexpr auto CONF_KEY_PLUGIN_LIST_X = "plugin_list_x";
-constexpr auto CONF_KEY_PLUGIN_LIST_Y = "plugin_list_y";
-constexpr auto CONF_KEY_PLUGIN_CHOOSER_X = "plugin_chooser_x";
-constexpr auto CONF_KEY_PLUGIN_CHOOSER_Y = "plugin_chooser_y";
-constexpr auto CONF_KEY_PLUGIN_CHOOSER_W = "plugin_chooser_w";
-constexpr auto CONF_KEY_PLUGIN_CHOOSER_H = "plugin_chooser_h";
-constexpr auto CONF_KEY_MIDI_INPUT_X = "midi_input_x";
-constexpr auto CONF_KEY_MIDI_INPUT_Y = "midi_input_y";
-constexpr auto CONF_KEY_MIDI_INPUT_W = "midi_input_w";
-constexpr auto CONF_KEY_MIDI_INPUT_H = "midi_input_h";
-constexpr auto CONF_KEY_PLUGIN_SORT_METHOD = "plugin_sort_method";
-constexpr auto CONF_KEY_REC_TRIGGER_MODE = "rec_trigger_mode";
-constexpr auto CONF_KEY_REC_TRIGGER_LEVEL = "rec_trigger_level";
+constexpr auto CONF_KEY_HEADER = "header";
+constexpr auto CONF_KEY_LOG_MODE = "log_mode";
+constexpr auto CONF_KEY_SOUND_SYSTEM = "sound_system";
+constexpr auto CONF_KEY_SOUND_DEVICE_IN = "sound_device_in";
+constexpr auto CONF_KEY_SOUND_DEVICE_OUT = "sound_device_out";
+constexpr auto CONF_KEY_CHANNELS_OUT = "channels_out";
+constexpr auto CONF_KEY_CHANNELS_IN_COUNT = "channels_in_count";
+constexpr auto CONF_KEY_CHANNELS_IN_START = "channels_in_start";
+constexpr auto CONF_KEY_SAMPLERATE = "samplerate";
+constexpr auto CONF_KEY_BUFFER_SIZE = "buffer_size";
+constexpr auto CONF_KEY_DELAY_COMPENSATION = "delay_compensation";
+constexpr auto CONF_KEY_LIMIT_OUTPUT = "limit_output";
+constexpr auto CONF_KEY_RESAMPLE_QUALITY = "resample_quality";
+constexpr auto CONF_KEY_MIDI_SYSTEM = "midi_system";
+constexpr auto CONF_KEY_MIDI_PORT_OUT = "midi_port_out";
+constexpr auto CONF_KEY_MIDI_PORT_IN = "midi_port_in";
+constexpr auto CONF_KEY_MIDIMAP_PATH = "midimap_path";
+constexpr auto CONF_KEY_LAST_MIDIMAP = "last_midimap";
+constexpr auto CONF_KEY_MIDI_SYNC = "midi_sync";
+constexpr auto CONF_KEY_MIDI_TC_FPS = "midi_tc_fps";
+constexpr auto CONF_KEY_MIDI_IN = "midi_in";
+constexpr auto CONF_KEY_MIDI_IN_FILTER = "midi_in_filter";
+constexpr auto CONF_KEY_MIDI_IN_REWIND = "midi_in_rewind";
+constexpr auto CONF_KEY_MIDI_IN_START_STOP = "midi_in_start_stop";
+constexpr auto CONF_KEY_MIDI_IN_ACTION_REC = "midi_in_action_rec";
+constexpr auto CONF_KEY_MIDI_IN_INPUT_REC = "midi_in_input_rec";
+constexpr auto CONF_KEY_MIDI_IN_METRONOME = "midi_in_metronome";
+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_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";
+constexpr auto CONF_KEY_OVERDUB_PROTECTION_DEFAULT_ON = "overdub_protection_default_on";
+constexpr auto CONF_KEY_PLUGINS_PATH = "plugins_path";
+constexpr auto CONF_KEY_PATCHES_PATH = "patches_path";
+constexpr auto CONF_KEY_SAMPLES_PATH = "samples_path";
+constexpr auto CONF_KEY_MAIN_WINDOW_X = "main_window_x";
+constexpr auto CONF_KEY_MAIN_WINDOW_Y = "main_window_y";
+constexpr auto CONF_KEY_MAIN_WINDOW_W = "main_window_w";
+constexpr auto CONF_KEY_MAIN_WINDOW_H = "main_window_h";
+constexpr auto CONF_KEY_BROWSER_X = "browser_x";
+constexpr auto CONF_KEY_BROWSER_Y = "browser_y";
+constexpr auto CONF_KEY_BROWSER_W = "browser_w";
+constexpr auto CONF_KEY_BROWSER_H = "browser_h";
+constexpr auto CONF_KEY_BROWSER_POSITION = "browser_position";
+constexpr auto CONF_KEY_BROWSER_LAST_PATH = "browser_last_path";
+constexpr auto CONF_KEY_BROWSER_LAST_VALUE = "browser_last_value";
+constexpr auto CONF_KEY_ACTION_EDITOR_X = "action_editor_x";
+constexpr auto CONF_KEY_ACTION_EDITOR_Y = "action_editor_y";
+constexpr auto CONF_KEY_ACTION_EDITOR_W = "action_editor_w";
+constexpr auto CONF_KEY_ACTION_EDITOR_H = "action_editor_h";
+constexpr auto CONF_KEY_ACTION_EDITOR_ZOOM = "action_editor_zoom";
+constexpr auto CONF_KEY_ACTION_EDITOR_GRID_VAL = "action_editor_grid_val";
+constexpr auto CONF_KEY_ACTION_EDITOR_GRID_ON = "action_editor_grid_on";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_X = "sample_editor_x";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_Y = "sample_editor_y";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_W = "sample_editor_w";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_H = "sample_editor_h";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_VAL = "sample_editor_grid_val";
+constexpr auto CONF_KEY_SAMPLE_EDITOR_GRID_ON = "sample_editor_grid_on";
+constexpr auto CONF_KEY_PIANO_ROLL_Y = "piano_roll_y";
+constexpr auto CONF_KEY_PIANO_ROLL_H = "piano_roll_h";
+constexpr auto CONF_KEY_SAMPLE_ACTION_EDITOR_H = "sample_action_editor_h";
+constexpr auto CONF_KEY_VELOCITY_EDITOR_H = "velocity_editor_h";
+constexpr auto CONF_KEY_ENVELOPE_EDITOR_H = "envelope_editor_h";
+constexpr auto CONF_KEY_PLUGIN_LIST_X = "plugin_list_x";
+constexpr auto CONF_KEY_PLUGIN_LIST_Y = "plugin_list_y";
+constexpr auto CONF_KEY_PLUGIN_CHOOSER_X = "plugin_chooser_x";
+constexpr auto CONF_KEY_PLUGIN_CHOOSER_Y = "plugin_chooser_y";
+constexpr auto CONF_KEY_PLUGIN_CHOOSER_W = "plugin_chooser_w";
+constexpr auto CONF_KEY_PLUGIN_CHOOSER_H = "plugin_chooser_h";
+constexpr auto CONF_KEY_MIDI_INPUT_X = "midi_input_x";
+constexpr auto CONF_KEY_MIDI_INPUT_Y = "midi_input_y";
+constexpr auto CONF_KEY_MIDI_INPUT_W = "midi_input_w";
+constexpr auto CONF_KEY_MIDI_INPUT_H = "midi_input_h";
+constexpr auto CONF_KEY_PLUGIN_SORT_METHOD = "plugin_sort_method";
+constexpr auto CONF_KEY_REC_TRIGGER_MODE = "rec_trigger_mode";
+constexpr auto CONF_KEY_REC_TRIGGER_LEVEL = "rec_trigger_level";
/* JSON midimaps keys */
diff --git a/src/core/graphics.cpp b/src/core/graphics.cpp
index 74e09a3..3a9e83a 100644
--- a/src/core/graphics.cpp
+++ b/src/core/graphics.cpp
@@ -1855,3 +1855,31 @@ const char* armOn_xpm[] = {
" ",
" ",
" "};
+
+const char* armDisabled_xpm[] = {
+"18 18 7 1",
+" c None",
+". c #232523",
+"+ c #303230",
+"@ c #393B38",
+"# c #424441",
+"$ c #4B4D4A",
+"% c #4D4F4C",
+"..................",
+"..................",
+"..................",
+"..................",
+"......+#$$#+......",
+".....@%%%%%%@.....",
+"....+%%%%%%%%+....",
+"....#%%%%%%%%#....",
+"....$%%%%%%%%$....",
+"....$%%%%%%%%$....",
+"....#%%%%%%%%#....",
+"....+%%%%%%%%+....",
+".....@%%%%%%@.....",
+"......+#$$#+......",
+"..................",
+"..................",
+"..................",
+".................."};
\ No newline at end of file
diff --git a/src/core/graphics.h b/src/core/graphics.h
index 3c2cb1d..7db77c9 100644
--- a/src/core/graphics.h
+++ b/src/core/graphics.h
@@ -86,6 +86,7 @@ extern const char* soloOn_xpm[];
extern const char* armOff_xpm[];
extern const char* armOn_xpm[];
+extern const char* armDisabled_xpm[];
extern const char* readActionOn_xpm[];
extern const char* readActionOff_xpm[];
diff --git a/src/core/idManager.cpp b/src/core/idManager.cpp
index 2c0f100..8e8fa65 100644
--- a/src/core/idManager.cpp
+++ b/src/core/idManager.cpp
@@ -28,8 +28,7 @@
#include "idManager.h"
-namespace giada {
-namespace m
+namespace giada::m
{
IdManager::IdManager() : m_id(0)
{
@@ -53,4 +52,4 @@ ID IdManager::get(ID id)
{
return id != 0 ? id : ++m_id;
}
-}} // giada::m::
+} // giada::m::
diff --git a/src/core/idManager.h b/src/core/idManager.h
index 4b54b1a..5404286 100644
--- a/src/core/idManager.h
+++ b/src/core/idManager.h
@@ -32,23 +32,31 @@
#include "core/types.h"
-namespace giada {
-namespace m
+namespace giada::m
{
class IdManager
{
public:
IdManager();
-
+
+ /* set
+ Stores a new id, only if != 0 (valid) and greater than current id (unique). */
+
void set(ID id);
+
+ /* get
+ Generates a new unique id. If 'id' parameter is passed in is valid, it just
+ returns it with no unique id generation. Useful when loading things from the
+ model that already have their own id. */
+
ID get(ID id=0);
private:
ID m_id;
};
-}} // giada::m::
+} // giada::m::
#endif
diff --git a/src/core/init.cpp b/src/core/init.cpp
index d43a561..9c15aa8 100644
--- a/src/core/init.cpp
+++ b/src/core/init.cpp
@@ -45,18 +45,19 @@
#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"
-#include "core/pluginManager.h"
-#include "core/pluginHost.h"
+#include "core/plugins/pluginManager.h"
+#include "core/plugins/pluginHost.h"
#include "core/recorder.h"
#include "core/recorderHandler.h"
#include "core/recManager.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();
@@ -200,17 +204,23 @@ void printBuildInfo_()
{
u::log::print("[init] Giada %s\n", G_VERSION_STR);
u::log::print("[init] Build date: " BUILD_DATE "\n");
+#ifdef G_DEBUG_MODE
+ u::log::print("[init] Debug build\n");
+#else
+ u::log::print("[init] Release build\n");
+#endif
u::log::print("[init] Dependencies:\n");
u::log::print("[init] FLTK - %d.%d.%d\n", FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION);
- u::log::print("[init] RtAudio - %s\n", u::ver::getRtAudioVersion().c_str());
- u::log::print("[init] RtMidi - %s\n", u::ver::getRtMidiVersion().c_str());
+ u::log::print("[init] RtAudio - %s\n", u::ver::getRtAudioVersion());
+ u::log::print("[init] RtMidi - %s\n", u::ver::getRtMidiVersion());
u::log::print("[init] Libsamplerate\n"); // TODO - print version
- u::log::print("[init] Libsndfile - %s\n", u::ver::getLibsndfileVersion().c_str());
- u::log::print("[init] JSON for modern C++ - %d.%d.%d\n",
+ u::log::print("[init] Libsndfile - %s\n", u::ver::getLibsndfileVersion());
+ u::log::print("[init] JSON for modern C++ - %d.%d.%d\n",
NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH);
#ifdef WITH_VST
u::log::print("[init] JUCE - %d.%d.%d\n", JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER);
#endif
+ kernelAudio::logCompiledAPIs();
}
} // {anonymous}
@@ -260,6 +270,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 +289,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..aa1ed50 100644
--- a/src/core/kernelAudio.cpp
+++ b/src/core/kernelAudio.cpp
@@ -32,14 +32,13 @@
#include "glue/main.h"
#include "core/model/model.h"
#include "conf.h"
+#include "const.h"
#include "mixer.h"
#include "const.h"
#include "kernelAudio.h"
-namespace giada {
-namespace m {
-namespace kernelAudio
+namespace giada::m::kernelAudio
{
namespace
{
@@ -49,17 +48,17 @@ bool inputEnabled = false;
unsigned realBufsize = 0; // Real buffer size from the soundcard
int api = 0;
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef WITH_AUDIO_JACK
JackState jackState;
-jack_client_t* jackGetHandle()
+jack_client_t* jackGetHandle_()
{
return static_cast(rtSystem->HACK__getJackClient());
}
#endif
-}; // {anonymous}
+} // {anonymous}
/* -------------------------------------------------------------------------- */
@@ -67,10 +66,22 @@ jack_client_t* jackGetHandle()
/* -------------------------------------------------------------------------- */
+#ifdef WITH_AUDIO_JACK
+
+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;
}
@@ -125,7 +136,7 @@ int openDevice()
return 0;
}
- u::log::print("[KA] Opening devices %d (out), %d (in), f=%d...\n",
+ u::log::print("[KA] Opening device out=%d, in=%d, samplerate=%d\n",
conf::conf.soundDeviceOut, conf::conf.soundDeviceIn, conf::conf.samplerate);
numDevs = rtSystem->getDeviceCount();
@@ -138,7 +149,7 @@ int openDevice()
else {
u::log::print("[KA] %d device(s) found\n", numDevs);
for (unsigned i=0; iopenStream(
- &outParams, // output params
+ &outParams, // output params
conf::conf.soundDeviceIn != -1 ? &inParams : nullptr, // input params if inDevice is selected
- RTAUDIO_FLOAT32, // audio format
+ RTAUDIO_FLOAT32, // audio format
conf::conf.samplerate, // sample rate
- &realBufsize, // buffer size in byte
- &mixer::masterPlay, // audio callback
- nullptr, // user data (unused)
+ &realBufsize, // buffer size in byte
+ &mixer::masterPlay, // audio callback
+ 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) {
- u::log::print("[KA] rtSystem init error: %s\n", e.getMessage().c_str());
+ u::log::print("[KA] rtSystem init error: %s\n", e.getMessage());
closeDevice();
return 0;
}
@@ -210,7 +222,7 @@ int startStream()
return 1;
}
catch (RtAudioError &e) {
- u::log::print("[KA] Start stream error: %s\n", e.getMessage().c_str());
+ u::log::print("[KA] Start stream error: %s\n", e.getMessage());
return 0;
}
}
@@ -225,7 +237,7 @@ int stopStream()
rtSystem->stopStream();
return 1;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
u::log::print("[KA] Stop stream error\n");
return 0;
}
@@ -240,7 +252,7 @@ std::string getDeviceName(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).name;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
u::log::print("[KA] invalid device ID = %d\n", dev);
return "";
}
@@ -272,7 +284,7 @@ unsigned getMaxInChans(int dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).inputChannels;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
u::log::print("[KA] Unable to get input channels\n");
return 0;
}
@@ -287,7 +299,7 @@ unsigned getMaxOutChans(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).outputChannels;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
u::log::print("[KA] Unable to get output channels\n");
return 0;
}
@@ -302,7 +314,7 @@ bool isProbed(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).probed;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -316,7 +328,7 @@ unsigned getDuplexChans(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).duplexChannels;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -330,7 +342,7 @@ bool isDefaultIn(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).isDefaultInput;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -344,7 +356,7 @@ bool isDefaultOut(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).isDefaultOutput;
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -358,7 +370,7 @@ int getTotalFreqs(unsigned dev)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).sampleRates.size();
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -372,7 +384,7 @@ int getFreq(unsigned dev, int i)
try {
return static_cast(rtSystem->getDeviceInfo(dev)).sampleRates.at(i);
}
- catch (RtAudioError &e) {
+ catch (RtAudioError& /*e*/) {
return 0;
}
}
@@ -432,19 +444,56 @@ int getAPI() { return api; }
/* -------------------------------------------------------------------------- */
-#if defined(__linux__) || defined(__FreeBSD__)
+void logCompiledAPIs()
+{
+ std::vector APIs;
+ RtAudio::getCompiledApi(APIs);
+ u::log::print("[KA] Compiled RtAudio APIs: %d\n", APIs.size());
+
+ for (const RtAudio::Api& api : APIs) {
+ switch (api) {
+ case RtAudio::Api::LINUX_ALSA:
+ u::log::print(" ALSA\n"); break;
+ case RtAudio::Api::LINUX_PULSE:
+ u::log::print(" PulseAudio\n"); break;
+ case RtAudio::Api::UNIX_JACK:
+ u::log::print(" JACK\n"); break;
+ case RtAudio::Api::MACOSX_CORE:
+ u::log::print(" CoreAudio\n"); break;
+ case RtAudio::Api::WINDOWS_WASAPI:
+ u::log::print(" WASAPI\n"); break;
+ case RtAudio::Api::WINDOWS_ASIO:
+ u::log::print(" ASIO\n"); break;
+ case RtAudio::Api::WINDOWS_DS:
+ u::log::print(" DirectSound\n"); break;
+ case RtAudio::Api::RTAUDIO_DUMMY:
+ u::log::print(" Dummy\n"); break;
+ default:
+ u::log::print(" (unknown)\n"); break;
+ }
+ }
+}
-const JackState &jackTransportQuery()
+
+/* -------------------------------------------------------------------------- */
+
+
+#ifdef WITH_AUDIO_JACK
+
+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 +503,7 @@ const JackState &jackTransportQuery()
void jackStart()
{
if (api == G_SYS_API_JACK)
- jack_transport_start(jackGetHandle());
+ jack_transport_start(jackGetHandle_());
}
@@ -466,9 +515,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 +529,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,9 +545,8 @@ 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__)
-
-}}}; // giada::m::kernelAudio
+#endif // WITH_AUDIO_JACK
+} // giada::m::kernelAudio
diff --git a/src/core/kernelAudio.h b/src/core/kernelAudio.h
index 942a139..8e3ed7e 100644
--- a/src/core/kernelAudio.h
+++ b/src/core/kernelAudio.h
@@ -30,24 +30,24 @@
#include
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef WITH_AUDIO_JACK
#include
#include
#include
#endif
-namespace giada {
-namespace m {
-namespace kernelAudio
+namespace giada::m::kernelAudio
{
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef WITH_AUDIO_JACK
struct JackState
{
- bool running;
- double bpm;
- uint32_t frame;
+ bool running;
+ double bpm;
+ uint32_t frame;
+
+ bool operator!=(const JackState& o) const;
};
#endif
@@ -75,16 +75,18 @@ int getDefaultOut();
int getDefaultIn();
bool hasAPI(int API);
int getAPI();
+void logCompiledAPIs();
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef WITH_AUDIO_JACK
void jackStart();
void jackStop();
void jackSetPosition(uint32_t frame);
void jackSetBpm(double bpm);
-const JackState &jackTransportQuery();
+JackState jackTransportQuery();
#endif
-}}}; // giada::m::kernelAudio::
+} // giada::m::kernelAudio::
+
#endif
diff --git a/src/core/kernelMidi.cpp b/src/core/kernelMidi.cpp
index 93e8da2..4e0f62b 100644
--- a/src/core/kernelMidi.cpp
+++ b/src/core/kernelMidi.cpp
@@ -25,12 +25,8 @@
* -------------------------------------------------------------------------- */
-#include "const.h"
-#ifdef G_OS_MAC
#include
-#else
-#include
-#endif
+#include "const.h"
#include "utils/log.h"
#include "midiDispatcher.h"
#include "midiMapConf.h"
@@ -51,7 +47,7 @@ unsigned numOutPorts_ = 0;
unsigned numInPorts_ = 0;
-static void callback_(double t, std::vector* msg, void* data)
+static void callback_(double /*t*/, std::vector* msg, void* /*data*/)
{
if (msg->size() < 3) {
//u::log::print("[KM] MIDI received - unknown signal - size=%d, value=0x", (int) msg->size());
@@ -72,11 +68,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}
@@ -102,7 +99,7 @@ int openOutDevice(int port)
status_ = true;
}
catch (RtMidiError &error) {
- u::log::print("[KM] MIDI out device error: %s\n", error.getMessage().c_str());
+ u::log::print("[KM] MIDI out device error: %s\n", error.getMessage());
status_ = false;
return 0;
}
@@ -112,7 +109,7 @@ int openOutDevice(int port)
numOutPorts_ = midiOut_->getPortCount();
u::log::print("[KM] %d output MIDI ports found\n", numOutPorts_);
for (unsigned i=0; igetPortCount();
u::log::print("[KM] %d input MIDI ports found\n", numInPorts_);
for (unsigned i=0; igetPortName(p); }
- catch (RtMidiError &error) { return ""; }
+ catch (RtMidiError& /*error*/) { return ""; }
}
std::string getInPortName(unsigned p)
{
try { return midiIn_->getPortName(p); }
- catch (RtMidiError &error) { return ""; }
+ catch (RtMidiError& /*error*/) { return ""; }
}
@@ -224,7 +221,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 +241,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 +281,7 @@ unsigned countInPorts() { return numInPorts_; }
unsigned countOutPorts() { return numOutPorts_; }
bool getStatus() { return status_; }
+
/* -------------------------------------------------------------------------- */
@@ -296,15 +294,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::
+}}} // giada::m::kernelMidi::
diff --git a/src/core/kernelMidi.h b/src/core/kernelMidi.h
index f185b0c..7e1c90a 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. */
@@ -89,7 +83,7 @@ unsigned countOutPorts();
bool hasAPI(int API);
-}}}; // giada::m::kernelMidi::
+}}} // giada::m::kernelMidi::
#endif
diff --git a/src/core/midiDispatcher.cpp b/src/core/midiDispatcher.cpp
index 09227fc..eb2bd20 100644
--- a/src/core/midiDispatcher.cpp
+++ b/src/core/midiDispatcher.cpp
@@ -28,20 +28,15 @@
#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"
-#include "core/pluginHost.h"
-#include "core/plugin.h"
+#include "core/plugins/pluginHost.h"
+#include "core/plugins/plugin.h"
#include "core/recManager.h"
#include "core/types.h"
#include "core/midiDispatcher.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);
}
@@ -95,19 +90,18 @@ void processPlugins_(const std::vector& ids, const MidiEvent& midiEvent)
/* Plugins' parameters layout reflects the structure of the matrix
Channel::midiInPlugins. It is safe to assume then that Plugin 'p' and
- k indexes match both the structure of Channel::midiInPlugins and the vector
- of plugins. */
+ parameter indexes match both the structure of Channel::midiInPlugins and the
+ vector of plugins. */
m::model::PluginsLock l(m::model::plugins);
-
for (ID id : ids) {
- 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))
+ const m::Plugin& p = m::model::get(m::model::plugins, id);
+ for (const MidiLearnParam& param : p.midiInParams) {
+ if (pure != param.getValue())
continue;
- c::plugin::setParameter(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);
+ c::events::setPluginParameter(id, param.getIndex(), vf, /*gui=*/false);
+ u::log::print(" >>> [pluginId=%d paramIndex=%d] (pure=0x%X, value=%d, float=%f)\n",
+ p.id, param.getIndex(), pure, midiEvent.getVelocity(), vf);
}
}
}
@@ -122,98 +116,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... */
-
- std::vector> actions;
+ model::ChannelsLock lock(model::channels);
- 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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.getValue()) {
+ 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 +192,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 +244,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.setValue(raw); break;
+ case G_MIDI_IN_KEYREL: c.midiLearner.state->keyRelease.setValue(raw); break;
+ case G_MIDI_IN_KILL: c.midiLearner.state->kill.setValue(raw); break;
+ case G_MIDI_IN_ARM: c.midiLearner.state->arm.setValue(raw); break;
+ case G_MIDI_IN_MUTE: c.midiLearner.state->mute.setValue(raw); break;
+ case G_MIDI_IN_SOLO: c.midiLearner.state->solo.setValue(raw); break;
+ case G_MIDI_IN_VOLUME: c.midiLearner.state->volume.setValue(raw); break;
+ case G_MIDI_IN_PITCH: c.midiLearner.state->pitch.setValue(raw); break;
+ case G_MIDI_IN_READ_ACTIONS: c.midiLearner.state->readActions.setValue(raw); break;
+ case G_MIDI_OUT_L_PLAYING: c.midiLighter.state->playing.setValue(raw); break;
+ case G_MIDI_OUT_L_MUTE: c.midiLighter.state->mute.setValue(raw); break;
+ case G_MIDI_OUT_L_SOLO: c.midiLighter.state->solo.setValue(raw); break;
}
});
@@ -307,18 +274,18 @@ 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;
case G_MIDI_IN_START_STOP: m.startStop = raw; break;
case G_MIDI_IN_ACTION_REC: m.actionRec = raw; break;
case G_MIDI_IN_INPUT_REC: m.inputRec = raw; break;
- case G_MIDI_IN_METRONOME: m.volumeIn = raw; break;
- case G_MIDI_IN_VOLUME_IN: m.volumeOut = raw; break;
- case G_MIDI_IN_VOLUME_OUT: m.beatDouble = raw; break;
- case G_MIDI_IN_BEAT_DOUBLE: m.beatHalf = raw; break;
- case G_MIDI_IN_BEAT_HALF: m.metronome = raw; break;
+ case G_MIDI_IN_METRONOME: m.metronome = raw; break;
+ case G_MIDI_IN_VOLUME_IN: m.volumeIn = raw; break;
+ case G_MIDI_IN_VOLUME_OUT: m.volumeOut = raw; break;
+ case G_MIDI_IN_BEAT_DOUBLE: m.beatDouble = raw; break;
+ case G_MIDI_IN_BEAT_HALF: m.beatHalf = raw; break;
}
});
@@ -329,11 +296,12 @@ void learnMaster_(MidiEvent e, int param, std::function doneCb)
#ifdef WITH_VST
-void learnPlugin_(MidiEvent e, int paramIndex, ID pluginId, std::function doneCb)
+void learnPlugin_(MidiEvent e, std::size_t paramIndex, ID pluginId, std::function doneCb)
{
- model::onSwap(model::plugins, pluginId, [&](Plugin& p)
+ model::onGet(model::plugins, pluginId, [&](Plugin& p)
{
- p.midiInParams[paramIndex] = e.getRawNoVelocity();
+ assert(paramIndex < p.midiInParams.size());
+ p.midiInParams[paramIndex].setValue(e.getRawNoVelocity());
});
stopLearn();
@@ -375,7 +343,7 @@ void startMasterLearn (int param, std::function f)
#ifdef WITH_VST
-void startPluginLearn (int paramIndex, ID pluginId, std::function f)
+void startPluginLearn (std::size_t paramIndex, ID pluginId, std::function f)
{
learnCb_ = [=](m::MidiEvent e) { learnPlugin_(e, paramIndex, pluginId, f); };
}
@@ -406,7 +374,7 @@ void clearChannelLearn(int param, ID channelId, std::function f)
#ifdef WITH_VST
-void clearPluginLearn (int paramIndex, ID pluginId, std::function f)
+void clearPluginLearn (std::size_t paramIndex, ID pluginId, std::function f)
{
learnPlugin_(MidiEvent(), paramIndex, pluginId, f); // Empty event (0x0)
}
@@ -455,5 +423,5 @@ void setSignalCallback(std::function f)
signalCb_ = f;
}
-}}}; // giada::m::midiDispatcher::
+}}} // giada::m::midiDispatcher::
diff --git a/src/core/midiDispatcher.h b/src/core/midiDispatcher.h
index dd5de7c..45f79e8 100644
--- a/src/core/midiDispatcher.h
+++ b/src/core/midiDispatcher.h
@@ -46,14 +46,14 @@ void stopLearn();
void clearMasterLearn (int param, std::function f);
void clearChannelLearn(int param, ID channelId, std::function f);
#ifdef WITH_VST
-void startPluginLearn (int paramIndex, ID pluginId, std::function f);
-void clearPluginLearn (int paramIndex, ID pluginId, std::function f);
+void startPluginLearn (std::size_t paramIndex, ID pluginId, std::function f);
+void clearPluginLearn (std::size_t paramIndex, ID pluginId, std::function f);
#endif
void dispatch(int byte1, int byte2, int byte3);
void setSignalCallback(std::function f);
-}}}; // giada::m::midiDispatcher::
+}}} // giada::m::midiDispatcher::
#endif
diff --git a/src/core/midiEvent.cpp b/src/core/midiEvent.cpp
index abdbea4..a22d987 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 = static_cast(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/midiLearnParam.cpp b/src/core/midiLearnParam.cpp
new file mode 100644
index 0000000..6fd9d47
--- /dev/null
+++ b/src/core/midiLearnParam.cpp
@@ -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
+ * .
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#include "midiLearnParam.h"
+
+
+namespace giada::m
+{
+MidiLearnParam::MidiLearnParam()
+: m_param(0x0)
+, m_index(0)
+{
+}
+
+
+MidiLearnParam::MidiLearnParam(uint32_t v, std::size_t index)
+: m_param(v)
+, m_index(index)
+{
+}
+
+
+MidiLearnParam::MidiLearnParam(const MidiLearnParam& o)
+: m_param(o.m_param.load(std::memory_order_relaxed))
+, m_index(o.m_index)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+uint32_t MidiLearnParam::getValue() const
+{
+ return m_param.load(std::memory_order_relaxed);
+}
+
+
+void MidiLearnParam::setValue(uint32_t v)
+{
+ m_param.store(v, std::memory_order_relaxed);
+}
+
+
+std::size_t MidiLearnParam::getIndex() const
+{
+ return m_index;
+}
+} // giada::m::
\ No newline at end of file
diff --git a/src/core/midiLearnParam.h b/src/core/midiLearnParam.h
new file mode 100644
index 0000000..c5a86a0
--- /dev/null
+++ b/src/core/midiLearnParam.h
@@ -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
+ * .
+ *
+ * -------------------------------------------------------------------------- */
+
+
+#ifndef G_MIDI_LEARN_PARAM_H
+#define G_MIDI_LEARN_PARAM_H
+
+
+#include
+
+
+namespace giada::m
+{
+class MidiLearnParam
+{
+public:
+
+ MidiLearnParam();
+ MidiLearnParam(uint32_t v, std::size_t index=0);
+ MidiLearnParam(const MidiLearnParam& o);
+
+ uint32_t getValue() const;
+ std::size_t getIndex() const;
+ void setValue(uint32_t v);
+
+private:
+
+ std::atomic m_param;
+ std::size_t m_index;
+};
+} // giada::m::
+
+
+#endif
diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp
index fa4efc7..0a48172 100644
--- a/src/core/midiMapConf.cpp
+++ b/src/core/midiMapConf.cpp
@@ -29,7 +29,7 @@
#include
#include
#include
-#include
+#include
#include "deps/json/single_include/nlohmann/json.hpp"
#include "utils/string.h"
#include "utils/log.h"
@@ -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, "");
@@ -116,10 +116,9 @@ void parse_(Message& message)
message.value = strtoul(output.c_str(), nullptr, 16);
u::log::print("[parse] parsed chan=%d valueStr=%s value=%#x, offset=%d\n",
- message.channel, message.valueStr.c_str(), message.value, message.offset);
+ message.channel, message.valueStr, message.value, message.offset);
}
-
-}; // {anonymous}
+} // {anonymous}
/* -------------------------------------------------------------------------- */
@@ -142,30 +141,22 @@ void init()
/* scan dir of midi maps and load the filenames into <>maps. */
u::log::print("[midiMapConf::init] scanning midimaps directory '%s'...\n",
- midimapsPath.c_str());
-
- DIR* dp;
- dirent* ep;
- dp = opendir(midimapsPath.c_str());
+ midimapsPath);
- if (dp == nullptr) {
+ if (!std::filesystem::exists(midimapsPath)) {
u::log::print("[midiMapConf::init] unable to scan midimaps directory!\n");
return;
}
- while ((ep = readdir(dp))) {
- if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
- continue;
-
+ for (const auto& d : std::filesystem::directory_iterator(midimapsPath)) {
// TODO - check if is a valid midimap file (verify headers)
-
- u::log::print("[midiMapConf::init] found midimap '%s'\n", ep->d_name);
-
- maps.push_back(ep->d_name);
+ if (!d.is_regular_file())
+ continue;
+ u::log::print("[midiMapConf::init] found midimap '%s'\n", d.path().filename().string());
+ maps.push_back(d.path().filename().string());
}
u::log::print("[midiMapConf::init] total midimaps found: %d\n", maps.size());
- closedir(dp);
}
@@ -197,7 +188,7 @@ int read(const std::string& file)
return MIDIMAP_NOT_SPECIFIED;
}
- u::log::print("[midiMapConf::read] reading midimap file '%s'\n", file.c_str());
+ u::log::print("[midiMapConf::read] reading midimap file '%s'\n", file);
std::ifstream ifs(midimapsPath + file);
if (!ifs.good())
@@ -221,4 +212,4 @@ int read(const std::string& file)
return MIDIMAP_READ_OK;
}
-}}}; // giada::m::midimap::
+}}} // giada::m::midimap::
diff --git a/src/core/midiMapConf.h b/src/core/midiMapConf.h
index 0d7ecc9..4d348bd 100644
--- a/src/core/midiMapConf.h
+++ b/src/core/midiMapConf.h
@@ -103,6 +103,6 @@ bool isDefined(const Message& msg);
Reads a midi map from file 'file'. */
int read(const std::string& file);
-}}}; // giada::m::midimap::
+}}} // giada::m::midimap::
#endif
diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp
index ebe4966..181b452 100644
--- a/src/core/mixer.cpp
+++ b/src/core/mixer.cpp
@@ -31,20 +31,18 @@
#include "utils/log.h"
#include "utils/math.h"
#include "core/model/model.h"
-#include "core/channels/channel.h"
-#include "core/channels/sampleChannel.h"
-#include "core/channels/midiChannel.h"
#include "core/wave.h"
#include "core/kernelAudio.h"
#include "core/recorder.h"
#include "core/recManager.h"
-#include "core/pluginHost.h"
+#include "core/plugins/pluginHost.h"
#include "core/conf.h"
#include "core/mixerHandler.h"
#include "core/clock.h"
#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. Used for the in->out bridge. */
-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,109 @@ 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 and prepares the internal working buffer for input
+recording. */
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) {
+G_DEBUG("Signal > threshold!");
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. */
+
+ assert(inBuf.countChannels() <= inBuffer_.countChannels());
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}
+} // {anonymous}
/* -------------------------------------------------------------------------- */
@@ -343,26 +266,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 +311,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_;
}
@@ -410,24 +333,22 @@ const AudioBuffer& getVirtualInput()
int masterPlay(void* outBuf, void* inBuf, unsigned bufferSize,
- double streamTime, RtAudioStreamStatus status, void* userData)
+ double /*streamTime*/, RtAudioStreamStatus /*status*/, void* /*userData*/)
{
if (!kernelAudio::isReady() || active_.load() == false)
return 0;
processing_.store(true);
-#if defined(__linux__) || defined(__FreeBSD__)
-
+#ifdef WITH_AUDIO_JACK
if (kernelAudio::getAPI() == G_SYS_API_JACK)
clock::recvJackSync();
-
#endif
AudioBuffer out, in;
- out.setData((float*) outBuf, bufferSize, G_MAX_IO_CHANS);
+ out.setData(static_cast(outBuf), bufferSize, G_MAX_IO_CHANS);
if (kernelAudio::isInputEnabled())
- in.setData((float*) inBuf, bufferSize, G_MAX_IO_CHANS);
+ in.setData(static_cast(inBuf), bufferSize, conf::conf.channelsInCount);
/* Reset peak computation. */
@@ -437,22 +358,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 +401,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 +417,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::
+}}} // giada::m::mixer::
diff --git a/src/core/mixer.h b/src/core/mixer.h
index 1ced345..3651af1 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 {
@@ -46,24 +49,57 @@ class AudioBuffer;
namespace mixer
{
-struct FrameEvents
+enum class EventType
{
- Frame frameLocal;
- Frame frameGlobal;
- bool doQuantize;
- bool onBar;
- bool onFirstBeat;
- bool quantoPassed;
- const std::vector* actions;
+ 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_KILL_READ_ACTIONS,
+ CHANNEL_TOGGLE_ARM,
+ CHANNEL_MUTE,
+ CHANNEL_SOLO,
+ CHANNEL_VOLUME,
+ CHANNEL_PITCH,
+ CHANNEL_PAN
};
+struct Event
+{
+ 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..a247020 100644
--- a/src/core/mixerHandler.cpp
+++ b/src/core/mixerHandler.cpp
@@ -35,17 +35,14 @@
#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"
#include "core/const.h"
#include "core/init.h"
-#include "core/pluginHost.h"
-#include "core/pluginManager.h"
-#include "core/plugin.h"
+#include "core/plugins/pluginHost.h"
+#include "core/plugins/pluginManager.h"
+#include "core/plugins/plugin.h"
#include "core/waveFx.h"
#include "core/conf.h"
#include "core/patch.h"
@@ -68,15 +65,14 @@ namespace
{
std::unique_ptr createChannel_(ChannelType type, ID columnId, ID channelId=0)
{
- std::unique_ptr ch = channelManager::create(type,
- kernelAudio::getRealBufSize(), conf::conf.inputMonitorDefaultOn, columnId);
+ std::unique_ptr ch = channelManager::create(type, columnId, conf::conf);
if (type == ChannelType::MASTER) {
assert(channelId != 0);
ch->id = channelId;
}
- return ch;
+ return ch;
}
@@ -85,24 +81,15 @@ std::unique_ptr createChannel_(ChannelType type, ID columnId, ID channe
waveManager::Result createWave_(const std::string& fname)
{
- waveManager::Result res = waveManager::createFromFile(fname);
- if (res.status != G_RES_OK)
- return res;
- if (res.wave->getRate() != conf::conf.samplerate) {
- u::log::print("[mh::createWave_] input rate (%d) != system rate (%d), conversion needed\n",
- res.wave->getRate(), conf::conf.samplerate);
- res.status = waveManager::resample(*res.wave.get(), conf::conf.rsmpQuality, conf::conf.samplerate);
- if (res.status != G_RES_OK)
- return res;
- }
- return res;
+ return waveManager::createFromFile(fname, /*ID=*/0, conf::conf.samplerate,
+ conf::conf.rsmpQuality);
}
/* -------------------------------------------------------------------------- */
-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,31 +99,126 @@ 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() && !c->hasWave(); });
+}
+
+
+std::vector getOverdubbableChannels_()
+{
+ return getChannelsIf_([] (const Channel* c) { return c->canInputRec() && c->hasWave(); });
}
/* -------------------------------------------------------------------------- */
/* 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