diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..69038d1 --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Left +AlignOperands: Align +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +BreakBeforeBraces: Attach +BreakConstructorInitializers: AfterColon +IncludeBlocks: Preserve +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +InsertBraces: true +ColumnLimit: 0 +PointerAlignment: Left +AllowShortEnumsOnASingleLine: true diff --git a/Source/BufferedMidiMessage.h b/Source/BufferedMidiMessage.h index 4142e3e..afc347b 100644 --- a/Source/BufferedMidiMessage.h +++ b/Source/BufferedMidiMessage.h @@ -3,10 +3,11 @@ #include "JuceHeader.h" class BufferedMidiMessage { -public: - BufferedMidiMessage(const juce::MidiMessage& message, unsigned long long time) : message(message), time(time) {} - ~BufferedMidiMessage() {} + public: + BufferedMidiMessage(const juce::MidiMessage& message, unsigned long long time) : + message(message), time(time) {} + ~BufferedMidiMessage() {} - juce::MidiMessage message; - unsigned long long time; + juce::MidiMessage message; + unsigned long long time; }; \ No newline at end of file diff --git a/Source/BufferedNote.cpp b/Source/BufferedNote.cpp index 71fc8c7..7d51ba1 100644 --- a/Source/BufferedNote.cpp +++ b/Source/BufferedNote.cpp @@ -1,24 +1,20 @@ #include "BufferedNote.h" -BufferedNote::BufferedNote(const juce::MidiMessage& noteOnMessage, unsigned long long startTime) -{ - this->pitch = noteOnMessage.getNoteNumber(); - this->velocity = noteOnMessage.getVelocity(); - this->startTime = startTime; - this->endTime = std::optional(); +BufferedNote::BufferedNote(const juce::MidiMessage& noteOnMessage, unsigned long long startTime) { + this->pitch = noteOnMessage.getNoteNumber(); + this->velocity = noteOnMessage.getVelocity(); + this->startTime = startTime; + this->endTime = std::optional(); } -std::optional BufferedNote::length() -{ - if (endTime.has_value()) { - return startTime - endTime.value(); - } - else { - return std::optional(); - } +std::optional BufferedNote::length() { + if (endTime.has_value()) { + return startTime - endTime.value(); + } else { + return std::optional(); + } } -bool BufferedNote::hasEnd() -{ - return endTime.has_value(); +bool BufferedNote::hasEnd() { + return endTime.has_value(); } \ No newline at end of file diff --git a/Source/BufferedNote.h b/Source/BufferedNote.h index d5e2e91..e18ce5c 100644 --- a/Source/BufferedNote.h +++ b/Source/BufferedNote.h @@ -3,19 +3,21 @@ #include "JuceHeader.h" class BufferedNote { -public: - BufferedNote(const juce::MidiMessage& noteOnMessage, unsigned long long startTime); - BufferedNote(int pitch, int velocity, unsigned long long startTime, unsigned long long endTime) : pitch(pitch), velocity(velocity), startTime(startTime), endTime(endTime) {} - BufferedNote(int pitch, int velocity, unsigned long long startTime) : pitch(pitch), velocity(velocity), startTime(startTime), endTime(std::optional()) {} - ~BufferedNote() {} + public: + BufferedNote(const juce::MidiMessage& noteOnMessage, unsigned long long startTime); + BufferedNote(int pitch, int velocity, unsigned long long startTime, unsigned long long endTime) : + pitch(pitch), velocity(velocity), startTime(startTime), endTime(endTime) {} + BufferedNote(int pitch, int velocity, unsigned long long startTime) : + pitch(pitch), velocity(velocity), startTime(startTime), endTime(std::optional()) {} + ~BufferedNote() {} - int pitch; - int velocity; - unsigned long long startTime; - int endDelay = 0; - std::optional endTime; - std::optional length(); - bool hasEnd(); + int pitch; + int velocity; + unsigned long long startTime; + int endDelay = 0; + std::optional endTime; + std::optional length(); + bool hasEnd(); - bool allowLegato = false; + bool allowLegato = false; }; \ No newline at end of file diff --git a/Source/ConfigParserUtil.cpp b/Source/ConfigParserUtil.cpp index 20cb474..f4fd88f 100644 --- a/Source/ConfigParserUtil.cpp +++ b/Source/ConfigParserUtil.cpp @@ -1,22 +1,17 @@ #include "ConfigParserUtil.h" -int ConfigParserUtil::keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC) -{ - static const char* const noteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "", "Db", "", "Eb", "", "", "Gb", "", "Ab", "", "Bb" }; +int ConfigParserUtil::keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC) { + static const char* const noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "", "Db", "", "Eb", "", "", "Gb", "", "Ab", "", "Bb"}; int keyNumber, octave = 0, numPos = keyName.indexOfAnyOf("01234567890-"); - if (numPos == 0) - keyNumber = keyName.getIntValue(); //apparently already a number! - else - { - if (numPos > 0) - { + if (numPos == 0) { + keyNumber = keyName.getIntValue(); // apparently already a number! + } else { + if (numPos > 0) { octave = keyName.substring(numPos).getIntValue() - octaveForMiddleC + 5; - } - else - { - octave = octaveForMiddleC; //default to octave of middle C if none found + } else { + octave = octaveForMiddleC; // default to octave of middle C if none found numPos = keyName.length(); } diff --git a/Source/ConfigParserUtil.h b/Source/ConfigParserUtil.h index 096e6cc..ec65667 100644 --- a/Source/ConfigParserUtil.h +++ b/Source/ConfigParserUtil.h @@ -3,7 +3,7 @@ #include "JuceHeader.h" class ConfigParserUtil { -public: - // Adapted from https://forum.juce.com/t/midimessage-keynametonumber/9904 - static int keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC); + public: + // Adapted from https://forum.juce.com/t/midimessage-keynametonumber/9904 + static int keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC); }; \ No newline at end of file diff --git a/Source/Configuration.cpp b/Source/Configuration.cpp index 0d188ad..3256872 100644 --- a/Source/Configuration.cpp +++ b/Source/Configuration.cpp @@ -1,134 +1,131 @@ #include "Configuration.h" -#include "InputTreeRootNode.h" #include "ConfigParserUtil.h" +#include "InputTreeRootNode.h" -Configuration::Configuration() : Configuration("\n \n 0\n \n \n \n") -{ +Configuration::Configuration() : + Configuration("\n \n 0\n \n \n \n") { } -Configuration::Configuration(const std::string& xml) -{ - this->xml = xml; +Configuration::Configuration(const std::string& xml) { + this->xml = xml; - std::unique_ptr xmlDocument = std::make_unique(xml); - std::unique_ptr rootElement = xmlDocument->getDocumentElementIfTagMatches("foresight"); + std::unique_ptr xmlDocument = std::make_unique(xml); + std::unique_ptr rootElement = xmlDocument->getDocumentElementIfTagMatches("foresight"); - // Header + // Header - if (!rootElement) throw std::exception(xmlDocument->getLastParseError().toStdString().c_str()); + if (!rootElement) { + throw std::exception(xmlDocument->getLastParseError().toStdString().c_str()); + } - version = rootElement->getIntAttribute("version", 0); - name = rootElement->getStringAttribute("name").toStdString(); + version = rootElement->getIntAttribute("version", 0); + name = rootElement->getStringAttribute("name").toStdString(); - if (version > CURRENT_CONFIG_VERSION) throw std::exception("This configuration was created for a newer version of Foresight. Please update the plugin to use this configuration."); + if (version > CURRENT_CONFIG_VERSION) { + throw std::exception("This configuration was created for a newer version of Foresight. Please update the plugin to use this configuration."); + } - // Settings + // Settings - juce::XmlElement* settingsRootElement = rootElement->getChildByName("settings"); + juce::XmlElement* settingsRootElement = rootElement->getChildByName("settings"); - if (settingsRootElement) { - // Latency - juce::XmlElement* latencySettingElement = settingsRootElement->getChildByName("latency"); - if (latencySettingElement) latency = std::stod(latencySettingElement->getAllSubText().toStdString()) / 1000.0; + if (settingsRootElement) { + // Latency + juce::XmlElement* latencySettingElement = settingsRootElement->getChildByName("latency"); + if (latencySettingElement) { + latency = std::stod(latencySettingElement->getAllSubText().toStdString()) / 1000.0; + } - // Range - for (const auto& rangeElement : settingsRootElement->getChildWithTagNameIterator("range")) { - std::string rangeModeText = rangeElement->getStringAttribute("boundary", "lower").toStdString(); - int* targetVariable = rangeModeText == "upper" ? &rangeUpperBoundary : &rangeLowerBoundary; - int noteNumber = ConfigParserUtil::keyNameToNumber(rangeElement->getAllSubText(), 3); - *targetVariable = noteNumber; - } + // Range + for (const auto& rangeElement : settingsRootElement->getChildWithTagNameIterator("range")) { + std::string rangeModeText = rangeElement->getStringAttribute("boundary", "lower").toStdString(); + int* targetVariable = rangeModeText == "upper" ? &rangeUpperBoundary : &rangeLowerBoundary; + int noteNumber = ConfigParserUtil::keyNameToNumber(rangeElement->getAllSubText(), 3); + *targetVariable = noteNumber; + } - // Blocklist - for (const auto& blockElement : settingsRootElement->getChildWithTagNameIterator("block")) { - juce::String targetText = blockElement->getAllSubText(); - if (targetText.startsWith("CC")) { - blocked.insert(targetText.trim().toStdString()); - } - else { - blocked.insert(std::to_string(ConfigParserUtil::keyNameToNumber(targetText, 3))); - } - } - } + // Blocklist + for (const auto& blockElement : settingsRootElement->getChildWithTagNameIterator("block")) { + juce::String targetText = blockElement->getAllSubText(); + if (targetText.startsWith("CC")) { + blocked.insert(targetText.trim().toStdString()); + } else { + blocked.insert(std::to_string(ConfigParserUtil::keyNameToNumber(targetText, 3))); + } + } + } - // Input + // Input - juce::XmlElement* inputTreeRootElement = rootElement->getChildByName("input"); + juce::XmlElement* inputTreeRootElement = rootElement->getChildByName("input"); - if (!inputTreeRootElement) throw std::exception("No node found."); + if (!inputTreeRootElement) { + throw std::exception("No node found."); + } - inputTreeRoot = std::make_unique(*inputTreeRootElement); + inputTreeRoot = std::make_unique(*inputTreeRootElement); - // Output + // Output - juce::XmlElement* outputListRootElement = rootElement->getChildByName("output"); + juce::XmlElement* outputListRootElement = rootElement->getChildByName("output"); - if (!outputListRootElement) throw std::exception("No node found."); + if (!outputListRootElement) { + throw std::exception("No node found."); + } - for (const auto& tagElement : outputListRootElement->getChildIterator()) { - std::string tagName = tagElement->getStringAttribute("name").toStdString(); + for (const auto& tagElement : outputListRootElement->getChildIterator()) { + std::string tagName = tagElement->getStringAttribute("name").toStdString(); - for (const auto& setElement : tagElement->getChildIterator()) { - outputList[tagName].emplace_back(*setElement); - } - } + for (const auto& setElement : tagElement->getChildIterator()) { + outputList[tagName].emplace_back(*setElement); + } + } } -std::string Configuration::getSourceXML() -{ - return xml; +std::string Configuration::getSourceXML() { + return xml; } -std::string Configuration::getName() -{ - return name; +std::string Configuration::getName() { + return name; } -double Configuration::getLatencySeconds() -{ - return latency; +double Configuration::getLatencySeconds() { + return latency; } -double Configuration::getSampleRate() -{ - return lastSampleRate; +double Configuration::getSampleRate() { + return lastSampleRate; } -void Configuration::updateSampleRate(double sampleRate) -{ - lastSampleRate = sampleRate; +void Configuration::updateSampleRate(double sampleRate) { + lastSampleRate = sampleRate; } -int Configuration::getLatencySamples() -{ - return (int)std::round(latency * lastSampleRate); +int Configuration::getLatencySamples() { + return (int)std::round(latency * lastSampleRate); } -bool Configuration::isInRange(int noteNumber) -{ - return noteNumber >= rangeLowerBoundary && noteNumber <= rangeUpperBoundary; +bool Configuration::isInRange(int noteNumber) { + return noteNumber >= rangeLowerBoundary && noteNumber <= rangeUpperBoundary; } -bool Configuration::isBlocked(const juce::MidiMessage& message) -{ - if (message.isController()) { - std::string ccString = "CC" + std::to_string(message.getControllerNumber()); - return blocked.contains(ccString); - } - else if (message.isNoteOnOrOff()) { - return blocked.contains(std::to_string(message.getNoteNumber())); - } - - return false; +bool Configuration::isBlocked(const juce::MidiMessage& message) { + if (message.isController()) { + std::string ccString = "CC" + std::to_string(message.getControllerNumber()); + return blocked.contains(ccString); + } else if (message.isNoteOnOrOff()) { + return blocked.contains(std::to_string(message.getNoteNumber())); + } + + return false; } -std::unordered_set Configuration::getTagsForNote(NoteContext& context) -{ - inputTreeRoot->visit(context); - return context.getTags(); +std::unordered_set Configuration::getTagsForNote(NoteContext& context) { + inputTreeRoot->visit(context); + return context.getTags(); } -std::vector Configuration::getOutputNodes(const std::string& tag) -{ - return outputList[tag]; +std::vector Configuration::getOutputNodes(const std::string& tag) { + return outputList[tag]; } diff --git a/Source/Configuration.h b/Source/Configuration.h index 459e2f4..f7a84cf 100644 --- a/Source/Configuration.h +++ b/Source/Configuration.h @@ -1,36 +1,37 @@ #pragma once -#include -#include "Versioning.h" #include "IInputTreeNode.h" #include "OutputListNode.h" +#include "Versioning.h" +#include class Configuration { -public: - Configuration(); - Configuration(const std::string& xml); - std::string getSourceXML(); - std::string getName(); - double getLatencySeconds(); - double getSampleRate(); - void updateSampleRate(double sampleRate); - int getLatencySamples(); - bool isInRange(int noteNumber); - bool isBlocked(const juce::MidiMessage& message); + public: + Configuration(); + Configuration(const std::string& xml); + std::string getSourceXML(); + std::string getName(); + double getLatencySeconds(); + double getSampleRate(); + void updateSampleRate(double sampleRate); + int getLatencySamples(); + bool isInRange(int noteNumber); + bool isBlocked(const juce::MidiMessage& message); + + std::unordered_set getTagsForNote(NoteContext& context); + std::vector getOutputNodes(const std::string& tag); - std::unordered_set getTagsForNote(NoteContext& context); - std::vector getOutputNodes(const std::string& tag); -private: - int version; - double lastSampleRate; - std::string name; - std::string xml; + private: + int version; + double lastSampleRate; + std::string name; + std::string xml; - double latency = 0.0; - int rangeLowerBoundary = INT_MIN; - int rangeUpperBoundary = INT_MAX; + double latency = 0.0; + int rangeLowerBoundary = INT_MIN; + int rangeUpperBoundary = INT_MAX; - std::unique_ptr inputTreeRoot; - std::map> outputList; - std::unordered_set blocked; + std::unique_ptr inputTreeRoot; + std::map> outputList; + std::unordered_set blocked; }; \ No newline at end of file diff --git a/Source/GuiEditorComponent.cpp b/Source/GuiEditorComponent.cpp index f00bed7..3569d8f 100644 --- a/Source/GuiEditorComponent.cpp +++ b/Source/GuiEditorComponent.cpp @@ -22,89 +22,81 @@ #include "GuiEditorComponent.h" - //[MiscUserDefs] You can add your own user definitions and misc code here... //[/MiscUserDefs] //============================================================================== -GuiEditorComponent::GuiEditorComponent () -{ +GuiEditorComponent::GuiEditorComponent() { //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - setName ("EditorComponent"); - juce_config_textEditor.reset (new juce::TextEditor ("configEditor")); - addAndMakeVisible (juce_config_textEditor.get()); - juce_config_textEditor->setMultiLine (true); - juce_config_textEditor->setReturnKeyStartsNewLine (true); - juce_config_textEditor->setReadOnly (false); - juce_config_textEditor->setScrollbarsShown (true); - juce_config_textEditor->setCaretVisible (true); - juce_config_textEditor->setPopupMenuEnabled (true); - juce_config_textEditor->setColour (juce::TextEditor::outlineColourId, juce::Colour (0x00000000)); - juce_config_textEditor->setText (TRANS("Paste your configuration XML here...")); - - juce_applyConfig_textButton.reset (new juce::TextButton ("applyConfigButton")); - addAndMakeVisible (juce_applyConfig_textButton.get()); - juce_applyConfig_textButton->setButtonText (TRANS("Apply")); - juce_applyConfig_textButton->addListener (this); - + setName("EditorComponent"); + juce_config_textEditor.reset(new juce::TextEditor("configEditor")); + addAndMakeVisible(juce_config_textEditor.get()); + juce_config_textEditor->setMultiLine(true); + juce_config_textEditor->setReturnKeyStartsNewLine(true); + juce_config_textEditor->setReadOnly(false); + juce_config_textEditor->setScrollbarsShown(true); + juce_config_textEditor->setCaretVisible(true); + juce_config_textEditor->setPopupMenuEnabled(true); + juce_config_textEditor->setColour(juce::TextEditor::outlineColourId, juce::Colour(0x00000000)); + juce_config_textEditor->setText(TRANS("Paste your configuration XML here...")); + + juce_applyConfig_textButton.reset(new juce::TextButton("applyConfigButton")); + addAndMakeVisible(juce_applyConfig_textButton.get()); + juce_applyConfig_textButton->setButtonText(TRANS("Apply")); + juce_applyConfig_textButton->addListener(this); //[UserPreSize] //[/UserPreSize] - setSize (300, 300); - + setSize(300, 300); //[Constructor] You can add your own custom stuff here.. //[/Constructor] } -GuiEditorComponent::~GuiEditorComponent() -{ +GuiEditorComponent::~GuiEditorComponent() { //[Destructor_pre]. You can add your own custom destruction code here.. //[/Destructor_pre] juce_config_textEditor = nullptr; juce_applyConfig_textButton = nullptr; - //[Destructor]. You can add your own custom destruction code here.. //[/Destructor] } //============================================================================== -void GuiEditorComponent::paint (juce::Graphics& g) -{ +void GuiEditorComponent::paint(juce::Graphics& g) { //[UserPrePaint] Add your own custom painting code here.. //[/UserPrePaint] - g.fillAll (juce::Colour (0xff323e44)); + g.fillAll(juce::Colour(0xff323e44)); //[UserPaint] Add your own custom painting code here.. //[/UserPaint] } -void GuiEditorComponent::resized() -{ +void GuiEditorComponent::resized() { //[UserPreResize] Add your own custom resize code here.. //[/UserPreResize] - juce_config_textEditor->setBounds (0, 0, getWidth() - 0, getHeight() - 32); - juce_applyConfig_textButton->setBounds (0, getHeight() - 28, proportionOfWidth (1.0000f), 24); + juce_config_textEditor->setBounds(0, 0, getWidth() - 0, getHeight() - 32); + juce_applyConfig_textButton->setBounds(0, getHeight() - 28, proportionOfWidth(1.0000f), 24); //[UserResized] Add your own custom resize handling here.. //[/UserResized] } -void GuiEditorComponent::buttonClicked (juce::Button* buttonThatWasClicked) -{ +void GuiEditorComponent::buttonClicked(juce::Button* buttonThatWasClicked) { //[UserbuttonClicked_Pre] //[/UserbuttonClicked_Pre] - if (buttonThatWasClicked == juce_applyConfig_textButton.get()) - { + if (buttonThatWasClicked == juce_applyConfig_textButton.get()) { //[UserButtonCode_juce_applyConfig_textButton] -- add your button handler code here.. - if(configUpdatedCallback) configUpdatedCallback(juce_config_textEditor->getText().toStdString()); + if (configUpdatedCallback) { + configUpdatedCallback(juce_config_textEditor->getText().toStdString()); + } //[/UserButtonCode_juce_applyConfig_textButton] } @@ -112,20 +104,15 @@ void GuiEditorComponent::buttonClicked (juce::Button* buttonThatWasClicked) //[/UserbuttonClicked_Post] } - - //[MiscUserCode] You can add your own definitions of your custom methods or any other code here... -void GuiEditorComponent::setConfigUpdatedCallback(std::function callback) -{ +void GuiEditorComponent::setConfigUpdatedCallback(std::function callback) { configUpdatedCallback = callback; } -void GuiEditorComponent::setDisplayedConfig(const std::string& value) -{ +void GuiEditorComponent::setDisplayedConfig(const std::string& value) { juce_config_textEditor->setText(value); } //[/MiscUserCode] - //============================================================================== #if 0 /* -- Projucer information section -- @@ -153,7 +140,5 @@ END_JUCER_METADATA */ #endif - //[EndFile] You can add extra defines here... //[/EndFile] - diff --git a/Source/GuiEditorComponent.h b/Source/GuiEditorComponent.h index e35b27c..b4804ae 100644 --- a/Source/GuiEditorComponent.h +++ b/Source/GuiEditorComponent.h @@ -23,8 +23,6 @@ #include //[/Headers] - - //============================================================================== /** //[Comments] @@ -33,12 +31,11 @@ Describe your class and how it works here! //[/Comments] */ -class GuiEditorComponent : public juce::Component, - public juce::Button::Listener -{ -public: +class GuiEditorComponent : public juce::Component, + public juce::Button::Listener { + public: //============================================================================== - GuiEditorComponent (); + GuiEditorComponent(); ~GuiEditorComponent() override; //============================================================================== @@ -47,13 +44,11 @@ class GuiEditorComponent : public juce::Component, void setDisplayedConfig(const std::string& value); //[/UserMethods] - void paint (juce::Graphics& g) override; + void paint(juce::Graphics& g) override; void resized() override; - void buttonClicked (juce::Button* buttonThatWasClicked) override; - + void buttonClicked(juce::Button* buttonThatWasClicked) override; - -private: + private: //[UserVariables] -- You can add your own custom variables in this section. std::function configUpdatedCallback; //[/UserVariables] @@ -62,11 +57,9 @@ class GuiEditorComponent : public juce::Component, std::unique_ptr juce_config_textEditor; std::unique_ptr juce_applyConfig_textButton; - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GuiEditorComponent) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GuiEditorComponent) }; //[EndFile] You can add extra defines here... //[/EndFile] - diff --git a/Source/GuiMainComponent.cpp b/Source/GuiMainComponent.cpp index 14e0289..b3d1530 100644 --- a/Source/GuiMainComponent.cpp +++ b/Source/GuiMainComponent.cpp @@ -22,57 +22,49 @@ #include "GuiMainComponent.h" - //[MiscUserDefs] You can add your own user definitions and misc code here... //[/MiscUserDefs] //============================================================================== -GuiMainComponent::GuiMainComponent () -{ +GuiMainComponent::GuiMainComponent() { //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - setName ("MainComponent"); - juce_currentConfigValue_label.reset (new juce::Label ("currentConfigValue", - TRANS("Config Title"))); - addAndMakeVisible (juce_currentConfigValue_label.get()); - juce_currentConfigValue_label->setFont (juce::Font (24.00f, juce::Font::plain).withTypefaceStyle ("Regular")); - juce_currentConfigValue_label->setJustificationType (juce::Justification::centred); - juce_currentConfigValue_label->setEditable (false, false, false); - juce_currentConfigValue_label->setColour (juce::TextEditor::textColourId, juce::Colours::black); - juce_currentConfigValue_label->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); - - juce_currentConfigHeading_label.reset (new juce::Label ("currentConfigHeading", - TRANS("Loaded Configuration"))); - addAndMakeVisible (juce_currentConfigHeading_label.get()); - juce_currentConfigHeading_label->setFont (juce::Font (14.00f, juce::Font::plain).withTypefaceStyle ("Regular")); - juce_currentConfigHeading_label->setJustificationType (juce::Justification::centred); - juce_currentConfigHeading_label->setEditable (false, false, false); - juce_currentConfigHeading_label->setColour (juce::TextEditor::textColourId, juce::Colours::black); - juce_currentConfigHeading_label->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); - - juce_currentLatency_label.reset (new juce::Label ("currentLatency", - TRANS("Total latency: xxx ms"))); - addAndMakeVisible (juce_currentLatency_label.get()); - juce_currentLatency_label->setFont (juce::Font (14.00f, juce::Font::plain).withTypefaceStyle ("Regular")); - juce_currentLatency_label->setJustificationType (juce::Justification::centred); - juce_currentLatency_label->setEditable (false, false, false); - juce_currentLatency_label->setColour (juce::TextEditor::textColourId, juce::Colours::black); - juce_currentLatency_label->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); - + setName("MainComponent"); + juce_currentConfigValue_label.reset(new juce::Label("currentConfigValue", TRANS("Config Title"))); + addAndMakeVisible(juce_currentConfigValue_label.get()); + juce_currentConfigValue_label->setFont(juce::Font(24.00f, juce::Font::plain).withTypefaceStyle("Regular")); + juce_currentConfigValue_label->setJustificationType(juce::Justification::centred); + juce_currentConfigValue_label->setEditable(false, false, false); + juce_currentConfigValue_label->setColour(juce::TextEditor::textColourId, juce::Colours::black); + juce_currentConfigValue_label->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000)); + + juce_currentConfigHeading_label.reset(new juce::Label("currentConfigHeading", TRANS("Loaded Configuration"))); + addAndMakeVisible(juce_currentConfigHeading_label.get()); + juce_currentConfigHeading_label->setFont(juce::Font(14.00f, juce::Font::plain).withTypefaceStyle("Regular")); + juce_currentConfigHeading_label->setJustificationType(juce::Justification::centred); + juce_currentConfigHeading_label->setEditable(false, false, false); + juce_currentConfigHeading_label->setColour(juce::TextEditor::textColourId, juce::Colours::black); + juce_currentConfigHeading_label->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000)); + + juce_currentLatency_label.reset(new juce::Label("currentLatency", TRANS("Total latency: xxx ms"))); + addAndMakeVisible(juce_currentLatency_label.get()); + juce_currentLatency_label->setFont(juce::Font(14.00f, juce::Font::plain).withTypefaceStyle("Regular")); + juce_currentLatency_label->setJustificationType(juce::Justification::centred); + juce_currentLatency_label->setEditable(false, false, false); + juce_currentLatency_label->setColour(juce::TextEditor::textColourId, juce::Colours::black); + juce_currentLatency_label->setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x00000000)); //[UserPreSize] //[/UserPreSize] - setSize (300, 300); - + setSize(300, 300); //[Constructor] You can add your own custom stuff here.. //[/Constructor] } -GuiMainComponent::~GuiMainComponent() -{ +GuiMainComponent::~GuiMainComponent() { //[Destructor_pre]. You can add your own custom destruction code here.. //[/Destructor_pre] @@ -80,50 +72,42 @@ GuiMainComponent::~GuiMainComponent() juce_currentConfigHeading_label = nullptr; juce_currentLatency_label = nullptr; - //[Destructor]. You can add your own custom destruction code here.. //[/Destructor] } //============================================================================== -void GuiMainComponent::paint (juce::Graphics& g) -{ +void GuiMainComponent::paint(juce::Graphics& g) { //[UserPrePaint] Add your own custom painting code here.. //[/UserPrePaint] - g.fillAll (juce::Colour (0xff323e44)); + g.fillAll(juce::Colour(0xff323e44)); //[UserPaint] Add your own custom painting code here.. //[/UserPaint] } -void GuiMainComponent::resized() -{ +void GuiMainComponent::resized() { //[UserPreResize] Add your own custom resize code here.. //[/UserPreResize] - juce_currentConfigValue_label->setBounds (proportionOfWidth (0.0000f), (getHeight() / 2) + -30, proportionOfWidth (1.0000f), 24); - juce_currentConfigHeading_label->setBounds (proportionOfWidth (0.0000f), (getHeight() / 2) + -54, proportionOfWidth (1.0000f), 24); - juce_currentLatency_label->setBounds (proportionOfWidth (0.0000f), (getHeight() / 2) + 34, proportionOfWidth (1.0000f), 24); + juce_currentConfigValue_label->setBounds(proportionOfWidth(0.0000f), (getHeight() / 2) + -30, proportionOfWidth(1.0000f), 24); + juce_currentConfigHeading_label->setBounds(proportionOfWidth(0.0000f), (getHeight() / 2) + -54, proportionOfWidth(1.0000f), 24); + juce_currentLatency_label->setBounds(proportionOfWidth(0.0000f), (getHeight() / 2) + 34, proportionOfWidth(1.0000f), 24); //[UserResized] Add your own custom resize handling here.. //[/UserResized] } - - //[MiscUserCode] You can add your own definitions of your custom methods or any other code here... -void GuiMainComponent::setDisplayedConfigName(const std::string& value) -{ +void GuiMainComponent::setDisplayedConfigName(const std::string& value) { juce_currentConfigValue_label->setText(value, juce::NotificationType::sendNotification); } -void GuiMainComponent::setDisplayedLatency(const std::string& value) -{ +void GuiMainComponent::setDisplayedLatency(const std::string& value) { juce_currentLatency_label->setText(value, juce::NotificationType::sendNotification); } //[/MiscUserCode] - //============================================================================== #if 0 /* -- Projucer information section -- @@ -159,7 +143,5 @@ END_JUCER_METADATA */ #endif - //[EndFile] You can add extra defines here... //[/EndFile] - diff --git a/Source/GuiMainComponent.h b/Source/GuiMainComponent.h index 41bac91..9f98752 100644 --- a/Source/GuiMainComponent.h +++ b/Source/GuiMainComponent.h @@ -23,8 +23,6 @@ #include //[/Headers] - - //============================================================================== /** //[Comments] @@ -33,11 +31,10 @@ Describe your class and how it works here! //[/Comments] */ -class GuiMainComponent : public juce::Component -{ -public: +class GuiMainComponent : public juce::Component { + public: //============================================================================== - GuiMainComponent (); + GuiMainComponent(); ~GuiMainComponent() override; //============================================================================== @@ -46,12 +43,10 @@ class GuiMainComponent : public juce::Component void setDisplayedLatency(const std::string& value); //[/UserMethods] - void paint (juce::Graphics& g) override; + void paint(juce::Graphics& g) override; void resized() override; - - -private: + private: //[UserVariables] -- You can add your own custom variables in this section. //[/UserVariables] @@ -60,11 +55,9 @@ class GuiMainComponent : public juce::Component std::unique_ptr juce_currentConfigHeading_label; std::unique_ptr juce_currentLatency_label; - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GuiMainComponent) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GuiMainComponent) }; //[EndFile] You can add extra defines here... //[/EndFile] - diff --git a/Source/IInputTreeNode.h b/Source/IInputTreeNode.h index d97c4eb..a71f942 100644 --- a/Source/IInputTreeNode.h +++ b/Source/IInputTreeNode.h @@ -2,7 +2,7 @@ #include "NoteContext.h" class IInputTreeNode { -public: - ~IInputTreeNode() {} - virtual NoteContext& visit(NoteContext& context) = 0; + public: + ~IInputTreeNode() {} + virtual NoteContext& visit(NoteContext& context) = 0; }; \ No newline at end of file diff --git a/Source/InputTreeCase.cpp b/Source/InputTreeCase.cpp index 6f7978b..2c7c26c 100644 --- a/Source/InputTreeCase.cpp +++ b/Source/InputTreeCase.cpp @@ -1,15 +1,13 @@ #include "InputTreeCase.h" -InputTreeCase::InputTreeCase(const juce::XmlElement& source) -{ - equals = source.getIntAttribute("equals", -1); - greater = source.getIntAttribute("greater", INT_MIN); - less = source.getIntAttribute("less", INT_MAX); +InputTreeCase::InputTreeCase(const juce::XmlElement& source) { + equals = source.getIntAttribute("equals", -1); + greater = source.getIntAttribute("greater", INT_MIN); + less = source.getIntAttribute("less", INT_MAX); - alwaysTrue = source.isTextElement(); + alwaysTrue = source.isTextElement(); } -bool InputTreeCase::check(int value) -{ - return alwaysTrue || (equals != -1 ? (value == equals) : (value > greater && value < less)); +bool InputTreeCase::check(int value) { + return alwaysTrue || (equals != -1 ? (value == equals) : (value > greater && value < less)); } diff --git a/Source/InputTreeCase.h b/Source/InputTreeCase.h index 3fe4f91..ed5db6d 100644 --- a/Source/InputTreeCase.h +++ b/Source/InputTreeCase.h @@ -3,10 +3,11 @@ #include "JuceHeader.h" class InputTreeCase { -public: - InputTreeCase(const juce::XmlElement& source); - bool check(int value); -private: - bool alwaysTrue; - int equals, greater, less; + public: + InputTreeCase(const juce::XmlElement& source); + bool check(int value); + + private: + bool alwaysTrue; + int equals, greater, less; }; \ No newline at end of file diff --git a/Source/InputTreeNodeFactory.cpp b/Source/InputTreeNodeFactory.cpp index db5b9cf..d698ee5 100644 --- a/Source/InputTreeNodeFactory.cpp +++ b/Source/InputTreeNodeFactory.cpp @@ -2,17 +2,14 @@ #include "InputTreeSwitchNode.h" #include "InputTreeTagNode.h" -IInputTreeNode* InputTreeNodeFactory::make(const juce::XmlElement& source) -{ +IInputTreeNode* InputTreeNodeFactory::make(const juce::XmlElement& source) { IInputTreeNode* node; if (source.getTagName() == "switch") { node = new InputTreeSwitchNode(source); - } - else if (source.getTagName() == "case") { + } else if (source.getTagName() == "case") { throw std::exception("Encountered a node that is not a direct child of a node."); - } - else { + } else { node = new InputTreeTagNode(source); } diff --git a/Source/InputTreeNodeFactory.h b/Source/InputTreeNodeFactory.h index b1d3f2c..22835c1 100644 --- a/Source/InputTreeNodeFactory.h +++ b/Source/InputTreeNodeFactory.h @@ -1,9 +1,9 @@ #pragma once -#include "JuceHeader.h" #include "IInputTreeNode.h" +#include "JuceHeader.h" class InputTreeNodeFactory { -public: - static IInputTreeNode* make(const juce::XmlElement& source); + public: + static IInputTreeNode* make(const juce::XmlElement& source); }; \ No newline at end of file diff --git a/Source/InputTreeRootNode.cpp b/Source/InputTreeRootNode.cpp index 7cbf21a..bccc5b2 100644 --- a/Source/InputTreeRootNode.cpp +++ b/Source/InputTreeRootNode.cpp @@ -1,16 +1,14 @@ #include "InputTreeRootNode.h" #include "InputTreeNodeFactory.h" -InputTreeRootNode::InputTreeRootNode(const juce::XmlElement& source) -{ +InputTreeRootNode::InputTreeRootNode(const juce::XmlElement& source) { for (const auto& childElement : source.getChildIterator()) { IInputTreeNode* child = InputTreeNodeFactory::make(*childElement); children.emplace_back(child); } } -NoteContext& InputTreeRootNode::visit(NoteContext& context) -{ +NoteContext& InputTreeRootNode::visit(NoteContext& context) { for (const auto& child : children) { child->visit(context); } diff --git a/Source/InputTreeRootNode.h b/Source/InputTreeRootNode.h index 766d02c..bf5baaf 100644 --- a/Source/InputTreeRootNode.h +++ b/Source/InputTreeRootNode.h @@ -1,13 +1,14 @@ #pragma once -#include "JuceHeader.h" #include "IInputTreeNode.h" #include "InputTreeCase.h" +#include "JuceHeader.h" class InputTreeRootNode : public IInputTreeNode { -public: - InputTreeRootNode(const juce::XmlElement& source); - NoteContext& visit(NoteContext& context) override; -private: - std::vector> children; + public: + InputTreeRootNode(const juce::XmlElement& source); + NoteContext& visit(NoteContext& context) override; + + private: + std::vector> children; }; \ No newline at end of file diff --git a/Source/InputTreeSwitchNode.cpp b/Source/InputTreeSwitchNode.cpp index 187bd01..8ae6c6a 100644 --- a/Source/InputTreeSwitchNode.cpp +++ b/Source/InputTreeSwitchNode.cpp @@ -1,31 +1,32 @@ #include "InputTreeSwitchNode.h" -#include "InputTreeNodeFactory.h" #include "ConfigParserUtil.h" +#include "InputTreeNodeFactory.h" -InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source) -{ +InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source) { if (!source.hasAttribute("target")) { throw std::exception("Encountered a node without the required target attribute."); } std::string targetStr = source.getStringAttribute("target").toStdString(); - if (targetStr == "legato") target = LEGATO; - else if (targetStr == "velocity") target = VELOCITY; - else if (targetStr == "length") target = LENGTH; - else if (targetStr == "program") target = PROGRAM; - else { + if (targetStr == "legato") { + target = LEGATO; + } else if (targetStr == "velocity") { + target = VELOCITY; + } else if (targetStr == "length") { + target = LENGTH; + } else if (targetStr == "program") { + target = PROGRAM; + } else { if (targetStr.starts_with("CC")) { std::string trimmed = targetStr.substr(2, targetStr.length()); targetNumber = std::stoi(trimmed); target = CC; - } - else { + } else { try { targetNumber = ConfigParserUtil::keyNameToNumber(targetStr, 3); target = NOTE; - } - catch (std::exception& e) { + } catch (std::exception& e) { throw std::exception("Encountered a node target attribute with an invalid value."); } } @@ -40,8 +41,7 @@ InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source) IInputTreeNode* child = InputTreeNodeFactory::make(*caseChildElement); std::get<1>(children[insertIndex]).emplace_back(child); } - } - else { + } else { int insertIndex = children.size(); children.emplace_back(std::make_tuple(InputTreeCase(*caseEntryElement), std::vector>())); @@ -51,8 +51,7 @@ InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source) } } -NoteContext& InputTreeSwitchNode::visit(NoteContext& context) -{ +NoteContext& InputTreeSwitchNode::visit(NoteContext& context) { for (const auto& cases : children) { InputTreeCase condition = std::get<0>(cases); @@ -66,27 +65,25 @@ NoteContext& InputTreeSwitchNode::visit(NoteContext& context) return context; } -int InputTreeSwitchNode::getTargetValue(NoteContext& context) -{ - switch (target) - { - case VELOCITY: - return context.getVelocity(); - break; - case LEGATO: - return context.isLegato(); - break; - case LENGTH: - return context.getLength().has_value() ? context.getLength().value() % INT_MAX : INT_MAX; - break; - case CC: - return context.getCCValue(targetNumber); - break; - case NOTE: - return context.getHeldNoteVelocity(targetNumber); - break; - case PROGRAM: - return context.getActiveProgram(); - break; +int InputTreeSwitchNode::getTargetValue(NoteContext& context) { + switch (target) { + case VELOCITY: + return context.getVelocity(); + break; + case LEGATO: + return context.isLegato(); + break; + case LENGTH: + return context.getLength().has_value() ? context.getLength().value() % INT_MAX : INT_MAX; + break; + case CC: + return context.getCCValue(targetNumber); + break; + case NOTE: + return context.getHeldNoteVelocity(targetNumber); + break; + case PROGRAM: + return context.getActiveProgram(); + break; } } diff --git a/Source/InputTreeSwitchNode.h b/Source/InputTreeSwitchNode.h index c6d00cf..bdfc1b3 100644 --- a/Source/InputTreeSwitchNode.h +++ b/Source/InputTreeSwitchNode.h @@ -1,19 +1,25 @@ #pragma once -#include "JuceHeader.h" #include "IInputTreeNode.h" #include "InputTreeCase.h" +#include "JuceHeader.h" class InputTreeSwitchNode : public IInputTreeNode { -public: - InputTreeSwitchNode(const juce::XmlElement& source); - NoteContext& visit(NoteContext& context) override; -private: - enum TargetType { CC, VELOCITY, LEGATO, LENGTH, NOTE, PROGRAM }; + public: + InputTreeSwitchNode(const juce::XmlElement& source); + NoteContext& visit(NoteContext& context) override; + + private: + enum TargetType { CC, + VELOCITY, + LEGATO, + LENGTH, + NOTE, + PROGRAM }; - std::vector>>> children; - int targetNumber; - TargetType target; + std::vector>>> children; + int targetNumber; + TargetType target; - int getTargetValue(NoteContext& context); + int getTargetValue(NoteContext& context); }; \ No newline at end of file diff --git a/Source/InputTreeTagNode.cpp b/Source/InputTreeTagNode.cpp index 44f43a7..836d1d6 100644 --- a/Source/InputTreeTagNode.cpp +++ b/Source/InputTreeTagNode.cpp @@ -1,12 +1,10 @@ #include "InputTreeTagNode.h" -InputTreeTagNode::InputTreeTagNode(const juce::XmlElement& source) -{ - tag = source.getText().trim().toStdString(); +InputTreeTagNode::InputTreeTagNode(const juce::XmlElement& source) { + tag = source.getText().trim().toStdString(); } -NoteContext& InputTreeTagNode::visit(NoteContext& context) -{ - context.addTag(tag); - return context; +NoteContext& InputTreeTagNode::visit(NoteContext& context) { + context.addTag(tag); + return context; } diff --git a/Source/InputTreeTagNode.h b/Source/InputTreeTagNode.h index a5a7946..6611eb3 100644 --- a/Source/InputTreeTagNode.h +++ b/Source/InputTreeTagNode.h @@ -1,12 +1,13 @@ #pragma once -#include "JuceHeader.h" #include "IInputTreeNode.h" +#include "JuceHeader.h" class InputTreeTagNode : public IInputTreeNode { -public: - InputTreeTagNode(const juce::XmlElement& source); - NoteContext& visit(NoteContext& context) override; -private: - std::string tag; + public: + InputTreeTagNode(const juce::XmlElement& source); + NoteContext& visit(NoteContext& context) override; + + private: + std::string tag; }; \ No newline at end of file diff --git a/Source/NoteContext.cpp b/Source/NoteContext.cpp index 65712c9..447f4be 100644 --- a/Source/NoteContext.cpp +++ b/Source/NoteContext.cpp @@ -1,57 +1,46 @@ #include "NoteContext.h" -NoteContext::NoteContext(BufferedNote* note, const std::optional& previousNote, int ccStates[128], int heldNotes[128], int program) -{ - this->note = note; - this->previousNote = previousNote; - this->ccStates = ccStates; - this->heldNotes = heldNotes; - this->program = program; +NoteContext::NoteContext(BufferedNote* note, const std::optional& previousNote, int ccStates[128], int heldNotes[128], int program) { + this->note = note; + this->previousNote = previousNote; + this->ccStates = ccStates; + this->heldNotes = heldNotes; + this->program = program; } -int NoteContext::getVelocity() const -{ - return note->velocity; +int NoteContext::getVelocity() const { + return note->velocity; } -int NoteContext::getCCValue(const int number) const -{ - return ccStates[number]; +int NoteContext::getCCValue(const int number) const { + return ccStates[number]; } -int NoteContext::getActiveProgram() const -{ - return program; +int NoteContext::getActiveProgram() const { + return program; } -int NoteContext::getHeldNoteVelocity(const int number) const -{ - return heldNotes[number]; +int NoteContext::getHeldNoteVelocity(const int number) const { + return heldNotes[number]; } -bool NoteContext::isLegato() const -{ - // 64 samples should definitely be enough to catch a legato even on inaccurate DAWs without creating unintentional legato transitions - return previousNote.has_value() && previousNote->allowLegato && llabs(note->startTime - previousNote->endTime.value()) <= 64; +bool NoteContext::isLegato() const { + // 64 samples should definitely be enough to catch a legato even on inaccurate DAWs without creating unintentional legato transitions + return previousNote.has_value() && previousNote->allowLegato && llabs(note->startTime - previousNote->endTime.value()) <= 64; } -std::optional NoteContext::getLength() const -{ - return note->endTime.has_value() ? note->endTime.value() - note->startTime : std::optional(); +std::optional NoteContext::getLength() const { + return note->endTime.has_value() ? note->endTime.value() - note->startTime : std::optional(); } -void NoteContext::addTag(const std::string& name) -{ - tags.insert(name); +void NoteContext::addTag(const std::string& name) { + tags.insert(name); } -std::unordered_set NoteContext::getTags() const -{ - return tags; +std::unordered_set NoteContext::getTags() const { + return tags; } -BufferedNote* NoteContext::getNote() const -{ - return note; +BufferedNote* NoteContext::getNote() const { + return note; } - diff --git a/Source/NoteContext.h b/Source/NoteContext.h index 0da0669..2d290c1 100644 --- a/Source/NoteContext.h +++ b/Source/NoteContext.h @@ -1,28 +1,29 @@ #pragma once -#include "JuceHeader.h" #include "BufferedNote.h" +#include "JuceHeader.h" class NoteContext { -public: - NoteContext(BufferedNote* note, const std::optional& previousNote, int ccStates[128], int heldNotes[128], int program); + public: + NoteContext(BufferedNote* note, const std::optional& previousNote, int ccStates[128], int heldNotes[128], int program); + + int getVelocity() const; + int getCCValue(const int number) const; + int getActiveProgram() const; + int getHeldNoteVelocity(const int number) const; + bool isLegato() const; + std::optional getLength() const; - int getVelocity() const; - int getCCValue(const int number) const; - int getActiveProgram() const; - int getHeldNoteVelocity(const int number) const; - bool isLegato() const; - std::optional getLength() const; + void addTag(const std::string& name); + std::unordered_set getTags() const; - void addTag(const std::string& name); - std::unordered_set getTags() const; + BufferedNote* getNote() const; - BufferedNote* getNote() const; -private: - BufferedNote* note; - std::optional previousNote; - std::unordered_set tags; - int* ccStates; - int* heldNotes; - int program; + private: + BufferedNote* note; + std::optional previousNote; + std::unordered_set tags; + int* ccStates; + int* heldNotes; + int program; }; \ No newline at end of file diff --git a/Source/NoteProcessor.cpp b/Source/NoteProcessor.cpp index 85aeede..5e7edac 100644 --- a/Source/NoteProcessor.cpp +++ b/Source/NoteProcessor.cpp @@ -1,90 +1,81 @@ #include "NoteProcessor.h" -NoteProcessor::NoteProcessor(const NoteContext& note, Configuration* configuration, const std::unordered_set& tags, int channel) -{ - this->target = note.getNote(); - this->sampleRate = configuration->getSampleRate(); - this->channel = channel; - - for (const auto& tag : tags) { - for (auto& node : configuration->getOutputNodes(tag)) { - switch (node.getTargetType()) { - case OutputListNode::CC: - addBeforeNote(juce::MidiMessage::controllerEvent(channel, node.getCCNumber(), node.getValue(note))); - break; - case OutputListNode::NOTE: - addBeforeNote(juce::MidiMessage::noteOn(channel, node.getValue(note), juce::uint8(64))); - addAfterNote(juce::MidiMessage::noteOff(channel, node.getValue(note))); - break; - case OutputListNode::PROGRAM: - addBeforeNote(juce::MidiMessage::programChange(channel, node.getValue(note))); - break; - case OutputListNode::START: - addStartDelay(node.getValue(note)); - break; - case OutputListNode::END: - addEndDelay(node.getValue(note)); - break; - case OutputListNode::LEGATO: - note.getNote()->allowLegato = true; - break; - } - } - } +NoteProcessor::NoteProcessor(const NoteContext& note, Configuration* configuration, const std::unordered_set& tags, int channel) { + this->target = note.getNote(); + this->sampleRate = configuration->getSampleRate(); + this->channel = channel; + + for (const auto& tag : tags) { + for (auto& node : configuration->getOutputNodes(tag)) { + switch (node.getTargetType()) { + case OutputListNode::CC: + addBeforeNote(juce::MidiMessage::controllerEvent(channel, node.getCCNumber(), node.getValue(note))); + break; + case OutputListNode::NOTE: + addBeforeNote(juce::MidiMessage::noteOn(channel, node.getValue(note), juce::uint8(64))); + addAfterNote(juce::MidiMessage::noteOff(channel, node.getValue(note))); + break; + case OutputListNode::PROGRAM: + addBeforeNote(juce::MidiMessage::programChange(channel, node.getValue(note))); + break; + case OutputListNode::START: + addStartDelay(node.getValue(note)); + break; + case OutputListNode::END: + addEndDelay(node.getValue(note)); + break; + case OutputListNode::LEGATO: + note.getNote()->allowLegato = true; + break; + } + } + } } -void NoteProcessor::addStartDelay(double delay) -{ - startDelaySamples += (delay / 1000) * sampleRate; +void NoteProcessor::addStartDelay(double delay) { + startDelaySamples += (delay / 1000) * sampleRate; } -void NoteProcessor::addEndDelay(double delay) -{ - endDelaySamples += (delay / 1000) * sampleRate; +void NoteProcessor::addEndDelay(double delay) { + endDelaySamples += (delay / 1000) * sampleRate; } -void NoteProcessor::addBeforeNote(juce::MidiMessage message) -{ - message.setChannel(channel); - beforeNoteMessages.emplace_back(message); +void NoteProcessor::addBeforeNote(juce::MidiMessage message) { + message.setChannel(channel); + beforeNoteMessages.emplace_back(message); } -void NoteProcessor::addAfterNote(juce::MidiMessage message) -{ - message.setChannel(channel); - afterNoteMessages.emplace_back(message); +void NoteProcessor::addAfterNote(juce::MidiMessage message) { + message.setChannel(channel); + afterNoteMessages.emplace_back(message); } -void NoteProcessor::applyStartDelay() -{ - target->startTime += startDelaySamples; +void NoteProcessor::applyStartDelay() { + target->startTime += startDelaySamples; } -int NoteProcessor::getStartDelaySamples() -{ - return startDelaySamples; +int NoteProcessor::getStartDelaySamples() { + return startDelaySamples; } -int NoteProcessor::getEndDelaySamples() -{ - return endDelaySamples; +int NoteProcessor::getEndDelaySamples() { + return endDelaySamples; } -std::vector NoteProcessor::getResults() -{ - target->endDelay += endDelaySamples; +std::vector NoteProcessor::getResults() { + target->endDelay += endDelaySamples; - std::vector results = std::vector(); + std::vector results = std::vector(); - for (const auto& message : beforeNoteMessages) { - results.emplace_back(message); - } + for (const auto& message : beforeNoteMessages) { + results.emplace_back(message); + } - results.emplace_back(juce::MidiMessage::noteOn(channel, target->pitch, (juce::uint8)target->velocity)); + results.emplace_back(juce::MidiMessage::noteOn(channel, target->pitch, (juce::uint8)target->velocity)); - for (const auto& message : afterNoteMessages) { - results.emplace_back(message); - } + for (const auto& message : afterNoteMessages) { + results.emplace_back(message); + } - return results; + return results; } diff --git a/Source/NoteProcessor.h b/Source/NoteProcessor.h index 9d2a3ff..67b2bdf 100644 --- a/Source/NoteProcessor.h +++ b/Source/NoteProcessor.h @@ -1,32 +1,33 @@ #pragma once -#include "JuceHeader.h" #include "BufferedNote.h" #include "Configuration.h" +#include "JuceHeader.h" class NoteProcessor { -public: - NoteProcessor(const NoteContext& note, Configuration* configuration, const std::unordered_set& tags, int channel); + public: + NoteProcessor(const NoteContext& note, Configuration* configuration, const std::unordered_set& tags, int channel); + + void applyStartDelay(); + int getStartDelaySamples(); + int getEndDelaySamples(); - void applyStartDelay(); - int getStartDelaySamples(); - int getEndDelaySamples(); + std::vector getResults(); - std::vector getResults(); -private: - double sampleRate; - int channel; + private: + double sampleRate; + int channel; - int startDelaySamples = 0; - int endDelaySamples = 0; + int startDelaySamples = 0; + int endDelaySamples = 0; - BufferedNote* target; - std::vector beforeNoteMessages; - std::vector afterNoteMessages; + BufferedNote* target; + std::vector beforeNoteMessages; + std::vector afterNoteMessages; - void addStartDelay(double delay); - void addEndDelay(double delay); + void addStartDelay(double delay); + void addEndDelay(double delay); - void addBeforeNote(juce::MidiMessage message); - void addAfterNote(juce::MidiMessage message); + void addBeforeNote(juce::MidiMessage message); + void addAfterNote(juce::MidiMessage message); }; \ No newline at end of file diff --git a/Source/OutputListNode.cpp b/Source/OutputListNode.cpp index a9ea28e..c689c00 100644 --- a/Source/OutputListNode.cpp +++ b/Source/OutputListNode.cpp @@ -1,38 +1,39 @@ #include "OutputListNode.h" #include "ConfigParserUtil.h" -OutputListNode::OutputListNode() -{ +OutputListNode::OutputListNode() { target = END; value = 0; } -OutputListNode::OutputListNode(const juce::XmlElement& source) -{ - std::string targetStr = source.getStringAttribute("target").toStdString(); - std::string valueStr = source.getAllSubText().toStdString(); +OutputListNode::OutputListNode(const juce::XmlElement& source) { + std::string targetStr = source.getStringAttribute("target").toStdString(); + std::string valueStr = source.getAllSubText().toStdString(); - if (targetStr == "note") target = NOTE; - else if (targetStr == "start") target = START; - else if (targetStr == "end") target = END; - else if (targetStr == "legato") target = LEGATO; - else if (targetStr == "program") target = PROGRAM; - else { + if (targetStr == "note") { + target = NOTE; + } else if (targetStr == "start") { + target = START; + } else if (targetStr == "end") { + target = END; + } else if (targetStr == "legato") { + target = LEGATO; + } else if (targetStr == "program") { + target = PROGRAM; + } else { if (targetStr.starts_with("CC")) { std::string trimmed = targetStr.substr(2, targetStr.length()); ccNumber = std::stoi(trimmed); target = CC; - } - else { + } else { throw std::exception("Encountered a node target attribute with an invalid value."); } } if (target == NOTE) { value = ConfigParserUtil::keyNameToNumber(juce::String(valueStr), 3); - } - else if (target != LEGATO) { + } else if (target != LEGATO) { if (valueStr.starts_with("CC")) { int multiplierStartIndex = valueStr.find('*'); @@ -42,35 +43,29 @@ OutputListNode::OutputListNode(const juce::XmlElement& source) std::string multiplierStr = valueStr.substr(multiplierStartIndex + 1, valueStr.length()); readValueMultiplier = std::stof(multiplierStr); - } - else { + } else { std::string trimmed = valueStr.substr(2, valueStr.length()); readCC = std::stoi(trimmed); } - } - else { + } else { value = std::stoi(valueStr); } } } -OutputListNode::TargetType OutputListNode::getTargetType() -{ +OutputListNode::TargetType OutputListNode::getTargetType() { return target; } -int OutputListNode::getCCNumber() -{ +int OutputListNode::getCCNumber() { return ccNumber; } -int OutputListNode::getValue(const NoteContext& context) -{ +int OutputListNode::getValue(const NoteContext& context) { if (readCC.has_value()) { int output = std::round(context.getCCValue(readCC.value()) * readValueMultiplier.value_or(1.0f)); return output; - } - else { + } else { return value; } } \ No newline at end of file diff --git a/Source/OutputListNode.h b/Source/OutputListNode.h index 0ae924a..c36dbeb 100644 --- a/Source/OutputListNode.h +++ b/Source/OutputListNode.h @@ -1,23 +1,28 @@ #pragma once -#include "JuceHeader.h" #include "BufferedNote.h" +#include "JuceHeader.h" #include "NoteContext.h" class OutputListNode { -public: - OutputListNode(); - OutputListNode(const juce::XmlElement& source); + public: + OutputListNode(); + OutputListNode(const juce::XmlElement& source); - enum TargetType { CC, NOTE, START, END, LEGATO, PROGRAM }; - TargetType getTargetType(); - int getCCNumber(); - int getValue(const NoteContext& context); + enum TargetType { CC, + NOTE, + START, + END, + LEGATO, + PROGRAM }; + TargetType getTargetType(); + int getCCNumber(); + int getValue(const NoteContext& context); -private: - TargetType target; - int ccNumber = 0; - int value = 0; - std::optional readCC; - std::optional readValueMultiplier; + private: + TargetType target; + int ccNumber = 0; + int value = 0; + std::optional readCC; + std::optional readValueMultiplier; }; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index eac53fb..05c41d7 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -6,13 +6,12 @@ ============================================================================== */ -#include "PluginProcessor.h" #include "PluginEditor.h" +#include "PluginProcessor.h" //============================================================================== -ForesightAudioProcessorEditor::ForesightAudioProcessorEditor (ForesightAudioProcessor& p) - : AudioProcessorEditor (&p), audioProcessor (p) -{ +ForesightAudioProcessorEditor::ForesightAudioProcessorEditor(ForesightAudioProcessor& p) : + AudioProcessorEditor(&p), audioProcessor(p) { addChildComponent(tabbedComponent); tabbedComponent.setVisible(true); @@ -38,36 +37,31 @@ ForesightAudioProcessorEditor::ForesightAudioProcessorEditor (ForesightAudioProc setSize(audioProcessor.currentWindowWidth, audioProcessor.currentWindowHeight); } -ForesightAudioProcessorEditor::~ForesightAudioProcessorEditor() -{ +ForesightAudioProcessorEditor::~ForesightAudioProcessorEditor() { audioProcessor.currentWindowWidth = getWidth(); audioProcessor.currentWindowHeight = getHeight(); } //============================================================================== -void ForesightAudioProcessorEditor::paint(juce::Graphics& g) -{ +void ForesightAudioProcessorEditor::paint(juce::Graphics& g) { g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); } -void ForesightAudioProcessorEditor::resized() -{ +void ForesightAudioProcessorEditor::resized() { tabbedComponent.setBoundsRelative(0, 0, 1, 1); } -void ForesightAudioProcessorEditor::updateGui(const std::string& configName, const std::string& configLatency, const std::string& configSource) -{ +void ForesightAudioProcessorEditor::updateGui(const std::string& configName, const std::string& configLatency, const std::string& configSource) { if (mainComponent) { mainComponent->setDisplayedConfigName(configName); mainComponent->setDisplayedLatency(configLatency); } - + if (editorComponent) { editorComponent->setDisplayedConfig(configSource); } } -void ForesightAudioProcessorEditor::applyButtonClicked(const std::string& configString) -{ +void ForesightAudioProcessorEditor::applyButtonClicked(const std::string& configString) { audioProcessor.setConfiguration(configString); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 356aa88..975aebe 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -8,36 +8,34 @@ #pragma once -#include -#include "PluginProcessor.h" -#include "GuiMainComponent.h" #include "GuiEditorComponent.h" +#include "GuiMainComponent.h" +#include "PluginProcessor.h" +#include //============================================================================== /** -*/ -class ForesightAudioProcessorEditor : public juce::AudioProcessorEditor -{ -public: - ForesightAudioProcessorEditor (ForesightAudioProcessor&); + */ +class ForesightAudioProcessorEditor : public juce::AudioProcessorEditor { + public: + ForesightAudioProcessorEditor(ForesightAudioProcessor&); ~ForesightAudioProcessorEditor() override; //============================================================================== - void paint (juce::Graphics&) override; + void paint(juce::Graphics&) override; void resized() override; void updateGui(const std::string& configName, const std::string& configLatency, const std::string& configSource); -private: + private: // This reference is provided as a quick way for your editor to // access the processor object that created it. ForesightAudioProcessor& audioProcessor; - juce::TabbedComponent tabbedComponent = juce::TabbedComponent(juce::TabbedButtonBar::Orientation::TabsAtTop); std::unique_ptr mainComponent; std::unique_ptr editorComponent; void applyButtonClicked(const std::string& configString); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ForesightAudioProcessorEditor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ForesightAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6d5ff57..c8ebce9 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -12,13 +12,14 @@ //============================================================================== ForesightAudioProcessor::ForesightAudioProcessor() #ifndef JucePlugin_PreferredChannelConfigurations - : AudioProcessor(BusesProperties() -#if ! JucePlugin_IsMidiEffect -#if ! JucePlugin_IsSynth - .withInput("Input", juce::AudioChannelSet::stereo(), true) -#endif - .withOutput("Output", juce::AudioChannelSet::stereo(), true) -#endif + : + AudioProcessor(BusesProperties() + #if !JucePlugin_IsMidiEffect + #if !JucePlugin_IsSynth + .withInput("Input", juce::AudioChannelSet::stereo(), true) + #endif + .withOutput("Output", juce::AudioChannelSet::stereo(), true) + #endif ) #endif { @@ -27,93 +28,81 @@ ForesightAudioProcessor::ForesightAudioProcessor() voiceManager = std::make_unique(); voiceProcessors.reserve(16); - if(!configuration) setDefaultConfiguration(); + if (!configuration) { + setDefaultConfiguration(); + } } -ForesightAudioProcessor::~ForesightAudioProcessor() -{ +ForesightAudioProcessor::~ForesightAudioProcessor() { } //============================================================================== -const juce::String ForesightAudioProcessor::getName() const -{ +const juce::String ForesightAudioProcessor::getName() const { return JucePlugin_Name; } -bool ForesightAudioProcessor::acceptsMidi() const -{ - #if JucePlugin_WantsMidiInput +bool ForesightAudioProcessor::acceptsMidi() const { +#if JucePlugin_WantsMidiInput return true; - #else +#else return false; - #endif +#endif } -bool ForesightAudioProcessor::producesMidi() const -{ - #if JucePlugin_ProducesMidiOutput +bool ForesightAudioProcessor::producesMidi() const { +#if JucePlugin_ProducesMidiOutput return true; - #else +#else return false; - #endif +#endif } -bool ForesightAudioProcessor::isMidiEffect() const -{ - #if JucePlugin_IsMidiEffect +bool ForesightAudioProcessor::isMidiEffect() const { +#if JucePlugin_IsMidiEffect return true; - #else +#else return false; - #endif +#endif } -double ForesightAudioProcessor::getTailLengthSeconds() const -{ +double ForesightAudioProcessor::getTailLengthSeconds() const { return 0.0; } -int ForesightAudioProcessor::getNumPrograms() -{ - return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, - // so this should be at least 1, even if you're not really implementing programs. +int ForesightAudioProcessor::getNumPrograms() { + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. } -int ForesightAudioProcessor::getCurrentProgram() -{ +int ForesightAudioProcessor::getCurrentProgram() { return 0; } -void ForesightAudioProcessor::setCurrentProgram (int index) -{ +void ForesightAudioProcessor::setCurrentProgram(int index) { } -const juce::String ForesightAudioProcessor::getProgramName (int index) -{ +const juce::String ForesightAudioProcessor::getProgramName(int index) { return {}; } -void ForesightAudioProcessor::changeProgramName (int index, const juce::String& newName) -{ +void ForesightAudioProcessor::changeProgramName(int index, const juce::String& newName) { } //============================================================================== -void ForesightAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) -{ +void ForesightAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { lastPlayingState = false; lastSampleRate = sampleRate; configuration->updateSampleRate(lastSampleRate); setLatencySamples(configuration->getLatencySamples()); - for (size_t i = 0; i < 16; i++) - { + for (size_t i = 0; i < 16; i++) { VoiceProcessor& processor = voiceProcessors.emplace_back(); processor.updateConfiguration(configuration.get()); } } -void ForesightAudioProcessor::releaseResources() -{ +void ForesightAudioProcessor::releaseResources() { lastPlayingState = false; voiceManager->reset(); @@ -122,33 +111,32 @@ void ForesightAudioProcessor::releaseResources() } #ifndef JucePlugin_PreferredChannelConfigurations -bool ForesightAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const -{ - #if JucePlugin_IsMidiEffect - juce::ignoreUnused (layouts); +bool ForesightAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const { + #if JucePlugin_IsMidiEffect + juce::ignoreUnused(layouts); return true; - #else + #else // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. // Some plugin hosts, such as certain GarageBand versions, will only // load plugins that support stereo bus layouts. - if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() - && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) { return false; + } - // This checks if the input layout matches the output layout - #if ! JucePlugin_IsSynth - if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) + // This checks if the input layout matches the output layout + #if !JucePlugin_IsSynth + if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) { return false; - #endif + } + #endif return true; - #endif + #endif } #endif -void ForesightAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& inputMidi) -{ +void ForesightAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& inputMidi) { isCurrentlyInsideProcessBlock = true; auto playheadInfo = getPlayHead()->getPosition(); @@ -161,17 +149,15 @@ void ForesightAudioProcessor::processBlock (juce::AudioBuffer& buffer, ju clearState(); juce::MidiBuffer noteStopBuffer; - for (int i = 0; i < 16; i++) - { + for (int i = 0; i < 16; i++) { noteStopBuffer.addEvent(juce::MidiMessage::allNotesOff(i + 1), 0); } inputMidi.swapWith(noteStopBuffer); // Also send note off if it was stopped - } - else if (isPlaying && !lastPlayingState) { + } else if (isPlaying && !lastPlayingState) { clearState(); - } - + } + #if DEBUG if (true) #else @@ -184,15 +170,13 @@ void ForesightAudioProcessor::processBlock (juce::AudioBuffer& buffer, ju // Process each voice seperately juce::MidiBuffer channelBuffers[16]; - for (int i = 0; i < 16; i++) - { + for (int i = 0; i < 16; i++) { channelBuffers[i] = voiceProcessors[i].processBuffer(splitBuffer, i + 1, buffer.getNumSamples(), false); } // Merge the buffers juce::MidiBuffer mergedOutputBuffer; - for (const juce::MidiBuffer& channelBuffer : channelBuffers) - { + for (const juce::MidiBuffer& channelBuffer : channelBuffers) { mergedOutputBuffer.addEvents(channelBuffer, 0, -1, 0); } @@ -204,8 +188,7 @@ void ForesightAudioProcessor::processBlock (juce::AudioBuffer& buffer, ju isCurrentlyInsideProcessBlock = false; } -void ForesightAudioProcessor::processBlockBypassed(juce::AudioBuffer& buffer, juce::MidiBuffer& inputMidi) -{ +void ForesightAudioProcessor::processBlockBypassed(juce::AudioBuffer& buffer, juce::MidiBuffer& inputMidi) { isCurrentlyInsideProcessBlock = true; auto playheadInfo = getPlayHead()->getPosition(); @@ -218,14 +201,12 @@ void ForesightAudioProcessor::processBlockBypassed(juce::AudioBuffer& buf clearState(); juce::MidiBuffer noteStopBuffer; - for (int i = 0; i < 16; i++) - { + for (int i = 0; i < 16; i++) { noteStopBuffer.addEvent(juce::MidiMessage::allNotesOff(i + 1), 0); } inputMidi.swapWith(noteStopBuffer); // Also send note off if it was stopped - } - else if (isPlaying && !lastPlayingState) { + } else if (isPlaying && !lastPlayingState) { clearState(); } @@ -241,15 +222,13 @@ void ForesightAudioProcessor::processBlockBypassed(juce::AudioBuffer& buf // Process each voice seperately juce::MidiBuffer channelBuffers[16]; - for (int i = 0; i < 16; i++) - { + for (int i = 0; i < 16; i++) { channelBuffers[i] = voiceProcessors[i].processBuffer(splitBuffer, i + 1, buffer.getNumSamples(), true); } // Merge the buffers juce::MidiBuffer mergedOutputBuffer; - for (const juce::MidiBuffer& channelBuffer : channelBuffers) - { + for (const juce::MidiBuffer& channelBuffer : channelBuffers) { mergedOutputBuffer.addEvents(channelBuffer, 0, -1, 0); } @@ -262,23 +241,20 @@ void ForesightAudioProcessor::processBlockBypassed(juce::AudioBuffer& buf } //============================================================================== -bool ForesightAudioProcessor::hasEditor() const -{ +bool ForesightAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } -juce::AudioProcessorEditor* ForesightAudioProcessor::createEditor() -{ +juce::AudioProcessorEditor* ForesightAudioProcessor::createEditor() { isCreatingEditor = true; - ForesightAudioProcessorEditor* editor = new ForesightAudioProcessorEditor (*this); + ForesightAudioProcessorEditor* editor = new ForesightAudioProcessorEditor(*this); isCreatingEditor = false; return editor; } //============================================================================== -void ForesightAudioProcessor::getStateInformation (juce::MemoryBlock& destData) -{ +void ForesightAudioProcessor::getStateInformation(juce::MemoryBlock& destData) { std::unique_ptr state = std::make_unique("foresight-state"); state->setAttribute("version", CURRENT_STATE_VERSION); @@ -299,13 +275,11 @@ void ForesightAudioProcessor::getStateInformation (juce::MemoryBlock& destData) copyXmlToBinary(*state, destData); } -void ForesightAudioProcessor::setStateInformation (const void* data, int sizeInBytes) -{ +void ForesightAudioProcessor::setStateInformation(const void* data, int sizeInBytes) { std::unique_ptr state(getXmlFromBinary(data, sizeInBytes)); // Check if the state is valid - if (state && state->getTagName() == "foresight-state" && std::stoi(state->getAttributeValue(0).toStdString()) <= CURRENT_STATE_VERSION) - { + if (state && state->getTagName() == "foresight-state" && std::stoi(state->getAttributeValue(0).toStdString()) <= CURRENT_STATE_VERSION) { std::string source = state->getChildByName("source")->getAllSubText().toStdString(); setConfiguration(source); @@ -313,40 +287,40 @@ void ForesightAudioProcessor::setStateInformation (const void* data, int sizeInB if (windowSizeElement) { currentWindowWidth = windowSizeElement->getIntAttribute("width"); currentWindowHeight = windowSizeElement->getIntAttribute("height"); - } - else { + } else { currentWindowWidth = 400; currentWindowHeight = 400; } - } - else { + } else { setDefaultConfiguration(); } } -bool ForesightAudioProcessor::setConfiguration(const std::string& configurationXml) -{ +bool ForesightAudioProcessor::setConfiguration(const std::string& configurationXml) { bool success = true; suspendProcessing(true); - while (isCurrentlyInsideProcessBlock) { } // Wait until the process block has finished + while (isCurrentlyInsideProcessBlock) {} // Wait until the process block has finished try { configuration = std::make_unique(configurationXml); configuration->updateSampleRate(lastSampleRate); voiceManager->updateConfiguration(configuration.get()); - for (auto& processor : voiceProcessors) processor.updateConfiguration(configuration.get()); + for (auto& processor : voiceProcessors) { + processor.updateConfiguration(configuration.get()); + } setLatencySamples(configuration->getLatencySamples()); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { configuration = std::make_unique(); configuration->updateSampleRate(lastSampleRate); voiceManager->updateConfiguration(configuration.get()); - for (auto& processor : voiceProcessors) processor.updateConfiguration(configuration.get()); + for (auto& processor : voiceProcessors) { + processor.updateConfiguration(configuration.get()); + } setLatencySamples(configuration->getLatencySamples()); @@ -361,8 +335,7 @@ bool ForesightAudioProcessor::setConfiguration(const std::string& configurationX return success; } -void ForesightAudioProcessor::setDefaultConfiguration() -{ +void ForesightAudioProcessor::setDefaultConfiguration() { suspendProcessing(true); configuration = std::make_unique(); @@ -372,36 +345,30 @@ void ForesightAudioProcessor::setDefaultConfiguration() updateGui(); } - - -Configuration& ForesightAudioProcessor::getConfiguration() -{ +Configuration& ForesightAudioProcessor::getConfiguration() { return *configuration; } -void ForesightAudioProcessor::updateGui() -{ +void ForesightAudioProcessor::updateGui() { bool editorAvailable = (getActiveEditor() || isCreatingEditor); - if (updateGuiCallback && editorAvailable) updateGuiCallback(configuration->getName(), "Latency: " + std::to_string(configuration->getLatencySeconds()) + "s", configuration->getSourceXML()); + if (updateGuiCallback && editorAvailable) { + updateGuiCallback(configuration->getName(), "Latency: " + std::to_string(configuration->getLatencySeconds()) + "s", configuration->getSourceXML()); + } } -void ForesightAudioProcessor::setUpdateGuiCallback(const std::function& callback) -{ +void ForesightAudioProcessor::setUpdateGuiCallback(const std::function& callback) { updateGuiCallback = callback; } -void ForesightAudioProcessor::clearState() -{ +void ForesightAudioProcessor::clearState() { voiceManager->reset(); - for (auto& voiceProcessor : voiceProcessors) - { + for (auto& voiceProcessor : voiceProcessors) { voiceProcessor.reset(); } } //============================================================================== // This creates new instances of the plugin.. -juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new ForesightAudioProcessor(); } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index c223ebc..23fc763 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -8,37 +8,38 @@ #pragma once -#include -#include "VoiceProcessor.h" -#include "VoiceManager.h" -#include "Versioning.h" #include "Configuration.h" +#include "Versioning.h" +#include "VoiceManager.h" +#include "VoiceProcessor.h" +#include #define OUTPUT_CHANNEL_COUNT 16 //============================================================================== /** -*/ -class ForesightAudioProcessor : public juce::AudioProcessor - #if JucePlugin_Enable_ARA - , public juce::AudioProcessorARAExtension - #endif + */ +class ForesightAudioProcessor : public juce::AudioProcessor +#if JucePlugin_Enable_ARA + , + public juce::AudioProcessorARAExtension +#endif { -public: + public: //============================================================================== ForesightAudioProcessor(); ~ForesightAudioProcessor() override; //============================================================================== - void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void prepareToPlay(double sampleRate, int samplesPerBlock) override; void releaseResources() override; - #ifndef JucePlugin_PreferredChannelConfigurations - bool isBusesLayoutSupported (const BusesLayout& layouts) const override; - #endif +#ifndef JucePlugin_PreferredChannelConfigurations + bool isBusesLayoutSupported(const BusesLayout& layouts) const override; +#endif - void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; - void processBlockBypassed (juce::AudioBuffer&, juce::MidiBuffer&) override; + void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override; + void processBlockBypassed(juce::AudioBuffer&, juce::MidiBuffer&) override; //============================================================================== juce::AudioProcessorEditor* createEditor() override; @@ -55,13 +56,13 @@ class ForesightAudioProcessor : public juce::AudioProcessor //============================================================================== int getNumPrograms() override; int getCurrentProgram() override; - void setCurrentProgram (int index) override; - const juce::String getProgramName (int index) override; - void changeProgramName (int index, const juce::String& newName) override; + void setCurrentProgram(int index) override; + const juce::String getProgramName(int index) override; + void changeProgramName(int index, const juce::String& newName) override; //============================================================================== - void getStateInformation (juce::MemoryBlock& destData) override; - void setStateInformation (const void* data, int sizeInBytes) override; + void getStateInformation(juce::MemoryBlock& destData) override; + void setStateInformation(const void* data, int sizeInBytes) override; //============================================================================== bool setConfiguration(const std::string& configurationXml); @@ -69,12 +70,12 @@ class ForesightAudioProcessor : public juce::AudioProcessor Configuration& getConfiguration(); void updateGui(); - void setUpdateGuiCallback(const std::function& callback); + void setUpdateGuiCallback(const std::function& callback); int currentWindowWidth = 400; int currentWindowHeight = 400; -private: + private: double lastSampleRate = 0; std::unique_ptr configuration; @@ -85,10 +86,10 @@ class ForesightAudioProcessor : public juce::AudioProcessor void clearState(); bool isCreatingEditor = false; - std::function updateGuiCallback; + std::function updateGuiCallback; std::atomic_bool isCurrentlyInsideProcessBlock = false; //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ForesightAudioProcessor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ForesightAudioProcessor) }; diff --git a/Source/VoiceManager.cpp b/Source/VoiceManager.cpp index ea08823..c62ee62 100644 --- a/Source/VoiceManager.cpp +++ b/Source/VoiceManager.cpp @@ -1,95 +1,84 @@ #include "VoiceManager.h" -VoiceManager::VoiceManager() -{ +VoiceManager::VoiceManager() { } -VoiceManager::~VoiceManager() -{ +VoiceManager::~VoiceManager() { } -juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer) -{ - juce::MidiBuffer processedBuffer; - - // Group events by time - std::map> eventsPerSample; - for (const auto& metadata : buffer) { - auto message = metadata.getMessage(); - const auto time = metadata.samplePosition; - - if (!eventsPerSample.contains(time)) { - eventsPerSample[time] = std::vector(); - } - - eventsPerSample[time].emplace_back(message); - } - - // Process voices - // Just makes it strictly monophonic for now - for (auto& entry : eventsPerSample) { // For each group... - int time = entry.first; - - // ...copy over everything that's not a note or that's outside the range - for (auto& message : entry.second) - { - if (!message.isNoteOnOrOff() || (message.isNoteOnOrOff() && !configuration->isInRange(message.getNoteNumber()))) { - message.setChannel(1); - processedBuffer.addEvent(message, time); - } - } - - // ...look for a note-off first - for (auto& message : entry.second) - { - if (message.isNoteOff() && configuration->isInRange(message.getNoteNumber()) && heldNote.has_value() && heldNote == message.getNoteNumber()) { - heldNote.reset(); - - message.setChannel(1); - processedBuffer.addEvent(message, time); - - break; - } - } - - // ...handle note-on - for (auto& message : entry.second) - { - if (message.isNoteOn() && configuration->isInRange(message.getNoteNumber())) - { - // If there's already a playing note, stop it - if (heldNote.has_value()) - { - processedBuffer.addEvent(juce::MidiMessage::noteOff(1, heldNote.value()), time); - heldNote.reset(); - } - - // Play the new note - - heldNote = message.getNoteNumber(); - - message.setChannel(1); - processedBuffer.addEvent(message, time); - - break; - } - } - } - - return processedBuffer; +juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer) { + juce::MidiBuffer processedBuffer; + + // Group events by time + std::map> eventsPerSample; + for (const auto& metadata : buffer) { + auto message = metadata.getMessage(); + const auto time = metadata.samplePosition; + + if (!eventsPerSample.contains(time)) { + eventsPerSample[time] = std::vector(); + } + + eventsPerSample[time].emplace_back(message); + } + + // Process voices + // Just makes it strictly monophonic for now + for (auto& entry : eventsPerSample) { // For each group... + int time = entry.first; + + // ...copy over everything that's not a note or that's outside the range + for (auto& message : entry.second) { + if (!message.isNoteOnOrOff() || (message.isNoteOnOrOff() && !configuration->isInRange(message.getNoteNumber()))) { + message.setChannel(1); + processedBuffer.addEvent(message, time); + } + } + + // ...look for a note-off first + for (auto& message : entry.second) { + if (message.isNoteOff() && configuration->isInRange(message.getNoteNumber()) && heldNote.has_value() && heldNote == message.getNoteNumber()) { + heldNote.reset(); + + message.setChannel(1); + processedBuffer.addEvent(message, time); + + break; + } + } + + // ...handle note-on + for (auto& message : entry.second) { + if (message.isNoteOn() && configuration->isInRange(message.getNoteNumber())) { + // If there's already a playing note, stop it + if (heldNote.has_value()) { + processedBuffer.addEvent(juce::MidiMessage::noteOff(1, heldNote.value()), time); + heldNote.reset(); + } + + // Play the new note + + heldNote = message.getNoteNumber(); + + message.setChannel(1); + processedBuffer.addEvent(message, time); + + break; + } + } + } + + return processedBuffer; } -int VoiceManager::getCurrentVoiceCount() -{ - return 1; +int VoiceManager::getCurrentVoiceCount() { + return 1; } -void VoiceManager::reset() -{ - heldNote.reset(); +void VoiceManager::reset() { + heldNote.reset(); } -void VoiceManager::updateConfiguration(Configuration* configuration) -{ - this->configuration = configuration; +void VoiceManager::updateConfiguration(Configuration* configuration) { + this->configuration = configuration; } diff --git a/Source/VoiceManager.h b/Source/VoiceManager.h index 8e0ab29..824eb43 100644 --- a/Source/VoiceManager.h +++ b/Source/VoiceManager.h @@ -1,33 +1,34 @@ #pragma once -#include "JuceHeader.h" #include "Configuration.h" +#include "JuceHeader.h" class VoiceManager { -public: - VoiceManager(); - ~VoiceManager(); - /// - /// Returns a new MidiBuffer containing the events from the input buffer, - /// distributed across MIDI channels so that each channel is strictly monophonic. - /// - /// The input MidiBuffer - /// The processed buffer - juce::MidiBuffer processBuffer(const juce::MidiBuffer& buffer); + public: + VoiceManager(); + ~VoiceManager(); + /// + /// Returns a new MidiBuffer containing the events from the input buffer, + /// distributed across MIDI channels so that each channel is strictly monophonic. + /// + /// The input MidiBuffer + /// The processed buffer + juce::MidiBuffer processBuffer(const juce::MidiBuffer& buffer); + + /// + /// Gets the amount of currently playing voices after processing the last buffer. + /// + /// The amount of playing voices + int getCurrentVoiceCount(); - /// - /// Gets the amount of currently playing voices after processing the last buffer. - /// - /// The amount of playing voices - int getCurrentVoiceCount(); + /// + /// Resets the internal state of the VoiceManager. This should be called when playback is stopped. + /// + void reset(); - /// - /// Resets the internal state of the VoiceManager. This should be called when playback is stopped. - /// - void reset(); + void updateConfiguration(Configuration* configuration); - void updateConfiguration(Configuration* configuration); -private: - std::optional heldNote; - Configuration* configuration; + private: + std::optional heldNote; + Configuration* configuration; }; \ No newline at end of file diff --git a/Source/VoiceProcessor.cpp b/Source/VoiceProcessor.cpp index 24d5e84..db6fa3b 100644 --- a/Source/VoiceProcessor.cpp +++ b/Source/VoiceProcessor.cpp @@ -1,206 +1,212 @@ #include "VoiceProcessor.h" -#include "PluginProcessor.h" #include "NoteContext.h" #include "NoteProcessor.h" +#include "PluginProcessor.h" -VoiceProcessor::VoiceProcessor() -{ +VoiceProcessor::VoiceProcessor() { } -VoiceProcessor::VoiceProcessor(const VoiceProcessor& other) -{ - bufferSizeSamples = other.bufferSizeSamples; - readHeadPosition = other.readHeadPosition; +VoiceProcessor::VoiceProcessor(const VoiceProcessor& other) { + bufferSizeSamples = other.bufferSizeSamples; + readHeadPosition = other.readHeadPosition; - for (const auto& note : other.bufferedNotes) { - BufferedNote* newNote = new BufferedNote(*note); - bufferedNotes.emplace_back(newNote); - } + for (const auto& note : other.bufferedNotes) { + BufferedNote* newNote = new BufferedNote(*note); + bufferedNotes.emplace_back(newNote); + } - heldNoteAtWritePosition = bufferedNotes.back(); - lastWrittenNote = other.lastWrittenNote; - unprocessedBuffer = other.unprocessedBuffer; + heldNoteAtWritePosition = bufferedNotes.back(); + lastWrittenNote = other.lastWrittenNote; + unprocessedBuffer = other.unprocessedBuffer; } -VoiceProcessor::~VoiceProcessor() -{ - reset(); +VoiceProcessor::~VoiceProcessor() { + reset(); } -juce::MidiBuffer VoiceProcessor::processBuffer(const juce::MidiBuffer& buffer, int channel, int lengthSamples, bool bypassed) -{ - juce::MidiBuffer outputBuffer; +juce::MidiBuffer VoiceProcessor::processBuffer(const juce::MidiBuffer& buffer, int channel, int lengthSamples, bool bypassed) { + juce::MidiBuffer outputBuffer; - // Group events by samples - std::map> eventsPerSample; - for (const auto metadata : buffer) { - auto message = metadata.getMessage(); + // Group events by samples + std::map> eventsPerSample; + for (const auto metadata : buffer) { + auto message = metadata.getMessage(); - if (message.getChannel() != channel) continue; + if (message.getChannel() != channel) { + continue; + } - const auto time = metadata.samplePosition; + const auto time = metadata.samplePosition; - if (!eventsPerSample.contains(time)) { - eventsPerSample[time] = std::vector(); - } + if (!eventsPerSample.contains(time)) { + eventsPerSample[time] = std::vector(); + } - eventsPerSample[time].emplace_back(message); - } + eventsPerSample[time].emplace_back(message); + } - // Do processing for each sample in the buffer - for (int i = 0; i < lengthSamples; i++) - { - std::vector output; - if (eventsPerSample.contains(i)) { - output = processSample(std::optional>(eventsPerSample[i]), channel, bypassed); - } - else { - output = processSample(std::optional>(), channel, bypassed); - } + // Do processing for each sample in the buffer + for (int i = 0; i < lengthSamples; i++) { + std::vector output; + if (eventsPerSample.contains(i)) { + output = processSample(std::optional>(eventsPerSample[i]), channel, bypassed); + } else { + output = processSample(std::optional>(), channel, bypassed); + } - for (const auto message : output) { - outputBuffer.addEvent(message, i); - } - } + for (const auto message : output) { + outputBuffer.addEvent(message, i); + } + } - return outputBuffer; + return outputBuffer; } -void VoiceProcessor::reset() -{ - for (const auto& entry : bufferedNotes) { - delete(entry); - } +void VoiceProcessor::reset() { + for (const auto& entry : bufferedNotes) { + delete (entry); + } - readHeadPosition = 0; + readHeadPosition = 0; - bufferedNotes.clear(); - lastWrittenNote.reset(); - heldNoteAtWritePosition = nullptr; - previousNoteAtWritePosition.reset(); - previousNoteAtReadPosition.reset(); - unprocessedBuffer.clear(); + bufferedNotes.clear(); + lastWrittenNote.reset(); + heldNoteAtWritePosition = nullptr; + previousNoteAtWritePosition.reset(); + previousNoteAtReadPosition.reset(); + unprocessedBuffer.clear(); } -void VoiceProcessor::updateConfiguration(Configuration* configuration) -{ - reset(); - this->bufferSizeSamples = configuration->getLatencySamples(); - this->configuration = configuration; +void VoiceProcessor::updateConfiguration(Configuration* configuration) { + reset(); + this->bufferSizeSamples = configuration->getLatencySamples(); + this->configuration = configuration; } -unsigned long long VoiceProcessor::getReadPosition() -{ - return readHeadPosition; +unsigned long long VoiceProcessor::getReadPosition() { + return readHeadPosition; } -unsigned long long VoiceProcessor::getWritePosition() -{ - return readHeadPosition + bufferSizeSamples; +unsigned long long VoiceProcessor::getWritePosition() { + return readHeadPosition + bufferSizeSamples; } -std::vector VoiceProcessor::processSample(const std::optional>& enteredMessages, int channel, bool bypassed) -{ - std::vector output; - - // Read from the buffer - - // Controller data, bypassed messages or notes outside range - while (!unprocessedBuffer.empty() && unprocessedBuffer.front().time <= getReadPosition()) { - juce::MidiMessage message = unprocessedBuffer.front().message; - if (message.isController()) readPositionCCStates[message.getControllerNumber()] = message.getControllerValue(); - if (message.isNoteOn()) readPositionHeldNotes[message.getNoteNumber()] = message.getVelocity(); - if (message.isNoteOff()) readPositionHeldNotes[message.getNoteNumber()] = 0; - if (message.isProgramChange()) readPositionProgram = message.getProgramChangeNumber(); - - if(!configuration->isBlocked(message)) output.emplace_back(unprocessedBuffer.front().message); - unprocessedBuffer.erase(unprocessedBuffer.begin()); - } - - // Note off - for (size_t i = 0; i < bufferedNotes.size(); i++) - { - BufferedNote* note = bufferedNotes[i]; - if (note->endTime && note->endTime.value() + note->endDelay <= getReadPosition()) { - output.emplace_back(juce::MidiMessage::noteOff(channel, note->pitch)); - bufferedNotes.erase(bufferedNotes.begin() + i); - previousNoteAtReadPosition = *note; - delete(note); - } - } - - // Note on - for (const auto& note : bufferedNotes) { - if (note->startTime == getReadPosition()) { - // Process note that's about to play - everything but start delay is processed here - NoteContext context = NoteContext(note, previousNoteAtReadPosition, readPositionCCStates, readPositionHeldNotes, readPositionProgram); - std::unordered_set tags = configuration->getTagsForNote(context); - NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel); - std::vector results = noteProcessor.getResults(); +std::vector VoiceProcessor::processSample(const std::optional>& enteredMessages, int channel, bool bypassed) { + std::vector output; + + // Read from the buffer + + // Controller data, bypassed messages or notes outside range + while (!unprocessedBuffer.empty() && unprocessedBuffer.front().time <= getReadPosition()) { + juce::MidiMessage message = unprocessedBuffer.front().message; + if (message.isController()) { + readPositionCCStates[message.getControllerNumber()] = message.getControllerValue(); + } + if (message.isNoteOn()) { + readPositionHeldNotes[message.getNoteNumber()] = message.getVelocity(); + } + if (message.isNoteOff()) { + readPositionHeldNotes[message.getNoteNumber()] = 0; + } + if (message.isProgramChange()) { + readPositionProgram = message.getProgramChangeNumber(); + } + + if (!configuration->isBlocked(message)) { + output.emplace_back(unprocessedBuffer.front().message); + } + unprocessedBuffer.erase(unprocessedBuffer.begin()); + } + + // Note off + for (size_t i = 0; i < bufferedNotes.size(); i++) { + BufferedNote* note = bufferedNotes[i]; + if (note->endTime && note->endTime.value() + note->endDelay <= getReadPosition()) { + output.emplace_back(juce::MidiMessage::noteOff(channel, note->pitch)); + bufferedNotes.erase(bufferedNotes.begin() + i); + previousNoteAtReadPosition = *note; + delete (note); + } + } + + // Note on + for (const auto& note : bufferedNotes) { + if (note->startTime == getReadPosition()) { + // Process note that's about to play - everything but start delay is processed here + NoteContext context = NoteContext(note, previousNoteAtReadPosition, readPositionCCStates, readPositionHeldNotes, readPositionProgram); + std::unordered_set tags = configuration->getTagsForNote(context); + NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel); + std::vector results = noteProcessor.getResults(); #if DEBUG - DBG("Read Note " << note->pitch << ": "); - for (const auto& tag : tags) { - DBG(tag << " "); - } + DBG("Read Note " << note->pitch << ": "); + for (const auto& tag : tags) { + DBG(tag << " "); + } #endif - for (const auto& message : results) { - output.emplace_back(message); - } - } - } - - // Advance the head - readHeadPosition++; - - // Write to the buffer - if (enteredMessages.has_value()) { - for (const auto& message : enteredMessages.value()) { - if (bypassed) { - unprocessedBuffer.emplace_back(message, getWritePosition()); - } else if (!message.isNoteOnOrOff() || (message.isNoteOnOrOff() && !configuration->isInRange(message.getNoteNumber()))) { // Controller data or notes outside range - unprocessedBuffer.emplace_back(message, getWritePosition()); - if (message.isController()) writePositionCCStates[message.getControllerNumber()] = message.getControllerValue(); - if (message.isNoteOn()) writePositionHeldNotes[message.getNoteNumber()] = message.getVelocity(); - if (message.isNoteOff()) writePositionHeldNotes[message.getNoteNumber()] = -1; - if (message.isProgramChange()) writePositionProgram = message.getProgramChangeNumber(); - } - else if (message.isNoteOff() && heldNoteAtWritePosition) { // Note off - heldNoteAtWritePosition->endTime = getWritePosition(); - lastWrittenNote = *heldNoteAtWritePosition; - previousNoteAtWritePosition = *heldNoteAtWritePosition; - heldNoteAtWritePosition = nullptr; - } - else if (message.isNoteOn()) { // Note on - BufferedNote* newNote = new BufferedNote(message, getWritePosition()); - bufferedNotes.emplace_back(newNote); - heldNoteAtWritePosition = newNote; - - // Process new note - only start delay is processed here - NoteContext context = NoteContext(newNote, previousNoteAtWritePosition, writePositionCCStates, writePositionHeldNotes, writePositionProgram); - std::unordered_set tags = configuration->getTagsForNote(context); - NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel); - noteProcessor.applyStartDelay(); - - // Re-time events that should probably happen at the same time as the note - if (std::abs(noteProcessor.getStartDelaySamples()) > 0) { - for (auto& unprocessedMsg : unprocessedBuffer) { // ugh - // 512 samples or about 10ms at 48000kHz is a very generous window, so it's probably even somewhat playable - if (std::llabs(getWritePosition() - unprocessedMsg.time) < 512) { - unprocessedMsg.time = newNote->startTime; - } - } - } + for (const auto& message : results) { + output.emplace_back(message); + } + } + } + + // Advance the head + readHeadPosition++; + + // Write to the buffer + if (enteredMessages.has_value()) { + for (const auto& message : enteredMessages.value()) { + if (bypassed) { + unprocessedBuffer.emplace_back(message, getWritePosition()); + } else if (!message.isNoteOnOrOff() || (message.isNoteOnOrOff() && !configuration->isInRange(message.getNoteNumber()))) { // Controller data or notes outside range + unprocessedBuffer.emplace_back(message, getWritePosition()); + if (message.isController()) { + writePositionCCStates[message.getControllerNumber()] = message.getControllerValue(); + } + if (message.isNoteOn()) { + writePositionHeldNotes[message.getNoteNumber()] = message.getVelocity(); + } + if (message.isNoteOff()) { + writePositionHeldNotes[message.getNoteNumber()] = -1; + } + if (message.isProgramChange()) { + writePositionProgram = message.getProgramChangeNumber(); + } + } else if (message.isNoteOff() && heldNoteAtWritePosition) { // Note off + heldNoteAtWritePosition->endTime = getWritePosition(); + lastWrittenNote = *heldNoteAtWritePosition; + previousNoteAtWritePosition = *heldNoteAtWritePosition; + heldNoteAtWritePosition = nullptr; + } else if (message.isNoteOn()) { // Note on + BufferedNote* newNote = new BufferedNote(message, getWritePosition()); + bufferedNotes.emplace_back(newNote); + heldNoteAtWritePosition = newNote; + + // Process new note - only start delay is processed here + NoteContext context = NoteContext(newNote, previousNoteAtWritePosition, writePositionCCStates, writePositionHeldNotes, writePositionProgram); + std::unordered_set tags = configuration->getTagsForNote(context); + NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel); + noteProcessor.applyStartDelay(); + + // Re-time events that should probably happen at the same time as the note + if (std::abs(noteProcessor.getStartDelaySamples()) > 0) { + for (auto& unprocessedMsg : unprocessedBuffer) { // ugh + // 512 samples or about 10ms at 48000kHz is a very generous window, so it's probably even somewhat playable + if (std::llabs(getWritePosition() - unprocessedMsg.time) < 512) { + unprocessedMsg.time = newNote->startTime; + } + } + } #if DEBUG - DBG("Write Note " << newNote->pitch << ": "); - for (const auto& tag : tags) { - DBG(tag << " "); - } + DBG("Write Note " << newNote->pitch << ": "); + for (const auto& tag : tags) { + DBG(tag << " "); + } #endif - } - } - } + } + } + } - return output; + return output; } diff --git a/Source/VoiceProcessor.h b/Source/VoiceProcessor.h index 60b9792..33814a6 100644 --- a/Source/VoiceProcessor.h +++ b/Source/VoiceProcessor.h @@ -1,40 +1,41 @@ #pragma once -#include "JuceHeader.h" -#include "BufferedNote.h" #include "BufferedMidiMessage.h" +#include "BufferedNote.h" #include "Configuration.h" +#include "JuceHeader.h" class VoiceProcessor { -public: - VoiceProcessor(); - VoiceProcessor(const VoiceProcessor& other); - ~VoiceProcessor(); - juce::MidiBuffer processBuffer(const juce::MidiBuffer& buffer, int channel, int lengthSamples, bool bypassed); - void reset(); - void updateConfiguration(Configuration* configuration); -private: - Configuration* configuration; - - int bufferSizeSamples; - unsigned long long readHeadPosition = 0; - - std::vector bufferedNotes; - std::optional lastWrittenNote; - BufferedNote* heldNoteAtWritePosition = nullptr; - std::optional previousNoteAtWritePosition; - std::optional previousNoteAtReadPosition; - int writePositionCCStates[128] = { 0 }; - int readPositionCCStates[128] = { 0 }; - int writePositionHeldNotes[128] = { 0 }; - int readPositionHeldNotes[128] = { 0 }; - int readPositionProgram = 0; - int writePositionProgram = 0; - - std::vector unprocessedBuffer; - - unsigned long long getReadPosition(); - unsigned long long getWritePosition(); - - std::vector processSample(const std::optional>& enteredMessages, int channel, bool bypassed); + public: + VoiceProcessor(); + VoiceProcessor(const VoiceProcessor& other); + ~VoiceProcessor(); + juce::MidiBuffer processBuffer(const juce::MidiBuffer& buffer, int channel, int lengthSamples, bool bypassed); + void reset(); + void updateConfiguration(Configuration* configuration); + + private: + Configuration* configuration; + + int bufferSizeSamples; + unsigned long long readHeadPosition = 0; + + std::vector bufferedNotes; + std::optional lastWrittenNote; + BufferedNote* heldNoteAtWritePosition = nullptr; + std::optional previousNoteAtWritePosition; + std::optional previousNoteAtReadPosition; + int writePositionCCStates[128] = {0}; + int readPositionCCStates[128] = {0}; + int writePositionHeldNotes[128] = {0}; + int readPositionHeldNotes[128] = {0}; + int readPositionProgram = 0; + int writePositionProgram = 0; + + std::vector unprocessedBuffer; + + unsigned long long getReadPosition(); + unsigned long long getWritePosition(); + + std::vector processSample(const std::optional>& enteredMessages, int channel, bool bypassed); }; \ No newline at end of file