Skip to content

Commit

Permalink
Add plugin subproject
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiashienzsch committed Feb 12, 2024
1 parent b81c4ab commit 7b763c0
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.23...3.27)
file(STRINGS VERSION CURRENT_VERSION)
project(grit-eurorack-dev VERSION ${CURRENT_VERSION} LANGUAGES C CXX)

option(GRITWAVE_EURORACK_ENABLE_PLUGIN "Build plugin (development tool)" OFF)

find_program(CCACHE ccache)
if (CCACHE)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
Expand Down Expand Up @@ -75,4 +77,8 @@ if(NOT CMAKE_CROSSCOMPILING)
"lib/grit/math/static_lookup_table_test.cpp"
"lib/grit/unit/decibel_test.cpp"
)

if(GRITWAVE_EURORACK_ENABLE_PLUGIN)
add_subdirectory(src/plugin)
endif()
endif()
66 changes: 66 additions & 0 deletions src/plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
project(gritwave-gritrack VERSION ${CMAKE_PROJECT_VERSION})

FetchContent_Declare(JUCE GIT_REPOSITORY "https://github.com/juce-framework/JUCE" GIT_TAG "develop" GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(JUCE)

set_directory_properties(PROPERTIES JUCE_COMPANY_COPYRIGHT "Copyright 2024 gritwave. All rights reserved.")
set_directory_properties(PROPERTIES JUCE_COMPANY_NAME "gritwave")
set_directory_properties(PROPERTIES JUCE_VST3_COPY_DIR "${CMAKE_BINARY_DIR}/VST3")
set_directory_properties(PROPERTIES JUCE_AU_COPY_DIR "${CMAKE_BINARY_DIR}/AU")

juce_add_plugin(Gritrack
PRODUCT_NAME "Gritrack" # The name of the final executable, which can differ from the target name
BUNDLE_ID "com.gritwave.gritrack" # Should uniquely identify this target
PLUGIN_CODE Grtr # A unique four-character plugin id with exactly one upper-case character
PLUGIN_MANUFACTURER_CODE Grtw # A four-character manufacturer id with at least one upper-case character
FORMATS AU VST3 Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3
HARDENED_RUNTIME_ENABLED TRUE # Enables macOS' hardened runtime for this target. Required for notarisation.
COPY_PLUGIN_AFTER_BUILD TRUE # Should the plugin be installed to a default location after building?
IS_SYNTH FALSE # Is this a synth or an effect?
NEEDS_MIDI_INPUT FALSE # Does the plugin need midi input?
NEEDS_MIDI_OUTPUT FALSE # Does the plugin need midi output?
IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect?
EDITOR_WANTS_KEYBOARD_FOCUS FALSE # Does the editor need keyboard focus?
)

set(Gritrack_Sources
"src/PluginEditor.hpp"
"src/PluginEditor.cpp"
"src/PluginParameter.hpp"
"src/PluginParameter.cpp"
"src/PluginProcessor.hpp"
"src/PluginProcessor.cpp"
)

target_sources(Gritrack PRIVATE ${Gritrack_Sources})
set_target_properties(Gritrack PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE GROUP)
set_source_files_properties(Gritrack ${Gritrack_Sources} PROPERTIES UNITY_GROUP "src")

target_compile_definitions(Gritrack
PUBLIC
JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0
JUCE_VST3_CAN_REPLACE_VST2=0
JUCE_USE_MP3AUDIOFORMAT=0
JUCE_DISPLAY_SPLASH_SCREEN=0
JUCE_MODAL_LOOPS_PERMITTED=1
JUCE_REPORT_APP_USAGE=0
JUCE_DISABLE_JUCE_VERSION_PRINTING=1
)

target_include_directories(Gritrack
PUBLIC
${PROJECT_SOURCE_DIR}/src
)

target_link_libraries(Gritrack
PRIVATE
juce::juce_audio_utils
juce::juce_dsp

PUBLIC
gritwave::eurorack
juce::juce_recommended_config_flags
# juce::juce_recommended_lto_flags
# juce::juce_recommended_warning_flags
)
17 changes: 17 additions & 0 deletions src/plugin/src/PluginEditor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "PluginEditor.hpp"

PluginEditor::PluginEditor(PluginProcessor& p) : AudioProcessorEditor(&p)
{
_tooltipWindow->setMillisecondsBeforeTipAppears(750);
setSize(600, 400);
}

PluginEditor::~PluginEditor() noexcept { setLookAndFeel(nullptr); }

auto PluginEditor::paint(juce::Graphics& g) -> void
{
// background
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
}

auto PluginEditor::resized() -> void {}
17 changes: 17 additions & 0 deletions src/plugin/src/PluginEditor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "PluginProcessor.hpp"

#include <juce_gui_extra/juce_gui_extra.h>

struct PluginEditor final : juce::AudioProcessorEditor
{
explicit PluginEditor(PluginProcessor& p);
~PluginEditor() noexcept override;

auto paint(juce::Graphics& g) -> void override;
auto resized() -> void override;

private:
juce::SharedResourcePointer<juce::TooltipWindow> _tooltipWindow;
};
21 changes: 21 additions & 0 deletions src/plugin/src/PluginParameter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "PluginParameter.hpp"

namespace gritrack {

auto createParameters() -> juce::AudioProcessorValueTreeState::ParameterLayout
{
auto const normalized = juce::NormalisableRange(0.0F, 1.0F);

return {
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv1, 1}, "CV-1", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv2, 1}, "CV-2", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv3, 1}, "CV-3", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv4, 1}, "CV-4", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv5, 1}, "CV-5", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv6, 1}, "CV-6", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv7, 1}, "CV-7", normalized, 0.0F),
std::make_unique<juce::AudioParameterFloat>(juce::ParameterID{ParamID::cv8, 1}, "CV-8", normalized, 0.0F),
};
}

} // namespace gritrack
22 changes: 22 additions & 0 deletions src/plugin/src/PluginParameter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_data_structures/juce_data_structures.h>

namespace gritrack {

struct ParamID
{
static constexpr char const* cv1 = "cv1";
static constexpr char const* cv2 = "cv2";
static constexpr char const* cv3 = "cv3";
static constexpr char const* cv4 = "cv4";
static constexpr char const* cv5 = "cv5";
static constexpr char const* cv6 = "cv6";
static constexpr char const* cv7 = "cv7";
static constexpr char const* cv8 = "cv8";
};

auto createParameters() -> juce::AudioProcessorValueTreeState::ParameterLayout;

} // namespace gritrack
143 changes: 143 additions & 0 deletions src/plugin/src/PluginProcessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@

#include "PluginProcessor.hpp"

#include "PluginEditor.hpp"
#include "PluginParameter.hpp"

PluginProcessor::PluginProcessor()
: AudioProcessor(BusesProperties()
.withInput("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
, _valueTree{*this, nullptr, juce::Identifier("Gritwave"), gritrack::createParameters()}
, _cv1{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv1))}
, _cv2{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv2))}
, _cv3{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv3))}
, _cv4{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv4))}
, _cv5{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv5))}
, _cv6{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv6))}
, _cv7{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv7))}
, _cv8{*dynamic_cast<juce::AudioParameterFloat*>(_valueTree.getParameter(gritrack::ParamID::cv8))}
{}

PluginProcessor::~PluginProcessor() = default;

auto PluginProcessor::getName() const -> juce::String const { return JucePlugin_Name; }

auto PluginProcessor::acceptsMidi() const -> bool { return false; }

auto PluginProcessor::producesMidi() const -> bool { return false; }

auto PluginProcessor::isMidiEffect() const -> bool { return false; }

auto PluginProcessor::getTailLengthSeconds() const -> double { return 0.0; }

auto PluginProcessor::getNumPrograms() -> int { return 1; }

auto PluginProcessor::getCurrentProgram() -> int { return 0; }

auto PluginProcessor::setCurrentProgram(int index) -> void { juce::ignoreUnused(index); }

auto PluginProcessor::getProgramName(int index) -> juce::String const
{
juce::ignoreUnused(index);
return {};
}

auto PluginProcessor::changeProgramName(int index, juce::String const& newName) -> void
{
juce::ignoreUnused(index, newName);
}

auto PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) -> void
{
_hades = std::make_unique<grit::Hades>();
_hades->prepare(sampleRate, static_cast<std::size_t>(samplesPerBlock));

_buffer.resize(2U * static_cast<std::size_t>(samplesPerBlock));
}

auto PluginProcessor::releaseResources() -> void {}

auto PluginProcessor::isBusesLayoutSupported(BusesLayout const& layouts) const -> bool
{
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) {
return false;
}

if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) {
return false;
}

return true;
}

void PluginProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
juce::ignoreUnused(midiMessages);
juce::ScopedNoDenormals const noDenormals;

for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) {
buffer.clear(i, 0, buffer.getNumSamples());
}

auto io = grit::StereoBlock<float>{_buffer.data(), static_cast<etl::size_t>(buffer.getNumSamples())};
for (auto i = 0; i < buffer.getNumSamples(); ++i) {
io(0, i) = buffer.getSample(0, i);
io(1, i) = buffer.getSample(1, i);
}

auto const controls = grit::Hades::ControlInput{
.textureKnob = _cv1,
.morphKnob = _cv2,
.ampKnob = _cv3,
.compressorKnob = _cv4,
.morphCV = _cv5,
.sideChainCV = _cv6,
.attackCV = _cv7,
.releaseCV = _cv8,
.gate1 = false,
.gate2 = false,
};

auto const cv = _hades->process(io, controls);
juce::ignoreUnused(cv);

for (auto i = 0; i < buffer.getNumSamples(); ++i) {
buffer.setSample(0, i, io(0, i));
buffer.setSample(1, i, io(1, i));
}
}

auto PluginProcessor::parameterChanged(juce::String const& parameterID, float newValue) -> void
{
juce::ignoreUnused(newValue, parameterID);
}

auto PluginProcessor::hasEditor() const -> bool { return true; }

auto PluginProcessor::createEditor() -> juce::AudioProcessorEditor*
{
return new juce::GenericAudioProcessorEditor(*this);
}

auto PluginProcessor::getStateInformation(juce::MemoryBlock& destData) -> void
{
juce::MemoryOutputStream stream(destData, false);
_valueTree.state.writeToStream(stream);
}

auto PluginProcessor::setStateInformation(void const* data, int sizeInBytes) -> void
{
juce::ValueTree const tree = juce::ValueTree::readFromData(data, static_cast<size_t>(sizeInBytes));
jassert(tree.isValid());
if (tree.isValid()) {
_valueTree.state = tree;
}
}

auto PluginProcessor::getState() noexcept -> juce::AudioProcessorValueTreeState& { return _valueTree; }

auto PluginProcessor::getState() const noexcept -> juce::AudioProcessorValueTreeState const& { return _valueTree; }

// This creates new instances of the plugin..
auto JUCE_CALLTYPE createPluginFilter() -> juce::AudioProcessor* { return new PluginProcessor(); }
62 changes: 62 additions & 0 deletions src/plugin/src/PluginProcessor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include <grit/eurorack/hades.hpp>

#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_dsp/juce_dsp.h>
#include <juce_gui_extra/juce_gui_extra.h>

struct PluginProcessor final
: juce::AudioProcessor
, juce::AudioProcessorValueTreeState::Listener
{
PluginProcessor();
~PluginProcessor() override;

auto getName() const -> juce::String const override;

auto prepareToPlay(double sampleRate, int samplesPerBlock) -> void override;
auto processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) -> void override;
using juce::AudioProcessor::processBlock;
auto releaseResources() -> void override;

auto isBusesLayoutSupported(BusesLayout const& layouts) const -> bool override;

auto createEditor() -> juce::AudioProcessorEditor* override;
auto hasEditor() const -> bool override;

auto acceptsMidi() const -> bool override;
auto producesMidi() const -> bool override;
auto isMidiEffect() const -> bool override;
auto getTailLengthSeconds() const -> double override;

auto getNumPrograms() -> int override;
auto getCurrentProgram() -> int override;
void setCurrentProgram(int index) override;
auto getProgramName(int index) -> juce::String const override;
void changeProgramName(int index, juce::String const& newName) override;

auto getStateInformation(juce::MemoryBlock& destData) -> void override;
auto setStateInformation(void const* data, int sizeInBytes) -> void override;

auto parameterChanged(juce::String const& parameterID, float newValue) -> void override;

auto getState() noexcept -> juce::AudioProcessorValueTreeState&;
auto getState() const noexcept -> juce::AudioProcessorValueTreeState const&;

private:
juce::UndoManager _undoManager{};
juce::AudioProcessorValueTreeState _valueTree;

std::vector<float> _buffer{};
std::unique_ptr<grit::Hades> _hades{nullptr};

juce::AudioParameterFloat& _cv1;
juce::AudioParameterFloat& _cv2;
juce::AudioParameterFloat& _cv3;
juce::AudioParameterFloat& _cv4;
juce::AudioParameterFloat& _cv5;
juce::AudioParameterFloat& _cv6;
juce::AudioParameterFloat& _cv7;
juce::AudioParameterFloat& _cv8;
};

0 comments on commit 7b763c0

Please sign in to comment.