text:
/*
  ==============================================================================

    PresetManager.cpp
    Created: 27 May 2019 10:57:01pm
    Author:  Vincenzo

  ==============================================================================
*/

#include "PresetManager.h"

SvkPresetManager::SvkPresetManager(ValueTree pluginSettingsNodeIn)
{
    pluginSettingsNode = pluginSettingsNodeIn;
    initializeModePresets();

    loadPreset(SvkPreset::getDefaultPreset().getPresetNode(), false);
}

SvkPresetManager::~SvkPresetManager()
{
    removeAllChangeListeners();
}

SvkPreset& SvkPresetManager::getPreset()
{
    return svkPresetWorking;
}

int SvkPresetManager::getNumMenuItems(bool withFactoryMenu , bool withUserMenu, bool withFavMenu, bool withSlots)
{
    int totalMenuItems = 0;

    if (withFactoryMenu)
    {
        int factoryMenuSize = 0;

        for (auto sortedArray : modesSorted)
            factoryMenuSize += sortedArray.size();

        totalMenuItems += factoryMenuSize;
        //modesSorted[0].size() + modesSorted[1].size() + modesSorted[2].size();
    }

    if (withUserMenu)
        totalMenuItems += loadedUserModes.size();

    if (withFavMenu)
        totalMenuItems += favoriteModes.size();

    if (withSlots)
        totalMenuItems += modeSlots.size();

    return totalMenuItems;
}

int SvkPresetManager::getNumModeSlots() const
{
    return modeSlots.size();
}

Array<int> SvkPresetManager::getModeSlotsInUse() const
{
    return svkPresetWorking.getSlotNumbersInUse();
}

int SvkPresetManager::getModeSlotOfSelector(int modeSelectorNumIn) const
{
    return svkPresetWorking.getSlotNumberBySelector(modeSelectorNumIn);
}

ValueTree SvkPresetManager::getModeInLibrary(int indexIn)
{
    if (indexIn < 0)
        indexIn = 0;

    int numModes = loadedFactoryModes.size() + loadedUserModes.size();

    int subMenu = indexIn / numModes;
    int index = indexIn % numModes;

    if (subMenu < modesSorted.size())
        if (index < modesSorted.getUnchecked(subMenu).size())
            return modesSorted.getUnchecked(subMenu).getUnchecked(index);

    return ValueTree();
}

Mode* SvkPresetManager::getModeInSlot(int modeSlotNumIn)
{
    int slotIndex = svkPresetWorking.getSlotNumberIndex(modeSlotNumIn);

    if (slotIndex >= 0)
    {
        jassert(slotIndex < modeSlots.size());

        Mode* mode = modeSlots[slotIndex];
        DBG("GRABBING MODE " + String(modeSlotNumIn) + " which is: " + mode->getDescription());

        return mode;
    }
    else if (modeSlotNumIn > 0)
    {
        return modeCustom.get();
    }

    return nullptr;
}

Mode* SvkPresetManager::getModeBySelector(int selectorNumber)
{
    int slotNumber = svkPresetWorking.getSlotNumberBySelector(selectorNumber);
    if (svkPresetWorking.isSlotNumberInUse(slotNumber))
    {
        return modeSlots[svkPresetWorking.getSlotNumberIndex(slotNumber)];
    }

    else if (slotNumber > MAX_MODE_SLOTS_INDEX)
    {
        return modeCustom.get();
    }

    return nullptr;
}

Mode* SvkPresetManager::getModeCustom()
{
    return modeCustom.get();
}

Mode* SvkPresetManager::setModeCustom(ValueTree modeNodeIn)
{
    svkPresetWorking.setCustomMode(modeNodeIn);
    modeCustom.reset(new Mode(svkPresetWorking.getCustomMode(), false));
    return modeCustom.get();
}

Mode* SvkPresetManager::setModeCustom(String stepsIn, String familyIn, String nameIn, String infoIn, int rootNoteIn)
{
    return setModeCustom(Mode::createNode(stepsIn, familyIn, nameIn, infoIn, rootNoteIn));
}

int SvkPresetManager::setSlotToMode(int modeSlotNum, ValueTree modeNode)
{
    int slotIndex = svkPresetWorking.setModeSlot(modeNode, modeSlotNum);
    modeNode = svkPresetWorking.getModeInSlot(modeSlotNum);

    if (slotIndex >= 0)
    {
        if (slotIndex >= modeSlots.size())
            modeSlots.add(new Mode(modeNode, false));

        else
            modeSlots.set(slotIndex, new Mode(modeNode, false));
    }

    return slotIndex;
}
int SvkPresetManager::addSlot(ValueTree modeNode)
{
    int slotNumber = svkPresetWorking.addModeSlot(modeNode);
    modeSlots.add(new Mode(modeNode));

    return slotNumber;
}

int SvkPresetManager::setSlotAndSelection(int modeSlotNum, int modeSelectorNum, ValueTree modeNode)
{
    int slotIndex = setSlotToMode(modeSlotNum, modeNode);
    svkPresetWorking.setModeSelectorSlotNum(modeSelectorNum, modeSlotNum);
    return slotIndex;
}

int SvkPresetManager::addSlotAndSetSelection(int modeSelectorNumber, ValueTree modeNode)
{
    int modeSlotNum = addSlot(modeNode);
    svkPresetWorking.setModeSelectorSlotNum(modeSelectorNumber, modeSlotNum);
    return modeSlotNum;
}

void SvkPresetManager::removeMode(int modeSlotNum)
{
    int slotIndex = svkPresetWorking.removeModeSlot(modeSlotNum);
    modeSlots.remove(slotIndex);
}

void SvkPresetManager::resetModeSlots()
{
    svkPresetWorking.resetModeSlots();
    modeSlots.clear();

    modeSlots.add(new Mode(svkPresetWorking.getMode1()));
    modeSlots.add(new Mode(svkPresetWorking.getMode2()));
}

/*
    This operation should be completely revised, as it's piecing together two systems without full implementation.
    Currently, modes can be inserted in one of 128 slots, and mode selectors can point to a certain slot.
    But inserting modes into different slots is not fully implemented yet, so the mode slot containing a mode that
    the mode selector is pointed to will be replaced by the mode chosen in calling this function.
*/
void SvkPresetManager::handleModeSelection(int selectorNumber, int idIn)
{
    int numSortedModes = getNumMenuItems(true, false, false, false);

    int modeLibraryIndex = idIn - 1;
    int userModesIndex = modeLibraryIndex - numSortedModes + loadedUserModes.size(); // issue here, subtracting loaded user modes is a workaround
    int favIdx = userModesIndex - loadedUserModes.size();
    int slotIdx = favIdx - favoriteModes.size();

    int modeSlotNumber = svkPresetWorking.getSlotNumberBySelector(selectorNumber);

    // if selector was set to a custom mode, change the slot to the selector's number
    if (modeSlotNumber > MAX_MODE_SLOTS_INDEX)
        modeSlotNumber = selectorNumber;

    ValueTree modeSelected;

    if (modeLibraryIndex < numSortedModes)
    {
        modeSelected = getModeInLibrary(modeLibraryIndex).createCopy();
    }
    else if (userModesIndex < loadedUserModes.size())
    {
        modeSelected = loadedUserModes[userModesIndex].createCopy();
    }
    else if (favIdx < favoriteModes.size())
    {
        modeSelected = favoriteModes[favIdx].createCopy();
    }
    else if (slotIdx < modeSlots.size())
    {
        // Currently the only way to switch a selector's mode slot pointer
        modeSelected = ValueTree();
        modeSlotNumber = slotIdx;
    }
    else
    {
        DBG("Custom mode selected");
        modeSelected = modeCustom->modeNode;
        modeSlotNumber = MAX_MODE_SLOTS_INDEX + 1;
    }

    // Replaces the mode selector's mode slot with new mode
    if (modeSelected.isValid() && selectorNumber < 2 && modeSlotNumber <= MAX_MODE_SLOTS_INDEX)
    {
        setSlotToMode(modeSlotNumber, modeSelected);
    }

    svkPresetWorking.setModeSelectorSlotNum(selectorNumber, modeSlotNumber);
    getModeInSlot(modeSlotNumber)->setRootNote(svkPresetWorking.getModeSelectorRootNote(selectorNumber));
}

bool SvkPresetManager::loadPreset(ValueTree presetNodeIn, bool sendChangeSignal)
{
    if (presetNodeIn.hasType(IDs::presetNode))
    {
        DBG("Loading Preset:" + presetNodeIn.toXmlString());

        svkPresetSaved.restoreFromNode(presetNodeIn, true);
        svkPresetWorking = SvkPreset(svkPresetSaved);

        modeSlots.clear();
        for (auto mode : svkPresetWorking.getModeSlots())
        {
            modeSlots.add(new Mode(mode.getChild(0), false));
        }

        ValueTree customModeNode = svkPresetWorking.getCustomMode();
        modeCustom.reset(new Mode(customModeNode, false));

        if (sendChangeSignal)
            sendChangeMessage();

        return true;
    }

    DBG("Problem loading preset.");

    return false;
}

bool SvkPresetManager::saveNodeToFile(ValueTree nodeToSave, String saveMsg, String fileEnding, String absolutePath)
{
    File fileOut = File(absolutePath);

    if (fileOut.isDirectory())
    {
        FileChooser chooser(saveMsg,
            fileOut, fileEnding);

        chooser.browseForFileToSave(true);
        fileOut = chooser.getResult();
    }
    else if (!fileOut.exists())
    {
        FileChooser chooser(saveMsg,
            File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory),
            fileEnding);

        chooser.browseForFileToSave(true);
        fileOut = chooser.getResult();
    }

    if (fileOut.getParentDirectory().exists())
    {
        std::unique_ptr<XmlElement> xml(nodeToSave.createXml());
        return xml->writeTo(fileOut);
    }

    return false;
}

bool SvkPresetManager::savePresetToFile(String absolutePath)
{
    commitPreset();
    DBG("Saving the following node to " + absolutePath + ": " + svkPresetWorking.toString());
    return saveNodeToFile(svkPresetWorking.getPresetNode(), "Save preset", "*.svk", absolutePath);
}

bool SvkPresetManager::saveModeToFile(int modeSlotNumber, String absolutePath)
{
    Mode* mode;

    if (modeSlotNumber < modeSlots.size())
        mode = getModeInSlot(modeSlotNumber);
    else
        mode = modeCustom.get();

    bool saved = saveNodeToFile(mode->modeNode, "Save mode", "*.svk", absolutePath);

    if (saved)
    {
        ValueTree newCopy = mode->modeNode.createCopy();
        addAndSortMode(newCopy);
        loadedUserModes.add(newCopy);

        // Reset custom mode, since saved mode is a user mode now
        // Not sure if this should be done like that...
        if (mode == modeCustom.get())
            modeCustom.reset(new Mode("1"));
    }

    return saved;
}

ValueTree SvkPresetManager::nodeFromFile(String openMsg, String fileEnding, String absoluteFilePath)
{
    ValueTree nodeIn;
    File fileIn = File(absoluteFilePath);

    if (!fileIn.existsAsFile())
    {
        std::unique_ptr<FileChooser> chooser;

        if (fileIn.isDirectory())
            chooser.reset(new FileChooser(openMsg, fileIn, fileEnding));
        else
            chooser.reset(new FileChooser(openMsg, File::getSpecialLocation(File::userDocumentsDirectory), fileEnding));

        chooser->browseForFileToOpen();
        fileIn = chooser->getResult();
    }

    if (fileIn.existsAsFile())
    {
        std::unique_ptr<XmlElement> xml = parseXML(fileIn);
        nodeIn = ValueTree::fromXml(*(xml.get()));

        if (nodeIn.isValid())
            return nodeIn;
    }

    return ValueTree();
}

ValueTree SvkPresetManager::modeFromFile(String absoluteFilePath)
{
    ValueTree nodeIn = nodeFromFile("Open mode", "*.svk", absoluteFilePath);

    if (nodeIn.hasType(IDs::modePresetNode))
        return nodeIn;

    return ValueTree();
}

ValueTree SvkPresetManager::presetFromFile(String absoluteFilePath)
{
    ValueTree nodeIn = nodeFromFile("Open preset", "*.svk", absoluteFilePath);

    if (nodeIn.hasType(IDs::presetNode))
        return nodeIn;

    return ValueTree();
}

bool SvkPresetManager::commitPreset()
{
    return loadPreset(svkPresetWorking.getPresetNode(), false);
}

bool SvkPresetManager::resetToSavedPreset()
{
    DBG("PRESET MANAGER: Resetting to last saved preset.");
    return loadPreset(svkPresetSaved.getPresetNode(), false);
}

void SvkPresetManager::initializeModePresets()
{
    modeLibraryNode = ValueTree(IDs::modeLibraryNode);
    loadedFactoryModes.clear();
    loadedUserModes.clear();
    modesSorted.clear();
    for (int m = 0; m < numSortTypes; m++)
        modesSorted.add(Array<ValueTree>());

    createFactoryModes();
    loadModeDirectory();
}

void SvkPresetManager::createFactoryModes()
{
    if (!(bool) pluginSettingsNode[IDs::saveFactoryModes] || !(bool)pluginSettingsNode[IDs::createPresetFolder])
    {
        const char* factoryModes = BinaryData::FactoryModes_txt;
        int size = BinaryData::FactoryModes_txtSize;

        loadedFactoryModes.clear();

        if (factoryModes && size > 0)
        {
            MemoryInputStream instream(factoryModes, size, false);

            String line, steps, family, info, name;
            ValueTree factoryMode;

            while (instream.getNumBytesRemaining() > 0)
            {
                line = instream.readNextLine();
                steps = line.upToFirstOccurrenceOf(", ", false, true);

                family = line.upToFirstOccurrenceOf("; ", false, true);
                family = family.substring(steps.length() + 2, family.length());

                info = line.fromFirstOccurrenceOf("; ", false, true);

                factoryMode = Mode::createNode(steps, family, "", info, 60, true);

                addAndSortMode(factoryMode);
                loadedFactoryModes.add(factoryMode);
             }

            factoryMode = ValueTree();
        }
    }

    DBG("Amt of factory modes:" + String(loadedFactoryModes.size()));
}

void SvkPresetManager::loadModeDirectory()
{
    // TODO: Add checking for duplicate modes

    if ((bool)pluginSettingsNode[IDs::createPresetFolder])
    {
        File modeDirectory = File(pluginSettingsNode[IDs::modeDirectory]);
        Array<File> filesToLoad = modeDirectory.findChildFiles(File::TypesOfFileToFind::findFiles, true, "*.svk");

        std::unique_ptr<XmlElement> xml;
        ValueTree modeNodeIn;

        while (filesToLoad.size() > 0)
        {
            xml = parseXML(filesToLoad.removeAndReturn(0));
            modeNodeIn = ValueTree::fromXml(*(xml.get()));

            if (modeNodeIn.hasType(IDs::modePresetNode))
            {
                addAndSortMode(modeNodeIn);

                if ((bool)modeNodeIn[IDs::factoryPreset])
                {
                    loadedFactoryModes.add(modeNodeIn);
                }
                else
                {
                    loadedUserModes.add(modeNodeIn);
                }
            }
        }
    }

    DBG("New amt of Factory Modes: " + String(loadedFactoryModes.size()));
    DBG("Amt User Modes loaded: " + String(loadedUserModes.size()));
}

void SvkPresetManager::resortModeLibrary()
{
    modesSorted.clear();
    for (int m = 0; m < numSortTypes; m++)
        modesSorted.add(Array<ValueTree>());

    ValueTree modeToSort;

    for (int i = 0; i < modeLibraryNode.getNumChildren(); i++)
    {
        modeToSort = modeLibraryNode.getChild(i);
        addModeToSort(modeToSort);
    }
}

int SvkPresetManager::addModeToLibrary(ValueTree modeNodeIn)
{
    if (modeNodeIn.hasType(IDs::modePresetNode))
    {
        int libraryIndex = modeLibraryNode.getNumChildren();
        modeNodeIn.setProperty(IDs::libraryIndexOfMode, libraryIndex, nullptr);

        modeLibraryNode.appendChild(modeNodeIn, nullptr);

        return libraryIndex;
    }

    return -1;
}

void SvkPresetManager::addModeToSort(ValueTree modeNodeIn)
{
    if (modeNodeIn.hasType(IDs::modePresetNode))
    {
        modesSorted.getReference(SortType::scaleSize).addSorted(scaleSizeSort, modeNodeIn);
        modesSorted.getReference(SortType::modeSize).addSorted(modeSizeSort, modeNodeIn);
        modesSorted.getReference(SortType::familyName).addSorted(familyNameSort, modeNodeIn);

        if (!(bool)modeNodeIn[IDs::factoryPreset])
        {
            modesSorted.getReference(SortType::user).addSorted(scaleSizeSort, modeNodeIn);
        }
    }
}

int SvkPresetManager::addAndSortMode(ValueTree modeNodeIn)
{
    int ind = addModeToLibrary(modeNodeIn);
    addModeToSort(modeNodeIn);

    return ind;
}

void SvkPresetManager::requestModeMenu(PopupMenu* menuToUse)
{
    menuToUse->clear();

    PopupMenu scaleSizeMenu;
    PopupMenu modeSizeMenu;
    PopupMenu familyMenu;
    PopupMenu userMenu;
    PopupMenu favMenu;
    PopupMenu slotsMenu;

    ValueTree presetIn;
    String displayName;
    var separatorProperty;

    int numModes = 0;
    int subMenuIndex = 0;

    for (int subMenu = 0; subMenu <= SortType::user; subMenu++)
    {

        if (subMenu < SortType::user)
            numModes = modeLibraryNode.getNumChildren();
        else
            numModes = loadedUserModes.size();

        for (int presetNum = 0; presetNum < numModes; presetNum++)
        {
            presetIn = modesSorted.getUnchecked(subMenu).getUnchecked(presetNum);

            switch (subMenu)
            {
                case (SortType::scaleSize):
                    if (separatorProperty != presetIn[IDs::scaleSize])
                        scaleSizeMenu.addSeparator();
                    separatorProperty = presetIn[IDs::scaleSize];
                    displayName = presetIn[IDs::modeName];
                    scaleSizeMenu.addItem(++subMenuIndex, displayName);
                    break;

                case (SortType::modeSize):
                    if (separatorProperty != presetIn[IDs::modeSize])
                        modeSizeMenu.addSeparator();
                    separatorProperty = presetIn[IDs::modeSize];
                    displayName = presetIn[IDs::modeName];
                    modeSizeMenu.addItem(++subMenuIndex, displayName);
                    break;

                case (SortType::familyName):
                    if (separatorProperty != presetIn[IDs::family])
                        familyMenu.addSeparator();
                    separatorProperty = presetIn[IDs::family];
                    displayName = presetIn[IDs::modeName];
                    familyMenu.addItem(++subMenuIndex, displayName);
                    break;

                case (SortType::user):
                    if (presetIn[IDs::factoryPreset])
                        continue;
                    if (separatorProperty != presetIn[IDs::scaleSize])
                        userMenu.addSeparator();
                    separatorProperty = presetIn[IDs::scaleSize];
                    displayName = presetIn[IDs::modeName];
                    userMenu.addItem(++subMenuIndex, displayName);
                    break;
            }
        }
    }

    // USERS
//    for (int i = 0; i < loadedUserModes.size(); i++)
//    {
//        userMenu.addItem(++subMenuIndex, loadedUserModes[i][IDs::modeName].toString());
//    }

    // FAVORITES
    for (int i = 0; i < favoriteModes.size(); i++)
    {
        favMenu.addItem(++subMenuIndex, favoriteModes[i][IDs::modeName].toString());
    }

    // SLOTS
    Mode* mode;

    for (int i = 0; i < modeSlots.size(); i++)
    {
        mode = modeSlots[i];
        slotsMenu.addItem(++subMenuIndex, mode->getName());
    }

    menuToUse->addSubMenu("by Scale Size", scaleSizeMenu);
    menuToUse->addSubMenu("by Mode Size", modeSizeMenu);
    menuToUse->addSubMenu("by Family", familyMenu);
    menuToUse->addSubMenu("User", userMenu);
    menuToUse->addSubMenu("Favorites", favMenu);

    menuToUse->addSeparator();
    menuToUse->addSubMenu("Slots", slotsMenu);
    menuToUse->addSeparator();

    String customModeName = modeCustom->getName();

    if (customModeName == "undefined[1] 1")
        customModeName = "Custom Mode";

    menuToUse->addItem(++subMenuIndex, customModeName);
}


text:
/*
  ==============================================================================

    This file was auto-generated!

    It contains the basic framework code for a JUCE plugin processor.

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "UI/PluginEditor.h"
#include "PluginState.h"

typedef AudioProcessorValueTreeState::Parameter Parameter;

//==============================================================================
/**
*/

class SvkAudioProcessor  : public AudioProcessor,
                            public ChangeBroadcaster
{
public:
    //==============================================================================
    SvkAudioProcessor();
    ~SvkAudioProcessor();

    std::unique_ptr<UndoManager> svkUndo;
    AudioProcessorValueTreeState svkValueTree;

    //==============================================================================
    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void releaseResources() override;

   #ifndef JucePlugin_PreferredChannelConfigurations
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
   #endif

    void processBlock (AudioBuffer<float>&, MidiBuffer&) override;

    //==============================================================================
    AudioProcessorEditor* createEditor() override;
    bool hasEditor() const override;

    //==============================================================================
    const String getName() const override;

    bool acceptsMidi() const override;
    bool producesMidi() const override;
    bool isMidiEffect() const override;
    double getTailLengthSeconds() const override;

    //==============================================================================
    int getNumPrograms() override;
    int getCurrentProgram() override;
    void setCurrentProgram (int index) override;
    const String getProgramName (int index) override;
    void changeProgramName (int index, const String& newName) override;

    //==============================================================================
    void getStateInformation (MemoryBlock& destData) override;
    void setStateInformation (const void* data, int sizeInBytes) override;

    //==============================================================================
    UndoManager* getUndoManager();
    //ApplicationCommandManager* getAppCmdMgr();
    SvkPluginState* getPluginState();
    //const Array<String>& getParamIDs() const;

    //==============================================================================

private:

    //void recordParamIDs();
    //
    AudioProcessorValueTreeState::ParameterLayout createParameters();
    //Array<String> paramIDs;

    std::unique_ptr<SvkPluginState> pluginState;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SvkAudioProcessor)
};

repo_name: vsicurella/SuperVirtualKeyboard

mime_type: text/x-c


text:
/*
  ==============================================================================

    Constants.h
    Created: 16 Aug 2020 12:41:04pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>
#include "Structures/Mode.h"

#define STD_TUNING_MODE_NODE Mode::createNode("2 2 1 2 2 2 1", "Meantone")

const int MAX_MODE_SLOTS_INDEX = 127;
const float SVK_PRESET_VERSION = 1.0;


text:
/*
  ==============================================================================

    CommonFunctions.h
    Created: 6 May 2019 7:31:20pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>
#include "PluginIDs.h"
#include "Structures/OwnedHashMap.h"

struct ScaleSizeSorter
{
    // Scale first, then mode, then family

    static int compareElements(const ValueTree t1, const ValueTree t2)
    {
        if ((t1.hasType(IDs::modePresetNode) || t1.hasType(IDs::presetNode)) &&
           (t2.hasType(IDs::modePresetNode) || t2.hasType(IDs::presetNode)))
        {
            int sz1 = (int) t1[IDs::scaleSize];
            int sz2 = (int) t2[IDs::scaleSize];

            int m1 = (int) t1[IDs::modeSize];
            int m2 = (int) t2[IDs::modeSize];

            String f1 = t1[IDs::family];
            String f2 = t2[IDs::family];

            if (sz1 < sz2) return -1;
            else if (sz1 > sz2) return 1;
            else
            {
                if (m1 < m2) return -1;
                else if (m1 > m2) return 1;
                else
                {
                    if (f1 < f2) return  -1;
                    else if (f1 > f2) return 1;
                    else return 0;
                }
            }
        }
        else
            return 0;
    }
};

struct ModeSizeSorter
{
    // Mode first, then scale, then family

    static int compareElements(const ValueTree t1, const ValueTree t2)
    {
        if ((t1.hasType(IDs::modePresetNode) || t1.hasType(IDs::presetNode)) &&
            (t2.hasType(IDs::modePresetNode) || t2.hasType(IDs::presetNode)))
        {
            int sz1 = (int) t1[IDs::scaleSize];
            int sz2 = (int) t2[IDs::scaleSize];

            int m1 = (int) t1[IDs::modeSize];
            int m2 = (int) t2[IDs::modeSize];

            String f1 = t1[IDs::family];
            String f2 = t2[IDs::family];

            if (m1 < m2) return -1;
            else if (m1 > m2) return 1;
            else
            {
                if (sz1 < sz2) return -1;
                else if (sz1 > sz2) return 1;
                else
                {
                    if (f1 < f2) return  -1;
                    else if (f1 > f2) return 1;
                    else return 0;
                }
            }
        }
        return 0;
    }
};

struct FamilyNameSorter
{
    // Family first, then scale, then mode

    static int compareElements(const ValueTree t1, const ValueTree t2)
    {
        if ((t1.hasType(IDs::modePresetNode) || t1.hasType(IDs::presetNode)) &&
            (t2.hasType(IDs::modePresetNode) || t2.hasType(IDs::presetNode)))
        {
            int sz1 = (int) t1[IDs::scaleSize];
            int sz2 = (int) t2[IDs::scaleSize];

            int m1 = (int) t1[IDs::modeSize];
            int m2 = (int) t2[IDs::modeSize];

            String f1 = t1[IDs::family];
            String f2 = t2[IDs::family];

            if (f1 < f2) return -1;
            else if (f1 > f2) return 1;
            else
            {
                if (sz1 < sz2) return -1;
                else if (sz1 > sz2) return 1;
                else
                {
                    if (m1 < m2) return  -1;
                    else if (m1 > m2) return 1;
                    else return 0;
                }
            }
        }
        return 0;
    }
};

struct IDasStringHash
{
    static int generateHash (const Identifier& key, int upperLimit) noexcept
    {
        return DefaultHashFunctions::generateHash(key.toString(), upperLimit);
    }
};

//typedef OwnedHashMap<Identifier, RangedAudioParameter, IDasStringHash> SvkParameters;

template <class T, class U>
struct Pair
{
    T key;
    U value;

    Pair<T, U>(T keyIn, U valueIn)
    {
        key = keyIn;
        value = valueIn;
    }
};

static bool toggleMenuItemEnablement(PopupMenu& menuIn, int itemIdIn, bool shouldBeEnabled)
{
    PopupMenu::MenuItemIterator menuItems(menuIn);
    while (menuItems.next())
    {
        PopupMenu::Item& item = menuItems.getItem();
        if (item.itemID == itemIdIn)
        {
            item.isEnabled = shouldBeEnabled;
            return true;
        }
    }

    return false;
}

//struct MidiPitch : Pair<int, int>
//{
//    MidiPitch(int midiNoteIn=-1, int pitchbendIn=0)
//    : Pair<int, int>(midiNoteIn, pitchbendIn) {}
//};

//static Point<int> pointFromString(Point<int>& p, String pointToStringIn)
//{
//    String val1 = pointToStringIn.upToFirstOccurrenceOf(",", false, true);
//    String val2 = pointToStringIn.fromFirstOccurrenceOf(",", false, true);
//    p = Point<int>(val1.getIntValue(), val2.getIntValue());
//    return p;
//}
//
//static Point<float> pointFromString(Point<float>& p, String pointToStringIn)
//{
//    String val1 = pointToStringIn.upToFirstOccurrenceOf(",", false, true);
//    String val2 = pointToStringIn.fromFirstOccurrenceOf(",", false, true);
//    p = Point<float>(val1.getFloatValue(), val2.getFloatValue());
//    return p;
//}
//
//static Point<double> pointFromString(Point<double>& p, String pointToStringIn)
//{
//    String val1 = pointToStringIn.upToFirstOccurrenceOf(",", false, true);
//    String val2 = pointToStringIn.fromFirstOccurrenceOf(",", false, true);
//    p = Point<double>(val1.getDoubleValue(), val2.getDoubleValue());
//    return p;
//}

template <class T>
static void add_array_to_node(ValueTree nodeIn, const Array<T>& arrayIn, Identifier arrayID, Identifier itemId)
{
    ValueTree arrayTree = ValueTree(arrayID);
    ValueTree item;

    for (int i = 0; i < arrayIn.size(); i++)
    {
        item = ValueTree(itemId);
        item.setProperty("Value", arrayIn[i], nullptr);
        arrayTree.addChild(item, i, nullptr);
    }

    nodeIn.addChild(arrayTree, -1, nullptr);
}

template <class T>
static void get_array_from_node(const ValueTree nodeIn, Array<T>& arrayIn, Identifier arrayID)
{
    ValueTree childArray = nodeIn.getChildWithName(arrayID);

    if (childArray.isValid())
    {
        for (int i = 0; i < childArray.getNumChildren(); i++)
        {
            arrayIn.add(childArray.getChild(i).getProperty("Value"));
        }
    }
}

///* Adds a Colour array reduced to nontrivial items to a node represented in a ValueTree structure */

//static void add_array_to_node(ValueTree nodeIn, const Array<Colour>& arrayIn, Identifier arrayID, Identifier itemId)
//{
//    ValueTree arrayTree = ValueTree(arrayID);
//    ValueTree item;
//
//    for (int i = 0; i < arrayIn.size(); i++)
//    {
//        if (arrayIn[i].isOpaque())
//        {
//            item = ValueTree(itemId);
//            item.setProperty("Key", i, nullptr);
//            item.setProperty("Value", arrayIn[i].toString(), nullptr);
//            arrayTree.addChild(item, i, nullptr);
//        }
//    }
//
//    nodeIn.addChild(arrayTree, -1, nullptr);
//}
//
//static void add_array_as_property(ValueTree& nodeIn, const Array<Colour>& arrayIn, Identifier itemId)
//{
//    String array = "";
//
//    for (int i = 0; i < arrayIn.size(); i++)
//    {
//        array += arrayIn[i].toString();
//        array += '\n';
//    }
//
//    nodeIn.setProperty(itemId, array, nullptr);
//}

//static void get_array_from_node(const ValueTree& nodeIn, Array<Colour>& arrayIn, Identifier arrayID)
//{
//    String array = nodeIn[arrayID];
//    String value = "";
//    int charsRead = 0;
//
//    Colour c = Colours::transparentWhite;
//
//    while (charsRead < array.length())
//    {
//        value += array[charsRead];
//
//        if (charsRead + 1 < array.length())
//        {
//            if (array[charsRead + 1] == '\n')
//                c = Colour::fromString(value);
//        }
//
//        if (c != Colours::transparentWhite)
//        {
//            arrayIn.add(c);
//            c = Colours::transparentWhite;
//        }
//    }
//}

///* Creates a Colour array from a node, and populates trivial and nontrivial keys */

//static void get_array_from_node(const ValueTree nodeIn, Array<Colour>& arrayIn, Identifier arrayID, int arraySizeOut)
//{
//    ValueTree arrayNode;
//    ValueTree item;
//
//    arrayIn.resize(arraySizeOut);
//
//    arrayNode = nodeIn.getChildWithName(arrayID);
//
//    for (int i = 0; i < arrayNode.getNumChildren(); i++)
//    {
//        item = arrayNode.getChild(i);
//        arrayIn.set(item["Key"], Colour::fromString(item["Value"].toString()));
//    }
//}

//template <class T>
//static void add_array_to_node(ValueTree nodeIn, const Array<Point<T>>& arrayIn, Identifier arrayID, Identifier itemId)
//{
//    ValueTree arrayTree = ValueTree(arrayID);
//    ValueTree item;
//    for (int i = 0; i < arrayIn.size(); i++)
//    {
//        item = ValueTree(itemId);
//        item.setProperty("Value", arrayIn[i].toString(), nullptr);
//        arrayTree.addChild(item, i, nullptr);
//    }
//
//    nodeIn.addChild(arrayTree, -1, nullptr);
//}

//static void get_array_from_node(const ValueTree nodeIn, Array<Point<int>>& arrayIn, Identifier arrayID)
//{
//    ValueTree childArray = nodeIn.getChildWithName(arrayID);
//
//    if (childArray.isValid())
//        for (int i = 0; i < childArray.getNumChildren(); i++)
//            arrayIn.add(pointFromString(arrayIn.getReference(0), childArray.getChild(i).getProperty("Value").toString()));
//}
//
//static void get_array_from_node(const ValueTree nodeIn, Array<Point<float>>& arrayIn, Identifier arrayID)
//{
//    ValueTree childArray = nodeIn.getChildWithName(arrayID);
//
//    if (childArray.isValid())
//        for (int i = 0; i < childArray.getNumChildren(); i++)
//            arrayIn.add(pointFromString(arrayIn.getReference(0), childArray.getChild(i).getProperty("Value")));
//}
//
//static void get_array_from_node(const ValueTree nodeIn, Array<Point<double>>& arrayIn, Identifier arrayID)
//{
//    ValueTree childArray = nodeIn.getChildWithName(arrayID);
//
//    if (childArray.isValid())
//        for (int i = 0; i < childArray.getNumChildren(); i++)
//            arrayIn.add(pointFromString(arrayIn.getReference(0), childArray.getChild(i).getProperty("Value")));
//}

template <class T>
static void set_value_in_array(ValueTree nodeIn, Identifier arrayID, int indexToChange, T valueToSet)
{
    ValueTree arrayNode = nodeIn.getChildWithName(arrayID);

    if (arrayNode.isValid() && indexToChange >= 0 && indexToChange < arrayNode.getNumChildren())
    {
        arrayNode.getChild(indexToChange).setProperty("Value", valueToSet, nullptr);
    }
}

static int totalModulus(int numIn, int mod)
{
    int val = 0;

    if (mod != 0)
        val = ((numIn % mod) + mod) % mod;

    return val;
}

template <class T>
static T sumUpToIndex(Array<T> arrayIn, int indexIn)
{
    indexIn = totalModulus(indexIn, arrayIn.size());
    T sum = 0;

    for (int i = 0; i < indexIn; i++)
    {
        sum += arrayIn[i];
    }

    return sum;
}

template <class T>
static T sumUpToRingIndex(Array<T> arrayIn, int indexIn)
{
    T sum = 0;

    for (int i = 0; i < indexIn; i++)
    {
        sum += arrayIn[i % arrayIn.size()];
    }

    return sum;
}

template <class T>
Array<T> reverseArray(const Array<T>& arrayIn)
{
    Array<T> outArray;
    outArray.resize(arrayIn.size());

    for (int i = 0; i < arrayIn.size(); i++)
    {
        outArray.set(i, arrayIn.getUnchecked(arrayIn.size() - i - 1));
    }

    return outArray;
}

template <class T>
String arrayToString(const Array<T>& arrayIn)
{
    String arrayString = "";

    for (int i = 0; i < arrayIn.size(); i++)
    {
        arrayString+= String(arrayIn[i]);

        if (i + 1 < arrayIn.size())
            arrayString += ", ";
    }

    return arrayString;
}

template <class T>
static void DBGArray(Array<T>& arrayIn, String arrayName="Array")
{
    DBG(arrayName + ":");

    for (int i = 0; i < arrayIn.size(); i++)
    {
        DBG(String(i) + ": " + String(arrayIn[i]));
    }
}

static double getStdFrequency(int midiNoteIn)
{
    return 440.0 * pow(2, (midiNoteIn - 69) / 12.0);
}


text:
/*
  ==============================================================================

    PluginModes.h
    Created: 25 Jul 2020 5:39:15pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>

namespace VirtualKeyboard
{
    enum UIMode
    {
        playMode = 0,
        editMode,
        mapMode
    };

    enum Orientation
    {
        horizontal = 0,
        verticalLeft,
        verticalRight
    };

    enum HighlightStyle
    {
        full = 1,
        inside,
        outline,
        circles,
        squares
    };

    enum VelocityStyle
    {
        linear = 1,
        curved,
        fixed
    };

    enum ScrollingStyle
    {
        smooth = 1,
        stepped
    };

    enum KeyPlacementType
    {
        nestedRight = 1,
        nestedCenter,
        flat,
        adjacent
    };

    enum KeyShapeFlags
    {
        Basic = 0x0001,
        NotchPrevious = 0x0010,
        NotchNext = 0x0100,
        NotchCenter = 0x1000
    };
}

enum MappingStyle
{
    ModeToMode = 1,
    ModeToScale,
    ModeByOrder
};

enum PluginStateNodeStatus
{
    InvalidNode = 0,
    CurrentVersion,
    AlphaVersion,
    IsPresetNode = 0x0100,
    IsAPVTSNode = 0x1000
};


text:
/*
  ==============================================================================

    PluginState.h
    Created: 7 Apr 2019 12:27:01pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include "JuceHeader.h"

#include "CommonFunctions.h"
#include "Constants.h"
#include "PluginIDs.h"
#include "PluginModes.h"
#include "PluginSettings.h"
#include "Midi/MidiProcessor.h"
#include "PresetManager.h"
#include "Structures/Preset.h"
#include "Structures/ModeMapper.h"

class SvkPluginState : public ChangeListener, public ValueTree::Listener
                        //private AudioProcessorValueTreeState::Listener
{
public:
    AudioProcessorValueTreeState& svkTree;
    ValueTree pluginStateNode;
    //ValueTree pluginSettingsNode;
    //ValueTree midiSettingsNode;
    //ValueTree modeLibraryNode;
    //ValueTree pianoNode;

    SvkPluginState(AudioProcessorValueTreeState&);
    ~SvkPluginState() {}

    //==============================================================================
    // Data

    void recallState(ValueTree nodeIn, bool fallbackToDefaultSettings = false);

    // TODO: make it so it's not necessary to call this before saving (?)
    void commitStateNode();

    static int isValidStateNode(ValueTree pluginStateNodeIn);

    //==============================================================================
    // Object getters

    ValueTree getPluginEditorNode() const;
    ValueTree getMappingNode() const;

    SvkPresetManager* getPresetManager();
    SvkMidiProcessor* getMidiProcessor();
    SvkPluginSettings* getPluginSettings();

    ModeMapper* getModeMapper();
    MappingHelper* getManualMappingHelper();

    NoteMap* getMidiInputFilterMap();
    NoteMap* getMidiOutputFilterMap();

    ValueTree getPresetNode();

    Mode* getModeInSlot(int slotNumIn) const;
    Mode* getModeViewed();
    Mode* getMode1();
    Mode* getMode2();
    Mode* getModeCustom();

    //==============================================================================
    // Parameter Getters

    //float getParameterValue(Identifier paramId);
    //float getParameterMin(Identifier paramId);
    //float getParameterMax(Identifier paramId);
    //float getParameterDefaultValue(Identifier paramId);

    int getNumModesLoaded() const;
    int getModeSelectorViewed() const;
    int getModeViewedSlotNumber() const;

    int getMappingMode() const;
    int getAutoMappingStyle() const;
    bool isAutoMapping() const;

    //bool isAutoRetuning();

    int getModeSlotRoot(int slotNum) const;
    int getMode1Root() const;
    int getMode2Root() const;

    bool isPresetEdited();

    // ValueTree::Listener Implementation
    void valueTreePropertyChanged(ValueTree&, const Identifier&) override;

    void valueTreeChildAdded(ValueTree& parentTree, ValueTree& child) override;

    void valueTreeChildRemoved(ValueTree& parentTree, ValueTree& child, int index) override;

    //==============================================================================

    //void setParameterValue(Identifier paramIdIn, float valueIn);

    void setMapMode(int mapModeSelectionIn, bool sendChangeMessage = true);
    void setAutoMapStyle(int mapStyleIn);

    void setModeSelectorRoot(int modeSlotIn, int rootNoteIn, bool updateParameter=false);

    /*
        Should be called whenever a mode is initialized in the PresetManager
    */
    void onModeUpdate(bool sendModeViewedChangeMessage = true, bool sendMappingChangeMessage = true);

	void sendChangeNotificationToHost();

    //==============================================================================
    // User Functionality

    void setModeSelectorViewed(int modeViewedIn);
    void handleModeSelection(int modeBoxNum, int idIn);

    void setMidiInputMap(NoteMap noteMapIn, bool updateNode, bool sendChangeMessage = true);
    void setMidiOutputMap(NoteMap noteMapIn, bool updateNode);

    void setModeCustom(String stepsIn, bool sendChangeMessage = true);

    void setMapOrder1(int orderIn);
    void setMapOrder2(int orderIn);
    void setMapOrderOffset1(int offsetIn);
    void setMapOrderOffset2(int offsetIn);

    void doAutoMapping(const Mode* mode1, const Mode* mode2, bool sendChangeMessage = true);
    void doAutoMapping(bool sendChangeMessage = true);

    void revertToSavedState(bool fallbackToDefaultSettings = false, bool sendChange = true);

    bool savePresetToFile();
    bool loadPresetFromFile();

    bool saveModeViewedToFile();
    bool loadModeFromFile();

    void commitModeInfo(bool sendChangeMessage = true);

    //==============================================================================

    //void parameterChanged(const String& paramID, float newValue) override;

    void changeListenerCallback(ChangeBroadcaster* source) override;

    //==============================================================================

    class Listener
    {
    public:

        virtual ~Listener() {};

        virtual void presetLoaded(ValueTree presetNodeIn) {};

        virtual void resyncPresetNode(ValueTree presetNodeIn) {}; // TODO: make unnecessary

        virtual void modeViewedChanged(Mode* modeIn, int selectorNumber, int slotNumber) {};

        virtual void mappingModeChanged(int mappingModeId) {};

        virtual void inputMappingChanged(NoteMap& inputNoteMap) {};

        virtual void outputMappingChanged(NoteMap& outputNoteMap) {};

        virtual void customModeChanged(Mode* newCustomMode) {};

        virtual void modeInfoChanged(Mode* modeEdited) {};
    };

    void addListener(SvkPluginState::Listener* listenerIn) { listeners.add(listenerIn); }

    void removeListener(SvkPluginState::Listener* listenerIn) { listeners.remove(listenerIn); }

protected:

    ListenerList<Listener> listeners;

private:
    //==============================================================================
    // Internal Functionality

    void buildFactoryDefaultState();
    void buildUserDefaultState();

    void updateModeViewed(bool sendChange=true);

    void syncNodes(bool sendChange=true);

private:

    std::unique_ptr<SvkPresetManager> presetManager;
    std::unique_ptr<SvkMidiProcessor> midiProcessor;
    std::unique_ptr<SvkPluginSettings> pluginSettings;

    std::unique_ptr<ModeMapper> modeMapper;
    std::unique_ptr<MappingHelper> manualMappingHelper;

    AudioParameterBool* notifyHostDummy;
    bool notifyHostValue = true;

    ValueTree factoryDefaultPluginStateNode;
    ValueTree defaultPluginStateNode;

    SvkPreset* svkPreset;
    ValueTree presetNode;

    Mode* modeViewed; // What is currently on screen
    int modeSelectorViewedNum = 1;

    bool presetEdited = false;
};


text:
/*
  ==============================================================================

    MidiProcessor.cpp
    Created: 21 May 2019 6:37:57pm
    Author:  Vincenzo Sicurella

  ==============================================================================
*/

#include "MidiProcessor.h"

SvkMidiProcessor::SvkMidiProcessor()
{
    midiSettingsNode = ValueTree(IDs::midiSettingsNode);
    midiMapNode = ValueTree(IDs::midiMapNode);

    midiSettingsNode.addChild(midiMapNode, -1, nullptr);

    midiInputFilter = std::make_unique<MidiFilter>();
    midiOutputFilter = std::make_unique<MidiFilter>();

    midiInput = nullptr;
    midiOutput = nullptr;

    mpeInst.reset(new MPEInstrument());

    originalKeyboardState.reset(new MidiKeyboardState());
    remappedKeyboardState.reset(new MidiKeyboardState());

    setRootNote(60);
}

SvkMidiProcessor::~SvkMidiProcessor()
{

}

void SvkMidiProcessor::updateNode()
{
    midiSettingsNode.setProperty(IDs::rootMidiNote, rootMidiNote, nullptr);
    midiSettingsNode.setProperty(IDs::pianoMidiChannel, midiChannelOut, nullptr);
    midiSettingsNode.setProperty(IDs::periodShift, periodShift, nullptr);
    midiSettingsNode.setProperty(IDs::periodShiftModeSize, useModePeriod, nullptr);
    midiSettingsNode.setProperty(IDs::transposeAmt, transposeAmt, nullptr);
    midiSettingsNode.setProperty(IDs::mpeOn, mpeOn, nullptr);
    midiSettingsNode.setProperty(IDs::mpeThru, mpeThru, nullptr);
    midiSettingsNode.setProperty(IDs::mpeZone, 0 /*do something here*/, nullptr);
    midiSettingsNode.setProperty(IDs::mpeLegacyOn, mpeLegacyOn, nullptr);
    midiSettingsNode.setProperty(IDs::pitchBendNoteMax, pitchBendNoteMax, nullptr);
    midiSettingsNode.setProperty(IDs::pitchBendGlobalMax, pitchBendGlobalMax, nullptr);
    midiSettingsNode.setProperty(IDs::mpePitchTrackingMode, mpePitchTrackingMode, nullptr);
    midiSettingsNode.setProperty(IDs::mpePressureTrackingMode, mpePressureTrackingMode, nullptr);
    midiSettingsNode.setProperty(IDs::mpeTimbreTrackingMode, mpeTimbreTrackingMode, nullptr);

    Array<int> inputMap = midiInputFilter->getRemappedNotes();
    midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputMap), nullptr);
    add_array_to_node(midiMapNode, inputMap, IDs::midiInputMap, "Note");

    Array<int> outputMap = midiOutputFilter->getRemappedNotes();
    midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiOutputMap), nullptr);
    add_array_to_node(midiMapNode, outputMap, IDs::midiOutputMap, "Note");
}

bool SvkMidiProcessor::restoreFromNode(ValueTree midiSettingsNodeIn)
{
    if (midiSettingsNodeIn.hasType(IDs::midiSettingsNode))
    {
        midiSettingsNode.copyPropertiesAndChildrenFrom(midiSettingsNodeIn, nullptr);

        // Root note
        rootMidiNote = midiSettingsNode.getProperty(IDs::rootMidiNote);

        // Set Note Maps
        if (!midiSettingsNode.getChildWithName(IDs::midiMapNode).isValid())
        {
            midiMapNode = ValueTree(IDs::midiMapNode);
            midiSettingsNode.addChild(midiMapNode, -1, nullptr);
        }

        setMidiMaps(midiSettingsNode.getChildWithName(IDs::midiMapNode));

        return true;
    }

    return false;
}

//==============================================================================

StringArray SvkMidiProcessor::getAvailableInputs() const
{
    return MidiInput::getDevices();
}

Array<MidiDeviceInfo> SvkMidiProcessor::getAvailableOutputs() const
{
    return MidiOutput::getAvailableDevices();
}

MidiInput* SvkMidiProcessor::getInputDevice()
{
    return midiInput.get();
}

MidiOutput*  SvkMidiProcessor::getOutputDevice()
{
    return midiOutput.get();
}

String SvkMidiProcessor::getInputName() const
{
    return midiInputName;
}

String SvkMidiProcessor::getOutputName() const
{
    return midiOutputName;
}

MidiKeyboardState* SvkMidiProcessor::getOriginalKeyboardState()
{
    return originalKeyboardState.get();
}

MidiKeyboardState* SvkMidiProcessor::getRemappedKeyboardState()
{
    return remappedKeyboardState.get();
}

int SvkMidiProcessor::getRootNote() const
{
    return rootMidiNote;
}

int SvkMidiProcessor::getPeriodShift() const
{
    return periodShift;
}

int SvkMidiProcessor::getMidiChannelOut() const
{
    return midiChannelOut;
}

int SvkMidiProcessor::getTransposeAmt() const
{
    return transposeAmt;
}

bool SvkMidiProcessor::isMPEOn() const
{
    return mpeOn;
}

bool SvkMidiProcessor::isMPELegacyMode() const
{
    return mpeLegacyOn;
}

bool SvkMidiProcessor::isMPEThru() const
{
    return mpeThru;
}

MPEZoneLayout SvkMidiProcessor::getMPEZone() const
{
    return mpeZone;
}

int SvkMidiProcessor::getPitchBendNoteMax() const
{
    return pitchBendNoteMax;
}

int SvkMidiProcessor::getPitchBendGlobalMax() const
{
    return pitchBendGlobalMax;
}

int SvkMidiProcessor::getPitchTrackingMode() const
{
    return mpePitchTrackingMode;
}

int SvkMidiProcessor::getPressureTrackingMode() const
{
    return mpePressureTrackingMode;
}

int SvkMidiProcessor::getTimbreTrackingMode() const
{
    return mpeTimbreTrackingMode;
}

NoteMap* SvkMidiProcessor::getInputNoteMap()
{
    return midiInputFilter->getNoteMap();
}

NoteMap* SvkMidiProcessor::getOutputNoteMap()
{
    return midiOutputFilter->getNoteMap();
}

MidiFilter* SvkMidiProcessor::getMidiInputFilter()
{
    return midiInputFilter.get();
}

MidiFilter* SvkMidiProcessor::getMidiOutputFilter()
{
    return midiOutputFilter.get();
}

int SvkMidiProcessor::getInputNote(int midiNoteIn)
{
    return midiInputFilter->getNoteRemapped(midiNoteIn);
}

int SvkMidiProcessor::getOutputNote(int midiNoteIn)
{
    return midiOutputFilter->getNoteRemapped(midiNoteIn);
}

//==============================================================================

String SvkMidiProcessor::setMidiInput(String deviceID)
{
    midiInput = MidiInput::openDevice(deviceID, this);
    midiInputName = midiInput->getName();
    if (midiInputName.isNotEmpty())
    {
        inputSelected = deviceID;
        midiSettingsNode.setProperty(IDs::midiInputName, midiInput->getIdentifier(), nullptr);
    }
    else
    {
        inputSelected = "";
        midiInput = nullptr;
    }

    return midiInputName;
}

String SvkMidiProcessor::setMidiOutput(String deviceID)
{
    midiOutput = MidiOutput::openDevice(deviceID);

    if (midiOutput.get())
    {
        midiOutputName = midiOutput->getName();
        outputSelected = deviceID;
        midiSettingsNode.setProperty(IDs::midiOutputName, midiOutput->getIdentifier(), nullptr);
    }
    else
    {
        midiOutputName = "";
        outputSelected = "";
        midiOutput = nullptr;
    }

    return midiOutputName;
}

void SvkMidiProcessor::setModeViewed(Mode* modeViewedIn)
{
    modeViewed = modeViewedIn;
}

void SvkMidiProcessor::setMode1(Mode* mode1In)
{
    mode1 = mode1In;
}

void SvkMidiProcessor::setMode2(Mode* mode2In)
{
    mode2 = mode2In;
}

void SvkMidiProcessor::setRootNote(int rootNoteIn)
{
    midiSettingsNode.setProperty(IDs::rootMidiNote, rootNoteIn, nullptr);
    rootMidiNote = rootNoteIn;
}

void SvkMidiProcessor::setPeriodShift(int shiftIn)
{
    midiSettingsNode.setProperty(IDs::periodShift, periodShift, nullptr);
    periodShift = shiftIn;
}

void SvkMidiProcessor::periodUsesModeSize(bool useModeSizeIn)
{
    useModePeriod = useModeSizeIn;
}

void SvkMidiProcessor::setMidiChannelOut(int channelOut)
{
    if (channelOut < 1)
        channelOut = 1;

    channelOut = channelOut % 16;

    midiSettingsNode.setProperty(IDs::pianoMidiChannel, channelOut, nullptr);
    midiChannelOut = channelOut;
}

void SvkMidiProcessor::setTransposeAmt(int notesToTranspose)
{
    transposeAmt = notesToTranspose;
}

void SvkMidiProcessor::setMPEOn(bool turnOnMPE)
{
    mpeOn = turnOnMPE;
}

void SvkMidiProcessor::setMPEThru(bool mpeOnThru)
{
    mpeThru = mpeOnThru;
}

void SvkMidiProcessor::setMPELegacy(bool turnOnLegacy)
{
    mpeLegacyOn = turnOnLegacy;
}

void SvkMidiProcessor::setMPEZone(MPEZoneLayout zoneIn)
{
    mpeZone = zoneIn;
}

void SvkMidiProcessor::setPitchBendNoteMax(int bendAmtIn)
{
    pitchBendNoteMax = bendAmtIn;
}

void SvkMidiProcessor::setPitchBendGlobalMax(int bendAmtIn)
{
    pitchBendGlobalMax = bendAmtIn;
}

void SvkMidiProcessor::setPitchTrackingMode(int modeIn)
{
    mpePitchTrackingMode = modeIn;
}

void SvkMidiProcessor::setPressureTrackingMode(int modeIn)
{
    mpePressureTrackingMode = modeIn;
}

void SvkMidiProcessor::setTimbreTrackingMode(int modeIn)
{
    mpeTimbreTrackingMode = modeIn;
}

void SvkMidiProcessor::setInputToRemap(bool doRemap)
{
    isInputRemapped = doRemap;
}

void SvkMidiProcessor::setOutputToRemap(bool doRemap)
{
    isOutputRemapped = doRemap;
}

void SvkMidiProcessor::setMidiMaps(ValueTree midiMapIn)
{
    if (midiMapIn.hasType(IDs::midiMapNode))
    {
        Array<int> map;

        get_array_from_node(midiMapIn, map, IDs::midiInputMap);

        if (map.size() > 0)
            setMidiInputMap(map);
        else
            setMidiInputMap(NoteMap());

        map.clear();
        get_array_from_node(midiMapIn, map, IDs::midiOutputMap);

        if (map.size() > 0)
            setMidiOutputMap(map);
        else
            setMidiOutputMap(NoteMap());

        midiMapNode.copyPropertiesAndChildrenFrom(midiMapIn, nullptr);
    }
}

void SvkMidiProcessor::setMidiInputMap(Array<int> mapIn, bool updateNode)
{
    midiInputFilter->setNoteMap(mapIn);

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputMap), nullptr);
        add_array_to_node(midiMapNode, midiInputFilter->getNoteMap()->getValues(), IDs::midiInputMap, "Note");
    }
}

void SvkMidiProcessor::setMidiInputMap(NoteMap mapIn, bool updateNode)
{
    midiInputFilter->setNoteMap(mapIn);

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputMap), nullptr);
        add_array_to_node(midiMapNode, midiInputFilter->getNoteMap()->getValues(), IDs::midiInputMap, "Note");
    }
}

void SvkMidiProcessor::setMidiOutputMap(Array<int> mapIn, bool updateNode)
{
    midiOutputFilter->setNoteMap(mapIn);

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiOutputMap), nullptr);
        add_array_to_node(midiMapNode, midiInputFilter->getNoteMap()->getValues(), IDs::midiOutputMap, "Note");
    }
}

void SvkMidiProcessor::setMidiOutputMap(NoteMap mapIn, bool updateNode)
{
    midiOutputFilter->setNoteMap(mapIn.getValues());

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiOutputMap), nullptr);
        add_array_to_node(midiMapNode, midiInputFilter->getNoteMap()->getValues(), IDs::midiOutputMap, "Note");
    }
}

void SvkMidiProcessor::resetInputMap(bool updateNode)
{
    setMidiInputMap(NoteMap(), updateNode);
}

void SvkMidiProcessor::resetOutputMap(bool updateNode)
{
    setMidiOutputMap(NoteMap(), updateNode);
}

void SvkMidiProcessor::mapInputNote(int noteIn, int noteOut, bool updateNode)
{
    midiInputFilter->setNote(noteIn, noteOut);

    if (updateNode)
        set_value_in_array(midiMapNode, IDs::midiInputMap, noteIn, noteOut);
}

void SvkMidiProcessor::mapOutputNode(int noteIn, int noteOut, bool updateNode)
{
    midiOutputFilter->setNote(noteIn, noteOut);

    if (updateNode)
        set_value_in_array(midiMapNode, IDs::midiInputMap, noteIn, noteOut);
}

//==============================================================================

void SvkMidiProcessor::processMidi(MidiBuffer& midiMessages)
{
    // Input filtering
    auto midiEvent = MidiBuffer::Iterator(midiMessages);
    MidiMessage msg;
    int smpl;
    int midiNote;

    if (midiInput == nullptr)
    {
        while (midiEvent.getNextEvent(msg, smpl))
        {
            originalKeyboardState->processNextMidiEvent(msg);
            midiNote = msg.getNoteNumber();

            if (isInputRemapped)
                midiNote = midiInputFilter->getNoteRemapped(msg.getNoteNumber());

            msg.setNoteNumber(midiNote);

            if (midiNote >= 0 && midiNote < 128)
            {
                msg.setTimeStamp(++msgCount);
                remappedKeyboardState->processNextMidiEvent(msg);
                addMessageToQueue(msg);
            }
        }
    }

    // Midi Output filtering
    msgCount = 0;
    midiMessages.clear();
    removeNextBlockOfMessages(midiBuffer, 4096);

    if (!midiInputPaused)
    {
        auto midiEventOut = MidiBuffer::Iterator(midiBuffer);
        while (midiEventOut.getNextEvent(msg, smpl))
        {
            int midiNote = msg.getNoteNumber();
            midiNote = midiOutputFilter->getNoteRemapped(msg.getNoteNumber());
            midiNote += periodShift * mode2->getScaleSize();
            msg.setNoteNumber(midiNote);
            msg.setTimeStamp(++msgCount);

            midiMessages.addEvent(msg, smpl);
            sendMsgToOutputs(msg);
        }
    }

    midiBuffer.clear();
    msgCount = 0;
}

void SvkMidiProcessor::sendMsgToOutputs(const MidiMessage& msg)
{
    if (midiOutput)
        midiOutput->sendMessageNow(msg);
}

//==============================================================================

void SvkMidiProcessor::pauseMidiInput(bool setPaused)
{
    midiInputPaused = setPaused;
}

bool SvkMidiProcessor::isMidiPaused()
{
    return midiInputPaused;
}

//==============================================================================

void SvkMidiProcessor::handleNoteOn(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity)
{
    MidiMessage msg = MidiMessage::noteOn(midiChannel, midiNoteNumber, velocity);
    msg.setTimeStamp(++msgCount);
    addMessageToQueue(msg);
}

void SvkMidiProcessor::handleNoteOff(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity)
{
    MidiMessage msg = MidiMessage::noteOn(midiChannel, midiNoteNumber, velocity);
    msg.setTimeStamp(++msgCount);
    addMessageToQueue(msg);
}

void SvkMidiProcessor::handleIncomingMidiMessage(MidiInput* source, const MidiMessage& msg)
{
    MidiMessage myMsg = MidiMessage(msg);

    if (isInputRemapped && !midiInputPaused)
        myMsg.setNoteNumber(midiInputFilter->getNoteRemapped(myMsg.getNoteNumber()));

    addMessageToQueue(myMsg);
}


text:
/*
  ==============================================================================

    PluginSettings.h
    Created: 27 May 2019 1:46:06pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginIDs.h"
#include "CommonFunctions.h"
#include "Constants.h"

class SvkPluginSettings
{
    const String appFolderName = "SuperVirtualKeyboard";
    const String settingsFileName = "SuperVirtualKeyboard.svksettings";
    const String presetSubDirectory = "Presets";
    const String modeSubDirectory = "Modes";

    File factoryDefaultSettingsLocation = File::getSpecialLocation(File::userApplicationDataDirectory).getChildFile(appFolderName);
    File factoryDefaultPresetLocation = File::getSpecialLocation(File::userDocumentsDirectory).getChildFile(appFolderName).getChildFile(presetSubDirectory);
    File factoryDefaultModeLocation = File::getSpecialLocation(File::userDocumentsDirectory).getChildFile(appFolderName).getChildFile(modeSubDirectory);

    File currentSettingsLocation;
    File currentPresetLocation;
    File currentModeLocation;

    ValueTree pluginSettingsNode;

    bool createPresetFolder = true;
    bool saveFactoryModes = false;

    bool resetDirectories = false;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SvkPluginSettings)

public:

    SvkPluginSettings();
    ~SvkPluginSettings();

    ValueTree getSettingsNode();

    bool updateNode(bool writeSettings=false);
    bool restoreNode(ValueTree pluginSettingsNodeIn);

    String getSettingsPath();
    String getPresetPath();
    String getModePath();

    bool getCreatePresetFolder();
    bool getSaveFactoryModes();

    void setPresetDirectory(File presetDirectoryIn);
    void setModeDirectory(File modeDirectoryIn);
    void setSettingsDirectory(File settingsDirectoryIn);

    void setCreatePresetFolder(bool shouldCreateFolder);
    void setSaveFactoryPresets(bool shouldSavePresets);

};


text:
/*
  ==============================================================================

    PresetManager.h
    Created: 27 May 2019 10:57:01pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

#include "CommonFunctions.h"
#include "PluginIDs.h"
#include "Structures/Preset.h"
#include "Structures/Mode.h"

class SvkPresetManager : public ChangeBroadcaster
{
    Array<ValueTree> loadedFactoryModes;
    Array<ValueTree> loadedUserModes;
    Array<ValueTree> favoriteModes;

    Array<Array<ValueTree>> modesSorted;
    int numSortTypes = 4;

    ScaleSizeSorter scaleSizeSort;
    ModeSizeSorter modeSizeSort;
    FamilyNameSorter familyNameSort;

    SvkPreset svkPresetSaved;
    SvkPreset svkPresetWorking;

    OwnedArray<Mode> modeSlots;
    std::unique_ptr<Mode> modeCustom;

    // Methods
    void createFactoryModes();
    void resortModeLibrary();

    void initializeModePresets();
    void loadModeDirectory();

    int addModeToLibrary(ValueTree modeNodeIn);
    void addModeToSort(ValueTree modeNodeIn);
    int addAndSortMode(ValueTree modeNodeIn);

public:

    ValueTree presetLibraryNode;
    ValueTree modeLibraryNode;
    ValueTree pluginSettingsNode;

    SvkPresetManager(ValueTree pluginSettingsNodeIn);
    ~SvkPresetManager();

    SvkPreset& getPreset();
    int getNumMenuItems(bool withFactoryMenu=true, bool withUserMenu=true, bool withFavMenu=true, bool withSlots=true);

    int getNumModeSlots() const;

    Array<int> getModeSlotsInUse() const;

    int getModeSlotOfSelector(int modeSelectorNumIn) const;

    ValueTree getModeInLibrary(int indexIn);

    /*
        Returns the mode in the given mode slot number
    */
    Mode* getModeInSlot(int modeSlotNumIn);

    /*
        Returns the mode used by given selector
        If the selector is set to an invalid slot, this will return nullptr
    */
    Mode* getModeBySelector(int selectorNumber);

    Mode* getModeCustom();

    Mode* setModeCustom(ValueTree modeNodeIn);
    Mode* setModeCustom(String stepsIn, String familyIn = "undefined", String nameIn = "", String infoIn = "", int rootNoteIn = 60);

    int setSlotToMode(int modeSlotNum, ValueTree modeNode);
    int addSlot(ValueTree modeNode);
    int setSlotAndSelection(int modeSlotNum, int modeSelectorNum, ValueTree modeNode);
    int addSlotAndSetSelection(int modeSelectorNumber, ValueTree modeNode);

    void removeMode(int modeSlotNum);
    void resetModeSlots();

    /*
        Replaces the mode slot the selector is pointed to with a new mode
        - See note above definition -
    */
    void handleModeSelection(int selectorNumber, int idIn);

    bool loadPreset(ValueTree presetNodeIn, bool sendChangeSignal=true);

    bool saveNodeToFile(ValueTree nodeToSave, String saveMsg, String fileEnding, String absolutePath = "");
    bool savePresetToFile(String absolutePath="");
    bool saveModeToFile(int modeSlotNumber, String absolutePath = "");

    /*
        Saves changes made since the last preset recall
        Returns true if successful
    */
    bool commitPreset();

    /*
        Reverts the changes made since the last recall
        Returns true if successful
    */
    bool resetToSavedPreset();

    static ValueTree nodeFromFile(String openMsg, String fileEnding, String absoluteFilePath = "");
    static ValueTree modeFromFile(String absoluteFilePath = "");
    static ValueTree presetFromFile(String absoluteFilePath = "");

    void requestModeMenu(PopupMenu* comboBoxToUse);

private:
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SvkPresetManager)
};


text:
/*
  ==============================================================================

    ReaperWriter.h
    Created: 18 Apr 2019 12:22:34pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include "../../JuceLibraryCode/JuceHeader.h"
#include "../Structures/Mode.h"

class ReaperWriter
{
    Mode* mode;
    //Scale* scale;

    Array<String> orderSymbols;

    File filePath;
    File fileOut;

    void setup_default_symbols();

public:

    ReaperWriter(Mode* modeIn);
    ~ReaperWriter();

    void set_mode(Mode* modeIn);
    void set_symbol(int orderIndex, String symbolIn);
    bool set_path(String pathIn);

    String ask_for_location();

    Array<String> get_symbols();
    String get_symbol(int orderIndexIn);
    String get_path();

    bool write();

};


text:
/*
  ==============================================================================

    KbmWriter.h
    Created: 23 Oct 2020 11:37:09pm
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>
#include "../Structures/ModeMapper.h"

class KbmWriter
{
public:

    KbmWriter(
        int sizeIn, int firstMidiNoteIn, int lastMidiNoteIn, int mappingRootNoteIn,
        int referenceMidiNoteIn, double referenceFreqIn, int periodDegreeIn,
        Array<int> mappingIn
    ) :
        size(sizeIn), firstMidiNote(firstMidiNoteIn), lastMidiNote(lastMidiNoteIn), mappingRootMidiNote(mappingRootNoteIn),
        referenceMidiNote(referenceMidiNoteIn), referenceFrequency(referenceFreqIn), periodDegree(periodDegreeIn),
        mapping(mappingIn) {}

    bool writeTo(const File& fileToWriteTo)
    {
        if (!fileToWriteTo.existsAsFile())
        {
            if (!fileToWriteTo.create())
                return false;
        }

        StringArray valuesOut =
        {
            String(size), String(firstMidiNote), String(lastMidiNote),
            String(mappingRootMidiNote), String(referenceMidiNote),
            String(referenceFrequency), String(periodDegree), ""
        };

        // Ensure frequency is floating point
        if (!valuesOut[5].containsChar('.'))
            valuesOut.getReference(5) += ".0";

        // Change OOB notes to x
        for (auto deg : mapping)
        {
            String degOut = (deg < 0 || deg > 127) ? "x" : String(deg);
            valuesOut.getReference(7) += degOut + '\n';
        }

        String fileTextOut;
        for (int i = 0; i < sectionLabels.size(); i++)
        {
            if (i > 0)
                fileTextOut += '\n';

            fileTextOut += sectionLabels[i];
            fileTextOut += valuesOut[i];
        }

        fileToWriteTo.replaceWithText(fileTextOut);

        return true;
    }

    static KbmWriter fromModes(
        const Mode* inputMode, const Mode* outputMode, const ModeMapper& mapperIn,
        int firstMidiNoteIn = 0, int lastMidiNoteIn = 127, int mappingRootNoteIn = -1,
        int referenceMidiNoteIn = 69, double referenceFreqIn = 440.0, int periodDegreeIn = -1)
    {
        Array<int> periodMap = mapperIn.getSelectedPeriodMap(*inputMode, *outputMode);

        int middleNote = mappingRootNoteIn;
        int period = periodDegreeIn;

        int size = periodMap.size();

        if (periodMap.size() >= 128)
        {
            middleNote = 0;
            period = 127;
            size = 0;
        }
        else
        {
            if (middleNote < 0 || middleNote > 127)
                middleNote = outputMode->getRootNote();

            if (period < 0)
                period = outputMode->getScaleSize();
        }

        return KbmWriter(
            size, jlimit(0, 127, firstMidiNoteIn), jlimit(0, 127, lastMidiNoteIn), middleNote,
            jlimit(0, 127, referenceMidiNoteIn), jlimit(10e-3, 20e3, referenceFreqIn), period, periodMap
        );
    }

    //static KbmWriter fromNoteMap(const NoteMap& noteMapIn)
    //{
    //    Array<int> mapping;
    //}

private:

    int size;
    int firstMidiNote;
    int lastMidiNote;
    int mappingRootMidiNote;
    int referenceMidiNote;
    double referenceFrequency;
    int periodDegree;
    Array<int> mapping;

    StringArray sectionLabels =
    {
        "! Template for a keyboard mapping\n!\n! Size of map. The pattern repeats every so many keys:\n",
        "! First MIDI note number to retune:\n",
        "! Last MIDI note number to retune:\n",
        "! Middle note where the first entry of the mapping is mapped to:\n",
        "! Reference note for which frequency is given:\n",
        "! Frequency to tune the above note to (floating point e.g. 440.0):\n",
        "! Scale degree to consider as formal octave (determines difference in pitch\n! between adjacent mapping patterns):\n",
        "! Mapping.\n! The numbers represent scale degrees mapped to keys. The first entry is for\n"
        "! the given middle note, the next for subsequent higher keys.\n"
        "! For an unmapped key, put in an \"x\". At the end, unmapped keys may be left out.\n"
    };
};


text:
/*
  ==============================================================================

    AbletonMidiSequence.h
    Created: 14 Sep 2020 1:05:22am
    Author:  Vincenzo

  ==============================================================================
*/

#pragma once

#include "../Structures/Mode.h"

static File abletonUserLibrary =
#if JUCE_WINDOWS
    File::getSpecialLocation(File::userDocumentsDirectory).getChildFile("Ableton").getChildFile("User Library")
#else
    File::getSpecialLocation(File::userMusicDirectory).getChildFile("Ableton").getChildFile("User Library")
#endif
;

class AbletonMidiWriter
{

public:

    AbletonMidiWriter(const Mode& modeIn)
        : mode(modeIn)
    {
        refreshSequence();
    };

    bool writeTo(File directory = abletonUserLibrary, String filename = "")
    {
        if (!directory.exists())
            directory = File::getSpecialLocation(File::userDocumentsDirectory);

        if (filename.length() == 0)
            filename = mode.getName();

        if (!filename.endsWith(".mid"))
            filename += ".mid";

        File fileOut = File::createTempFile(".mid");

        midiFile.clear();
        midiFile.addTrack(sequence);
        midiFile.setTicksPerQuarterNote(96);

        File intendedFile = directory.getChildFile(filename);
        FileOutputStream stream(intendedFile);
        if (midiFile.writeTo(stream))
        {
            stream.flush();
            DBG("File written");
            return true;
        }

        return false;
    }

    bool write(String filename = "")
    {
        if (filename.length() == 0)
            filename = mode.getName();

        if (!filename.endsWith(".mid"))
            filename += ".mid";

        File intendedFile = abletonUserLibrary.exists()
            ? abletonUserLibrary.getChildFile(filename)
            : File::getSpecialLocation(File::userDocumentsDirectory).getChildFile(filename);

        FileChooser chooser("Save Ableton Midi Map", intendedFile, "*.mid");

        if (chooser.browseForFileToSave(true))
            return writeTo(chooser.getResult().getParentDirectory(), chooser.getResult().getFileName());

        return false;
    }

    void refreshSequence()
    {
        Array<int> noteNums = mode.getNotesOfOrder();
        sequence.clear();

        for (auto noteNum : noteNums)
        {
            MidiMessage msg = MidiMessage::noteOn(1, noteNum, (uint8)1);
            sequence.addEvent(msg);
        }

        for (auto noteNum : noteNums)
        {
            MidiMessage msg = MidiMessage::noteOff(1, noteNum);
            sequence.addEvent(msg, 96);
        }

        sequence.updateMatchedPairs();
    }

private:

    const Mode& mode;

    MidiFile midiFile;
    MidiMessageSequence sequence;
};


text:
/*
  ==============================================================================

    ReaperWriter.cpp
    Created: 18 Apr 2019 12:22:34pm
    Author:  Vincenzo

  ==============================================================================
*/

#include "ReaperWriter.h"

ReaperWriter::ReaperWriter(Mode* modeIn)
{
    mode = modeIn;

    // check if reaper resource path exists

    if ((SystemStats::getOperatingSystemType() & SystemStats::OperatingSystemType::MacOSX) != 0)
    {
        filePath = File(File::getSpecialLocation(File::userApplicationDataDirectory).getChildFile("Application Support").getChildFile("REAPER").getChildFile("MIDINoteNames"));
    }
    else if ((SystemStats::getOperatingSystemType() & SystemStats::OperatingSystemType::Windows) != 0)
    {
        filePath = File(File::getSpecialLocation(File::userHomeDirectory).getChildFile("AppData").getChildFile("Roaming").getChildFile("REAPER").getChildFile("MIDINoteNames"));
    }

    if (!filePath.isDirectory())
       filePath = File::getSpecialLocation(File::userDocumentsDirectory);

    FileChooser chooser("Save as", filePath, "*.txt");
    chooser.browseForFileToSave(true);
    fileOut = chooser.getResult();

    setup_default_symbols();
}

ReaperWriter::~ReaperWriter()
{
}

void ReaperWriter::set_mode(Mode* modeIn)
{
    mode = modeIn;
}

void ReaperWriter::set_symbol(int orderIndex, String symbolIn)
{
    orderSymbols.set(orderIndex % orderSymbols.size(), symbolIn);
}

bool ReaperWriter::set_path(String pathIn)
{
    fileOut = File(pathIn);
    return fileOut.getParentDirectory().isDirectory();
}

String ReaperWriter::ask_for_location()
{
    return "";
}

Array<String> ReaperWriter::get_symbols()
{
    return orderSymbols;
}

String ReaperWriter::get_symbol(int orderIndexIn)
{
    return orderSymbols[orderIndexIn];
}

String ReaperWriter::get_path()
{
    return fileOut.getFullPathName();
}

void ReaperWriter::setup_default_symbols()
{
    orderSymbols.clear();

    CharPointer_UTF8 block = CharPointer_UTF8("\xe2\x96\x88");
    String symbol;
    int orderMax = mode->getMaxStep();
    int repeats = 11;

    for (int i = 0; i < repeats; i++)
    {
        symbol = "";

        for (int o = 0; o < (repeats - i); o++)
        {
            symbol += *block;
        }

        orderSymbols.add(symbol);
    }
}

bool ReaperWriter::write()
{
    Array<int> modeOrders = mode->getOrders();
    Array<float> modeDegrees = Mode::ordersToModalDegrees(modeOrders);

    if (!fileOut.getParentDirectory().exists())
        return false;

    std::unique_ptr<FileOutputStream> outStream(fileOut.createOutputStream());

    if (!outStream->openedOk())
        return false;

    outStream->setPosition(0);
    outStream->truncate();

    // Header
    outStream->writeText("# MIDI note / CC name map\n", false, false, nullptr);

    int scaleSize = mode->getScaleSize();
    int modeSize = mode->getModeSize();

    int order;
    int index;
    float degree;
    String noteName;

    for (int i = 0; i < 128; i++)
    {
        index = 127 - i;
        order = modeOrders[index];
        degree = modeDegrees[index];
        noteName = String(degree - ((int) degree / modeSize) * modeSize);

        outStream->writeText(String(index) + " ", false, false, nullptr);

        if (order != 0)
        {
            outStream->writeText(orderSymbols[order-1], false, false, nullptr);
        }

        outStream->writeText(" " + noteName, false, false, nullptr);
        outStream->write("\n", 1);
    }

    outStream->flush();
    return true;
}


text:
/*
  ==============================================================================

    MidiProcessor.h
    Created: 21 May 2019 6:37:57pm
    Author:  Vincenzo Sicurella

  ==============================================================================
*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"
#include "../CommonFunctions.h"

#include "../Structures/MidiFilter.h"
#include "../Structures/Mode.h"
#include "../Structures/MappingHelper.h"

class SvkMidiProcessor : public MidiInputCallback,
                         public MidiKeyboardStateListener
{
public:

    SvkMidiProcessor();
    ~SvkMidiProcessor();

    ValueTree midiSettingsNode;
    ValueTree midiMapNode;
    ValueTree midiDeviceNode;

    void updateNodes();
    bool restoreSettingsNode(ValueTree midiSettingsNodeIn);
    bool restoreMappingNode(ValueTree midiMapIn);
    bool restoreDevicesNode(ValueTree midiDevicesNodeIn);

    MidiInput* getInputDevice();
    MidiOutput* getOutputDevice();

    String getInputName() const;
    String getOutputName() const;

    MidiKeyboardState* getOriginalKeyboardState();
    MidiKeyboardState* getRemappedKeyboardState();

    int getPeriodShift() const;
    int getTransposeAmt() const;
    int getMidiChannelOut() const;

    NoteMap* getInputNoteFilter();
    NoteMap* getInputNoteRemap();
    NoteMap* getOutputNoteFilter();

    NoteMap getManualNoteMap() const;

    MidiFilter* getInputMidiFilter();
    MidiFilter* getInputRemapMidiFilter();
    MidiFilter* getOutputMidiFilter();

    int getInputNote(int midiNoteIn);
    int getOutputNote(int midiNoteIn);

    int getVoiceLimit() const;

    bool isRetuning() const;

    String setMidiInput(String deviceID);
    String setMidiOutput(String deviceID);

    void setModeViewed(Mode* modeViewedIn);
    void setMode1(Mode* mode1In);
    void setMode2(Mode* mode2In);

    void setInputToFilter(bool doRemap=true);
    void setInputToRemap(bool doRemap=true);
    void setOutputToFilter(bool doFilter=true);

    void setInputFilter(Array<int> mapIn, bool updateNode=true);
    void setInputFilter(NoteMap mapIn, bool updateNode=true);
    void setInputRemap(Array<int> mapIn, bool updateNode=true);
    void setInputRemap(NoteMap mapIn, bool updateNode = true);
    void setOutputFilter(Array<int> mapIn, bool updateNode = true);
    void setOutputFilter(NoteMap mapIn, bool updateNode = true);

    /*
        How many periods to shift notes.
        Notes held will be transposed for now.
    */
    void setPeriodShift(int periodsToShift);

    /*
        Sets the value to transpose all notes by
    */
    void setTransposeAmt(int transposeAmtIn);

    /*
        The MIDI channel for mouse-based virtual keyboard interaction
    */
    void setMidiChannelOut(int virtualKeyboardMidiChannelOut);

    /*
        Updates the note number transposition cache
    */
    void updateNoteTransposition();

    //void setVoiceLimit(int maxVoicesIn);
    //void setRetuneOn(bool retuneOn);

    void setMappingHelper(MappingHelper* helperIn);

    void setInManualMappingMode(bool manualModeOn);

    void resetInputFilter(bool updateNode=true);
    void resetInputMap(bool updateNode=true);
    void resetOutputFilter(bool updateNode=true);

    void processMidi(MidiBuffer& midiMessages);
    //MidiBuffer convertToMPE(const MidiBuffer& bufferToConvert);

    void sendMsgToOutputs(const MidiMessage& msgToSend);
    void sendBufferToOutputs(const MidiBuffer& bufferToSend);
    void allNotesOff();

    void pauseMidiInput(bool setPaused=true);
    bool isMidiPaused();

    //void parameterChanged(const String& paramID, float newValue) override;

    // Listen to UI input from VirtualKeyboard
    void handleNoteOn(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity) override;
    void handleNoteOff(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity) override;
    void handleIncomingMidiMessage(MidiInput* source, const MidiMessage& msg) override;

private:

    std::unique_ptr<MidiInput> midiInput;
    std::unique_ptr<MidiOutput> midiOutput;

    String midiInputName = "";
    String midiOutputName = "";

    String inputSelected = "";
    String outputSelected = "";

    double startTime;

    MidiBuffer externalInputBuffer;
    int numInputMsgs = 0;

    MidiBuffer svkBuffer;
    int numSvkMsgs = 0;

    MidiBuffer directOutBuffer;
    int directMsgs = 0;

    MidiBuffer allNotesOffBuffer;

    Array<Array<int>> notesOnPerChannel;

    Mode* modeViewed;
    Mode* mode1;
    Mode* mode2;

    int periodShift = 0;
    bool periodShiftModeSize = false;
    int transposeAmt = 0;
    int currentNoteShift = 0;

    int midiChannelOut = 1;

    int maxNumVoices = 15;
    int pitchBendNoteMax = 48;
    int pitchBendGlobalMax = 2;

    bool mpeTuningPreserveMidiNote = true;

    std::unique_ptr<MidiFilter> midiInputFilter;
    std::unique_ptr<MidiFilter> midiInputRemap;
    std::unique_ptr<MidiFilter> midiOutputFilter;

    MappingHelper* manualMappingHelper;
    NoteMap currentRemap;
    NoteMap manualRemap;

    bool inManualMappingMode = false;

    int mpePitchbendTrackingMode = 0;
    int mpePressureTrackingMode = 0;
    int mpeTimbreTrackingMode = 0;

    bool mpeOn = false;
    bool doRetuning = false;

    bool midiInputPaused = false;
    bool isInputFiltered = false;
    bool isInputRemapped = true;
    bool isOutputFiltered = false;

    bool noteMapIsCustom = false;

    std::unique_ptr<MidiKeyboardState> originalKeyboardState; // post-filtered input
    std::unique_ptr<MidiKeyboardState> remappedKeyboardState; // remapped input
};


text:
/*
  ==============================================================================

    MidiProcessor.cpp
    Created: 21 May 2019 6:37:57pm
    Author:  Vincenzo Sicurella

  ==============================================================================
*/

#include "MidiProcessor.h"

SvkMidiProcessor::SvkMidiProcessor()
{
    midiInputFilter.reset(new MidiFilter());
    midiInputRemap.reset(new MidiFilter());
    midiOutputFilter.reset(new MidiFilter());

    midiInput = nullptr;
    midiOutput = nullptr;

    originalKeyboardState.reset(new MidiKeyboardState());
    remappedKeyboardState.reset(new MidiKeyboardState());

    notesOnPerChannel.resize(16);

    startTime = Time::getMillisecondCounterHiRes();

    // Create all-notes-off buffer
    for (int i = 0; i < 128; i++)
        for (int ch = 1; ch <= 16; ch++)
            allNotesOffBuffer.addEvent(MidiMessage::noteOff(ch, i), ch * 16 + i);
}

SvkMidiProcessor::~SvkMidiProcessor()
{

}

//==============================================================================

void SvkMidiProcessor::updateNodes()
{
    //midiSettingsNode.setProperty(IDs::mpeThru, mpeThru, nullptr);
    //midiSettingsNode.setProperty(IDs::mpeZone, 0 /*do something here*/, nullptr);
    //midiSettingsNode.setProperty(IDs::mpeLegacyOn, mpeInst->isLegacyModeEnabled(), nullptr);
    //midiSettingsNode.setProperty(IDs::pitchBendNoteMax, pitchBendNoteMax, nullptr);
    //midiSettingsNode.setProperty(IDs::pitchBendGlobalMax, mpeInst->getLegacyModePitchbendRange(), nullptr);
    //midiSettingsNode.setProperty(IDs::mpePitchTrackingMode, mpePitchbendTrackingMode, nullptr);
    //midiSettingsNode.setProperty(IDs::mpePressureTrackingMode, mpePressureTrackingMode, nullptr);
    //midiSettingsNode.setProperty(IDs::mpeTimbreTrackingMode, mpeTimbreTrackingMode, nullptr);
    //midiSettingsNode.setProperty(IDs::mpeTuningPreserveMidiNote, mpeTuningPreserveMidiNote, nullptr);

    // Unused so far / TODO
    //NoteMap* inputFilter = midiInputFilter->getNoteMap();
    //midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputFilter), nullptr);
    //if (*inputFilter != standardNoteMap)
    //    midiMapNode.appendChild(inputFilter->getAsValueTree(IDs::midiInputFilter), nullptr);

    midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputRemap), nullptr);
    if (inManualMappingMode || (manualRemap != currentRemap))
        midiMapNode.appendChild(manualRemap.getAsValueTree(IDs::midiInputRemap), nullptr);

    // Unused so far / TODO
    //NoteMap* outputFilter = midiOutputFilter->getNoteMap();
    //midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiOutputFilter), nullptr);
    //if (*outputFilter != standardNoteMap)
    //    midiMapNode.appendChild(outputFilter->getAsValueTree(IDs::midiOutputFilter), nullptr);
}

bool SvkMidiProcessor::restoreSettingsNode(ValueTree midiSettingsNodeIn)
{
    if (midiSettingsNodeIn.hasType(IDs::midiSettingsNode))
    {
        midiSettingsNode = midiSettingsNodeIn;

        periodShift = midiSettingsNode[IDs::periodShift];
        transposeAmt = midiSettingsNode[IDs::transposeAmt];
        midiChannelOut = jlimit(1, 16, (int) midiSettingsNode[IDs::keyboardMidiChannel]);
        // Note transposition is updated when new mode is applied

        return true;
    }

    return false;
}

bool SvkMidiProcessor::restoreMappingNode(ValueTree midiMapIn)
{
    bool loadedMap = false;

    if (midiMapIn.hasType(IDs::midiMapNode))
    {
        midiMapNode = midiMapIn;

        Array<int> map;

        NoteMap noteMap;

        //get_array_from_node(midiMapIn, map, IDs::midiInputFilter);
        //
        //if (map.size() > 0)
        //    setInputFilter(map);
        //else
        //    resetInputFilter();
        //
        //map.clear();

        ValueTree remapNode = midiMapNode.getChildWithName(IDs::midiInputRemap);
        noteMap = NoteMap(midiMapNode.getChildWithName(IDs::midiInputRemap));

        if (remapNode.isValid() && remapNode.getNumChildren() > 0)
        {
            loadedMap = true;
            setInputRemap(noteMap);
        }

        //map.clear();
        //get_array_from_node(midiMapIn, map, IDs::midiOutputFilter);
        //
        //if (map.size() > 0)
        //    setOutputFilter(map);
        //else
        //    resetOutputFilter();
    }

    return loadedMap;
}

bool SvkMidiProcessor::restoreDevicesNode(ValueTree midiDevicesNodeIn)
{
    // TODO: check if devices exist, give a name to imply if it doesn't
    if (JUCEApplication::isStandaloneApp && midiDevicesNodeIn.hasType(IDs::midiDeviceSettingsNode))
    {
        midiDeviceNode = midiDevicesNodeIn;

        setMidiInput(midiDeviceNode[IDs::midiInputName]);
        setMidiOutput(midiDeviceNode[IDs::midiOutputName]);

        return true;
    }

    return false;
}

//==============================================================================

MidiInput* SvkMidiProcessor::getInputDevice()
{
    return midiInput.get();
}

MidiOutput*  SvkMidiProcessor::getOutputDevice()
{
    return midiOutput.get();
}

String SvkMidiProcessor::getInputName() const
{
    return midiInputName;
}

String SvkMidiProcessor::getOutputName() const
{
    return midiOutputName;
}

MidiKeyboardState* SvkMidiProcessor::getOriginalKeyboardState()
{
    return originalKeyboardState.get();
}

MidiKeyboardState* SvkMidiProcessor::getRemappedKeyboardState()
{
    return remappedKeyboardState.get();
}

int SvkMidiProcessor::getPeriodShift() const
{
    return periodShift;
}

int SvkMidiProcessor::getMidiChannelOut() const
{
    return midiChannelOut;
}

int SvkMidiProcessor::getTransposeAmt() const
{
    return transposeAmt;
}

int SvkMidiProcessor::getVoiceLimit() const
{
    return maxNumVoices;
}

bool SvkMidiProcessor::isRetuning() const
{
    return doRetuning;
}

NoteMap* SvkMidiProcessor::getInputNoteRemap()
{
    return midiInputRemap->getNoteMap();
}

NoteMap* SvkMidiProcessor::getOutputNoteFilter()
{
    return midiOutputFilter->getNoteMap();
}

NoteMap SvkMidiProcessor::getManualNoteMap() const
{
    return manualRemap;
}

MidiFilter* SvkMidiProcessor::getInputRemapMidiFilter()
{
    return midiInputRemap.get();
}

MidiFilter* SvkMidiProcessor::getOutputMidiFilter()
{
    return midiOutputFilter.get();
}
//
//MPEInstrument* SvkMidiProcessor::getMPEInstrument()
//{
//    return mpeInst.get();
//}

int SvkMidiProcessor::getInputNote(int midiNoteIn)
{
    return midiInputRemap->getNoteRemapped(midiNoteIn);
}

int SvkMidiProcessor::getOutputNote(int midiNoteIn)
{
    return midiOutputFilter->getNoteRemapped(midiNoteIn);
}

//==============================================================================

String SvkMidiProcessor::setMidiInput(String deviceID)
{
    std::unique_ptr<MidiInput> inputToOpen = MidiInput::openDevice(deviceID, this);

    if (inputToOpen.get())
    {
        if (midiInput.get())
            midiInput->stop();

        midiInput.swap(inputToOpen);
        midiInput->start();

        midiInputName = midiInput->getName();
        inputSelected = deviceID;
        midiDeviceNode.setProperty(IDs::midiInputName, inputSelected, nullptr);
        DBG("Successfully opened input " + midiInputName);
    }
    else
    {
        inputSelected = "";
        midiInput = nullptr;
    }

    return midiInputName;
}

String SvkMidiProcessor::setMidiOutput(String deviceID)
{
    std::unique_ptr<MidiOutput> outputToOpen = MidiOutput::openDevice(deviceID);

    if (outputToOpen.get())
    {
        midiOutput.swap(outputToOpen);
        midiOutputName = midiOutput->getName();
        outputSelected = deviceID;
        midiDeviceNode.setProperty(IDs::midiOutputName, outputSelected, nullptr);
        DBG("Successfully opened output " + midiOutputName);
    }
    else
    {
        midiOutputName = "";
        outputSelected = "";
        midiOutput = nullptr;
    }

    return midiOutputName;
}

void SvkMidiProcessor::setModeViewed(Mode* modeViewedIn)
{
    modeViewed = modeViewedIn;
}

void SvkMidiProcessor::setMode1(Mode* mode1In)
{
    mode1 = mode1In;
}

void SvkMidiProcessor::setMode2(Mode* mode2In)
{
    mode2 = mode2In;
    updateNoteTransposition();
}

void SvkMidiProcessor::setPeriodShift(int periodsToShift)
{
    periodShift = periodsToShift;
    midiSettingsNode.setProperty(IDs::periodShift, periodShift, nullptr);
    updateNoteTransposition();
}

void SvkMidiProcessor::setTransposeAmt(int transposeAmtIn)
{
    transposeAmt = transposeAmtIn;
    midiSettingsNode.setProperty(IDs::transposeAmt, transposeAmt, nullptr);
    updateNoteTransposition();
}

void SvkMidiProcessor::setMidiChannelOut(int virtualKeyboardMidiChannelOut)
{
    midiChannelOut = virtualKeyboardMidiChannelOut;
    midiSettingsNode.setProperty(IDs::keyboardMidiChannel, midiChannelOut, nullptr);

    // TODO: remove notes or better yet...use a note queue for the proper note off
}

void SvkMidiProcessor::updateNoteTransposition()
{
    currentNoteShift = transposeAmt;
    if (periodShiftModeSize)
        currentNoteShift += periodShift * mode2->getModeSize();
    else
        currentNoteShift += periodShift * mode2->getScaleSize();

    // handle sustained notes | TODO: more thorough solution
    for (int i = 0; i < 16; i++)
    {
        Array<int>& channel = notesOnPerChannel.getReference(i);

        for (auto note : channel)
        {
            directOutBuffer.addEvent(MidiMessage::noteOff(i + 1, note), directMsgs++);
        }
    }
}

//void SvkMidiProcessor::setRetuneOn(bool retuneOn)
//{
//    doRetuning = retuneOn;
//}

void SvkMidiProcessor::setInputToFilter(bool doRemap)
{
    isInputFiltered = doRemap;
}

void SvkMidiProcessor::setInputToRemap(bool doRemap)
{
    isInputRemapped = doRemap;
}

void SvkMidiProcessor::setOutputToFilter(bool doFilter)
{
    isOutputFiltered = doFilter;
}

void SvkMidiProcessor::setInputFilter(Array<int> mapIn, bool updateNode)
{
    setInputFilter(NoteMap(mapIn));
}

void SvkMidiProcessor::setInputFilter(NoteMap mapIn, bool updateNode)
{
    midiInputFilter->setNoteMap(mapIn);

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputFilter), nullptr);
        add_array_to_node(midiMapNode, midiInputFilter->getNoteMap()->getValues(), IDs::midiInputFilter, "Note");
    }
}

void SvkMidiProcessor::setInputRemap(Array<int> mapIn, bool updateNode)
{
    setInputRemap(NoteMap(mapIn));
}

void SvkMidiProcessor::setInputRemap(NoteMap mapIn, bool updateNode)
{
    // not using inManualMappingMode because the processMidi call depends on it,
    // and setting that to "true" before initialization is complete causes a crash in some hosts
    if ((int)midiMapNode[IDs::mappingMode] == 3 || updateNode)
        manualRemap = mapIn;

    currentRemap = mapIn;
    midiInputRemap->setNoteMap(currentRemap);

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiInputRemap), nullptr);
        midiMapNode.appendChild(currentRemap.getAsValueTree(IDs::midiInputRemap), nullptr);
        //add_array_to_node(midiMapNode, midiInputRemap->getNoteMap()->getValues(), IDs::midiInputRemap, "Note");
    }
}

void SvkMidiProcessor::setOutputFilter(Array<int> mapIn, bool updateNode)
{
    setOutputFilter(NoteMap(mapIn));
}

void SvkMidiProcessor::setOutputFilter(NoteMap mapIn, bool updateNode)
{
    midiOutputFilter->setNoteMap(mapIn.getValues());

    if (updateNode)
    {
        midiMapNode.removeChild(midiMapNode.getChildWithName(IDs::midiOutputFilter), nullptr);
        midiMapNode.appendChild(mapIn.getAsValueTree(IDs::midiOutputFilter), nullptr);
        //add_array_to_node(midiMapNode, midiInputRemap->getNoteMap()->getValues(), IDs::midiOutputFilter, "Note");
    }
}

void SvkMidiProcessor::resetInputFilter(bool updateNode)
{
    setInputFilter(NoteMap(), updateNode);
}

void SvkMidiProcessor::resetInputMap(bool updateNode)
{
    setInputRemap(NoteMap(), updateNode);
}

void SvkMidiProcessor::resetOutputFilter(bool updateNode)
{
    setOutputFilter(NoteMap(), updateNode);
}

void SvkMidiProcessor::setMappingHelper(MappingHelper* helperIn)
{
    manualMappingHelper = helperIn;
}

void SvkMidiProcessor::setInManualMappingMode(bool manualModeOn)
{
    inManualMappingMode = manualModeOn;
}

//==============================================================================

void SvkMidiProcessor::processMidi(MidiBuffer &midiMessages)
{
    // TODO: handle note offs if period/transpose is changed before note offs

    // Combine external inputs
    midiMessages.addEvents(externalInputBuffer, 0, numInputMsgs, 0);
    externalInputBuffer.clear();
    numInputMsgs = 0;

    // Process input
    MidiBuffer combinedMessages;
    MidiMessage msg;
    int midiNote;

    for (auto msgData : midiMessages)
    {
        msg = msgData.getMessage();

        if (msg.isNoteOnOrOff())
        {
            midiNote = msg.getNoteNumber();

            if (inManualMappingMode)
            {
                if (manualMappingHelper->isWaitingForKeyInput())
                {
                    MessageManagerLock lock;
                    manualMappingHelper->mapPreparedKeyToNote(midiNote, false);
                    originalKeyboardState->processNextMidiEvent(msg);
                    continue;
                }
            }

            if (isInputFiltered)
            {
                midiNote = midiInputFilter->getNoteRemapped(midiNote);
            }

            originalKeyboardState->processNextMidiEvent(msg);

            if (isInputRemapped)
            {
                midiNote = midiInputRemap->getNoteRemapped(midiNote);
            }

            msg.setNoteNumber(midiNote);
            remappedKeyboardState->processNextMidiEvent(msg);
        }

        combinedMessages.addEvent(msg, msgData.samplePosition);
    }

    combinedMessages.addEvents(svkBuffer, 0, numSvkMsgs, 0);
    MidiBuffer finalOutputBuffer;

    // Output Filtering on all MIDI events
    for (auto msgData : combinedMessages)
    {
        if (!msgData.numBytes)
            continue;

        msg = msgData.getMessage();

        // Process transpositions
        if (msg.isNoteOnOrOff())
        {
            midiNote = msg.getNoteNumber() + currentNoteShift;

            if (isOutputFiltered)
            {
                midiNote = midiOutputFilter->getNoteRemapped(midiNote);
            }

            if (midiNote < 0 || midiNote > 127)
                continue;

            msg.setNoteNumber(midiNote);

            // TODO: better method for handling this (when transpositions change before note off)
            if (msg.isNoteOn())
                notesOnPerChannel.getReference(msg.getChannel() - 1).addIfNotAlreadyThere(msg.getNoteNumber());
            else if (msg.isNoteOff())
                notesOnPerChannel.getReference(msg.getChannel() - 1).removeAllInstancesOf(msg.getNoteNumber());
        }

       finalOutputBuffer.addEvent(msg, msgData.samplePosition);
    }

    svkBuffer.clear();
    numSvkMsgs = 0;

    finalOutputBuffer.addEvents(directOutBuffer, 0, directMsgs, 0);
    directOutBuffer.clear();
    directMsgs = 0;

    midiMessages.swapWith(finalOutputBuffer);
    sendBufferToOutputs(midiMessages);
}

void SvkMidiProcessor::sendMsgToOutputs(const MidiMessage& msg)
{
    if (midiOutput)
        midiOutput->sendMessageNow(msg);
}

void SvkMidiProcessor::sendBufferToOutputs(const MidiBuffer& bufferToSend)
{
    if (midiOutput)
    {
        midiOutput->sendBlockOfMessagesNow(bufferToSend);
    }
}

void SvkMidiProcessor::allNotesOff()
{
    auto events = MidiBuffer::Iterator(allNotesOffBuffer);
    MidiMessage msg;
    int smplNum;
    while (events.getNextEvent(msg, smplNum))
    {
        externalInputBuffer.addEvent(msg, numInputMsgs++);
    }
}

//==============================================================================

void SvkMidiProcessor::pauseMidiInput(bool setPaused)
{
    midiInputPaused = setPaused;
}

bool SvkMidiProcessor::isMidiPaused()
{
    return midiInputPaused;
}

//==============================================================================
//
//void SvkMidiProcessor::parameterChanged(const String& paramID, float newValue)
//{
//    if (paramID == IDs::mappingMode.toString())
//    {
//        isInputRemapped = newValue > 1;
//    }
//    else if (paramID == IDs::periodShift.toString())
//    {
//        periodShift = newValue;
//        updateNoteTransposition();
//    }
//    else if (paramID == IDs::periodShiftModeSize.toString())
//    {
//        periodShiftModeSize = newValue;
//    }
//    else if (paramID == IDs::transposeAmt.toString())
//    {
//        transposeAmt = newValue;
//        updateNoteTransposition();
//    }
//    else if (paramID == IDs::keyboardMidiChannel.toString())
//    {
//        //allNotesOff(midiChannelOut);
//        midiChannelOut = newValue;
//    }
//    else if (paramID == IDs::mpeOn.toString())
//    {
//        mpeOn = newValue;
//    }
//
//    DBG("Midi processor updated parameter " + paramID + " to " + String(newValue));
//}

void SvkMidiProcessor::handleNoteOn(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity)
{
    MidiMessage msg = MidiMessage::noteOn(midiChannelOut, midiNoteNumber, velocity);
    msg.setTimeStamp(Time::getMillisecondCounterHiRes() - startTime);
    svkBuffer.addEvent(msg, numSvkMsgs++);
}

void SvkMidiProcessor::handleNoteOff(MidiKeyboardState* source, int midiChannel, int midiNoteNumber, float velocity)
{
    MidiMessage msg = MidiMessage::noteOn(midiChannelOut, midiNoteNumber, velocity);
    msg.setTimeStamp(Time::getMillisecondCounterHiRes() - startTime);
    svkBuffer.addEvent(msg, numSvkMsgs++);
}

void SvkMidiProcessor::handleIncomingMidiMessage(MidiInput* source, const MidiMessage& msg)
{
    MidiMessage myMsg = MidiMessage(msg);
    externalInputBuffer.addEvent(myMsg, numInputMsgs++);
}