Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/ConfigParserUtil.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "ConfigParserUtil.h"

int ConfigParserUtil::keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC)
int ConfigParserUtil::keyNameToNumber(const juce::String& keyName)
{
static const char* const noteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "", "Db", "", "Eb", "", "", "Gb", "", "Ab", "", "Bb" };

Expand Down
6 changes: 4 additions & 2 deletions Source/ConfigParserUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include "JuceHeader.h"

const int octaveForMiddleC = 3;

class ConfigParserUtil {
public:
// Adapted from https://forum.juce.com/t/midimessage-keynametonumber/9904
static int keyNameToNumber(const juce::String& keyName, const int octaveForMiddleC);
// Adapted from https://forum.juce.com/t/midimessage-keynametonumber/9904
static int keyNameToNumber(const juce::String& keyName);
};
27 changes: 24 additions & 3 deletions Source/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include "InputTreeRootNode.h"
#include "ConfigParserUtil.h"

Configuration::Configuration() : Configuration("<foresight version=\"0\" name=\"None\">\n <settings>\n <latency>0</latency>\n </settings>\n <input></input>\n <output></output>\n</foresight>")
Configuration::Configuration() : Configuration("<foresight version=\"4\" name=\"None\">\n <settings>\n <latency>0</latency>\n </settings>\n <input></input>\n <output></output>\n</foresight>")
{
}

Expand Down Expand Up @@ -35,8 +35,24 @@ Configuration::Configuration(const std::string& xml)
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);
int noteNumber = ConfigParserUtil::keyNameToNumber(rangeElement->getAllSubText());
*targetVariable = noteNumber;

#ifdef DEBUG
DBG("range" << rangeModeText << " : " << noteNumber);
#endif
}

// Keyswitches
for (const auto& keyswitchElement : settingsRootElement->getChildWithTagNameIterator("keyswitches")) {
std::string keyswitchModeText = keyswitchElement->getStringAttribute("boundary", "lower").toStdString();
int* targetVariable = keyswitchModeText == "upper" ? &keyswitchUpperBoundary : &keyswitchLowerBoundary;
int noteNumber = ConfigParserUtil::keyNameToNumber(keyswitchElement->getAllSubText());
*targetVariable = noteNumber;

#ifdef DEBUG
DBG("keyswitches" << keyswitchModeText << " : " << noteNumber);
#endif
}

// Blocklist
Expand All @@ -46,7 +62,7 @@ Configuration::Configuration(const std::string& xml)
blocked.insert(targetText.trim().toStdString());
}
else {
blocked.insert(std::to_string(ConfigParserUtil::keyNameToNumber(targetText, 3)));
blocked.insert(std::to_string(ConfigParserUtil::keyNameToNumber(targetText)));
}
}
}
Expand Down Expand Up @@ -109,6 +125,11 @@ bool Configuration::isInRange(int noteNumber)
return noteNumber >= rangeLowerBoundary && noteNumber <= rangeUpperBoundary;
}

bool Configuration::isKeyswitch(int noteNumber)
{
return noteNumber >= keyswitchLowerBoundary && noteNumber <= keyswitchUpperBoundary;
}

bool Configuration::isBlocked(const juce::MidiMessage& message)
{
if (message.isController()) {
Expand Down
3 changes: 3 additions & 0 deletions Source/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Configuration {
void updateSampleRate(double sampleRate);
int getLatencySamples();
bool isInRange(int noteNumber);
bool isKeyswitch(int noteNumber);
bool isBlocked(const juce::MidiMessage& message);

std::unordered_set<std::string> getTagsForNote(NoteContext& context);
Expand All @@ -29,6 +30,8 @@ class Configuration {
double latency = 0.0;
int rangeLowerBoundary = INT_MIN;
int rangeUpperBoundary = INT_MAX;
int keyswitchLowerBoundary = INT_MAX;
int keyswitchUpperBoundary = INT_MIN;

std::unique_ptr<IInputTreeNode> inputTreeRoot;
std::map<std::string, std::vector<OutputListNode>> outputList;
Expand Down
17 changes: 12 additions & 5 deletions Source/InputTreeCase.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#include "InputTreeCase.h"

InputTreeCase::InputTreeCase(const juce::XmlElement& source)
InputTreeCase::InputTreeCase(const juce::XmlElement& source, int keyswitch)
{
equals = source.getIntAttribute("equals", -1);
greater = source.getIntAttribute("greater", INT_MIN);
less = source.getIntAttribute("less", INT_MAX);
// keyswitch targets have no 'case' statement, so the 'equals' value
// needs to be specified here manually
if (keyswitch != -1) {
equals = keyswitch;
}
else {
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)
Expand Down
8 changes: 5 additions & 3 deletions Source/InputTreeCase.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

class InputTreeCase {
public:
InputTreeCase(const juce::XmlElement& source);
InputTreeCase(const juce::XmlElement& source, int keyswitch);
bool check(int value);
private:
bool alwaysTrue;
int equals, greater, less;
bool alwaysTrue = false;
int equals = -1;
int greater = INT_MIN;
int less = INT_MAX;
};
26 changes: 23 additions & 3 deletions Source/InputTreeSwitchNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source)
targetNumber = std::stoi(trimmed);

target = CC;
}
else if (targetStr.starts_with("KS_")) {
std::string trimmed = targetStr.substr(3, targetStr.length());
try {
targetNumber = ConfigParserUtil::keyNameToNumber(trimmed);
target = KEYSWITCH;
}
catch (std::exception& e) {
throw std::exception("Encountered a <switch> node target attribute with an invalid value.");
}
}
else {
try {
targetNumber = ConfigParserUtil::keyNameToNumber(targetStr, 3);
targetNumber = ConfigParserUtil::keyNameToNumber(targetStr);
target = NOTE;
}
catch (std::exception& e) {
Expand All @@ -31,10 +41,15 @@ InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source)
}
}

int keyswitch = -1;
if (target == KEYSWITCH) {
keyswitch = targetNumber;
}

for (const auto& caseEntryElement : source.getChildIterator()) {
if (caseEntryElement->getTagName() == "case") {
int insertIndex = children.size();
children.emplace_back(std::make_tuple(InputTreeCase(*caseEntryElement), std::vector<std::unique_ptr<IInputTreeNode>>()));
children.emplace_back(std::make_tuple(InputTreeCase(*caseEntryElement, keyswitch), std::vector<std::unique_ptr<IInputTreeNode>>()));

for (const auto& caseChildElement : caseEntryElement->getChildIterator()) {
IInputTreeNode* child = InputTreeNodeFactory::make(*caseChildElement);
Expand All @@ -43,7 +58,7 @@ InputTreeSwitchNode::InputTreeSwitchNode(const juce::XmlElement& source)
}
else {
int insertIndex = children.size();
children.emplace_back(std::make_tuple(InputTreeCase(*caseEntryElement), std::vector<std::unique_ptr<IInputTreeNode>>()));
children.emplace_back(std::make_tuple(InputTreeCase(*caseEntryElement, keyswitch), std::vector<std::unique_ptr<IInputTreeNode>>()));

IInputTreeNode* child = InputTreeNodeFactory::make(*caseEntryElement);
std::get<1>(children[insertIndex]).emplace_back(child);
Expand Down Expand Up @@ -85,8 +100,13 @@ int InputTreeSwitchNode::getTargetValue(NoteContext& context)
case NOTE:
return context.getHeldNoteVelocity(targetNumber);
break;
case KEYSWITCH:
return context.getLastKeyswitch();
break;
case PROGRAM:
return context.getActiveProgram();
break;
default:
throw std::exception("Invalid <input> target value");
}
}
2 changes: 1 addition & 1 deletion Source/InputTreeSwitchNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class InputTreeSwitchNode : public IInputTreeNode {
InputTreeSwitchNode(const juce::XmlElement& source);
NoteContext& visit(NoteContext& context) override;
private:
enum TargetType { CC, VELOCITY, LEGATO, LENGTH, NOTE, PROGRAM };
enum TargetType { CC, VELOCITY, LEGATO, LENGTH, NOTE, KEYSWITCH, PROGRAM };

std::vector<std::tuple<InputTreeCase, std::vector<std::unique_ptr<IInputTreeNode>>>> children;
int targetNumber;
Expand Down
13 changes: 12 additions & 1 deletion Source/NoteContext.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#include "NoteContext.h"

NoteContext::NoteContext(BufferedNote* note, const std::optional<BufferedNote>& previousNote, int ccStates[128], int heldNotes[128], int program)
NoteContext::NoteContext(BufferedNote* note, const std::optional<BufferedNote>& previousNote, int ccStates[128], int heldNotes[128], int program, int lastKeyswitch)
{
this->note = note;
this->previousNote = previousNote;
this->ccStates = ccStates;
this->heldNotes = heldNotes;
this->program = program;
this->lastKeyswitch = lastKeyswitch;
}

int NoteContext::getVelocity() const
Expand All @@ -24,13 +25,23 @@ int NoteContext::getActiveProgram() const
return program;
}

int NoteContext::getLastKeyswitch() const
{
return lastKeyswitch;
}

int NoteContext::getHeldNoteVelocity(const int number) const
{
return heldNotes[number];
}

bool NoteContext::isLegato() const
{
// if the previous or current note was or is a keyswitch, this isn't a legato transition
if (note->pitch == lastKeyswitch || (previousNote.has_value() && previousNote->pitch == lastKeyswitch)) {
return false;
}

// 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;
}
Expand Down
4 changes: 3 additions & 1 deletion Source/NoteContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

class NoteContext {
public:
NoteContext(BufferedNote* note, const std::optional<BufferedNote>& previousNote, int ccStates[128], int heldNotes[128], int program);
NoteContext(BufferedNote* note, const std::optional<BufferedNote>& previousNote, int ccStates[128], int heldNotes[128], int program, int lastKeyswitch);

int getVelocity() const;
int getCCValue(const int number) const;
int getActiveProgram() const;
int getLastKeyswitch() const;
int getHeldNoteVelocity(const int number) const;
bool isLegato() const;
std::optional<unsigned long long> getLength() const;
Expand All @@ -25,4 +26,5 @@ class NoteContext {
int* ccStates;
int* heldNotes;
int program;
int lastKeyswitch;
};
2 changes: 1 addition & 1 deletion Source/OutputListNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ OutputListNode::OutputListNode(const juce::XmlElement& source)
}

if (target == NOTE) {
value = ConfigParserUtil::keyNameToNumber(juce::String(valueStr), 3);
value = ConfigParserUtil::keyNameToNumber(juce::String(valueStr));
}
else if (target != LEGATO) {
if (valueStr.starts_with("CC")) {
Expand Down
4 changes: 2 additions & 2 deletions Source/Versioning.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma once

#define CURRENT_STATE_VERSION 2
#define CURRENT_CONFIG_VERSION 3
#define CURRENT_STATE_VERSION 3
#define CURRENT_CONFIG_VERSION 4
8 changes: 3 additions & 5 deletions Source/VoiceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer)
// ...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()))) {
if (!message.isNoteOnOrOff() || (message.isNoteOnOrOff() && !configuration->isInRange(message.getNoteNumber()) && !configuration->isKeyswitch(message.getNoteNumber()))) {
message.setChannel(1);
processedBuffer.addEvent(message, time);
}
Expand All @@ -42,7 +42,7 @@ juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer)
// ...look for a note-off first
for (auto& message : entry.second)
{
if (message.isNoteOff() && configuration->isInRange(message.getNoteNumber()) && heldNote.has_value() && heldNote == message.getNoteNumber()) {
if (message.isNoteOff() && (configuration->isInRange(message.getNoteNumber()) || configuration->isKeyswitch(message.getNoteNumber())) && heldNote.has_value() && heldNote == message.getNoteNumber()) {
heldNote.reset();

message.setChannel(1);
Expand All @@ -55,8 +55,7 @@ juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer)
// ...handle note-on
for (auto& message : entry.second)
{
if (message.isNoteOn() && configuration->isInRange(message.getNoteNumber()))
{
if (message.isNoteOn() && (configuration->isInRange(message.getNoteNumber()) || configuration->isKeyswitch(message.getNoteNumber()))) {
// If there's already a playing note, stop it
if (heldNote.has_value())
{
Expand All @@ -65,7 +64,6 @@ juce::MidiBuffer VoiceManager::processBuffer(const juce::MidiBuffer& buffer)
}

// Play the new note

heldNote = message.getNoteNumber();

message.setChannel(1);
Expand Down
13 changes: 11 additions & 2 deletions Source/VoiceProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ std::vector<juce::MidiMessage> VoiceProcessor::processSample(const std::optional
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);
NoteContext context = NoteContext(note, previousNoteAtReadPosition, readPositionCCStates, readPositionHeldNotes, readPositionProgram, lastKeyswitch);
std::unordered_set<std::string> tags = configuration->getTagsForNote(context);
NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel);
std::vector<juce::MidiMessage> results = noteProcessor.getResults();
Expand Down Expand Up @@ -165,6 +165,15 @@ std::vector<juce::MidiMessage> VoiceProcessor::processSample(const std::optional
if (message.isNoteOn()) writePositionHeldNotes[message.getNoteNumber()] = message.getVelocity();
if (message.isNoteOff()) writePositionHeldNotes[message.getNoteNumber()] = -1;
if (message.isProgramChange()) writePositionProgram = message.getProgramChangeNumber();
if (configuration->isKeyswitch(message.getNoteNumber())) {
lastKeyswitch = message.getNoteNumber();

#ifdef DEBUG
if (message.isNoteOn()) {
DBG("Keyswitch: " << lastKeyswitch);
}
#endif
}
}
else if (message.isNoteOff() && heldNoteAtWritePosition) { // Note off
heldNoteAtWritePosition->endTime = getWritePosition();
Expand All @@ -178,7 +187,7 @@ std::vector<juce::MidiMessage> VoiceProcessor::processSample(const std::optional
heldNoteAtWritePosition = newNote;

// Process new note - only start delay is processed here
NoteContext context = NoteContext(newNote, previousNoteAtWritePosition, writePositionCCStates, writePositionHeldNotes, writePositionProgram);
NoteContext context = NoteContext(newNote, previousNoteAtWritePosition, writePositionCCStates, writePositionHeldNotes, writePositionProgram, lastKeyswitch);
std::unordered_set<std::string> tags = configuration->getTagsForNote(context);
NoteProcessor noteProcessor = NoteProcessor(context, configuration, tags, channel);
noteProcessor.applyStartDelay();
Expand Down
1 change: 1 addition & 0 deletions Source/VoiceProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class VoiceProcessor {
int bufferSizeSamples;
unsigned long long readHeadPosition = 0;

int lastKeyswitch = -1;
std::vector<BufferedNote*> bufferedNotes;
std::optional<BufferedNote> lastWrittenNote;
BufferedNote* heldNoteAtWritePosition = nullptr;
Expand Down