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

 FractionalGrid.cpp
 Created: 22 Apr 2019 12:05:22pm
 Author:  Vincenzo

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

#include "Preset.h"

SvkPreset::SvkPreset()
{
    parentNode = ValueTree(IDs::presetNode);
    parentNode.setProperty(IDs::pluginPresetVersion, SVK_PRESET_VERSION, nullptr);

    thePropertiesNode = parentNode.getOrCreateChildWithName(IDs::presetProperties, nullptr);
    theModeSlots = parentNode.getOrCreateChildWithName(IDs::modeSlotsNode, nullptr);
    theModeSelectors = parentNode.getOrCreateChildWithName(IDs::modeSelectorsNode, nullptr);
    theCustomMode = parentNode.getOrCreateChildWithName(IDs::modeCustomNode, nullptr);
    theKeyboardNode = parentNode.getOrCreateChildWithName(IDs::pianoNode, nullptr);
    theMidiSettingsNode = parentNode.getOrCreateChildWithName(IDs::midiSettingsNode, nullptr);
    theMappingsNode = parentNode.getOrCreateChildWithName(IDs::midiMapNode, nullptr);
}

SvkPreset::SvkPreset(const ValueTree presetNodeIn)
    : SvkPreset()
{
    restoreFromNode(presetNodeIn, true);
}

SvkPreset::SvkPreset(const SvkPreset& presetToCopy)
:     SvkPreset(presetToCopy.parentNode.createCopy())
{
}

SvkPreset::~SvkPreset() {}

ValueTree SvkPreset::getPresetNode(bool sortModeSlots)
{
    if (sortModeSlots)
    {
        // TODO: Maybe rethink the context of this...
        // If sorted, PresetManager's ModeSlots will need to be resynched

        HashMap<int, ValueTree> slotMap;
        for (auto mode : theModeSlots)
            slotMap.set(mode[IDs::modeSlotNumber], mode);

        theModeSlots.removeAllChildren(nullptr);

        for (auto slotNum : slotNumbersInUse)
            theModeSlots.appendChild(slotMap[slotNum], nullptr);
    }

    return parentNode;
}

ValueTree SvkPreset::getKeyboardNode()
{
    return theKeyboardNode;
}

ValueTree SvkPreset::getMidiSettingsNode()
{
    return theMidiSettingsNode;
}

ValueTree SvkPreset::getMappingsNode()
{
    return theMappingsNode;
}

bool SvkPreset::restoreFromNode(ValueTree presetNodeIn, bool createCopy)
{
    if (isValidPresetNode(presetNodeIn))
    {
        if (createCopy)
            presetNodeIn = presetNodeIn.createCopy();

        parentNode = presetNodeIn;
        parentNode.setProperty(IDs::pluginPresetVersion, SVK_PRESET_VERSION, nullptr);

        thePropertiesNode = parentNode.getOrCreateChildWithName(IDs::presetProperties, nullptr);
        theModeSlots = parentNode.getOrCreateChildWithName(IDs::modeSlotsNode, nullptr);
        theModeSelectors = parentNode.getOrCreateChildWithName(IDs::modeSelectorsNode, nullptr);
        theCustomMode = parentNode.getOrCreateChildWithName(IDs::modeCustomNode, nullptr);
        theKeyboardNode = parentNode.getOrCreateChildWithName(IDs::pianoNode, nullptr);
        theMidiSettingsNode = parentNode.getOrCreateChildWithName(IDs::midiSettingsNode, nullptr);
        theMappingsNode = parentNode.getOrCreateChildWithName(IDs::midiMapNode, nullptr);

        slotNumbersInUse.clear();
        for (int i = 0; i < theModeSlots.getNumChildren(); i++)
        {
            ValueTree modeNode = theModeSlots.getChild(i);

            int slotNum = jlimit(0, MAX_MODE_SLOTS_INDEX, (int)modeNode[IDs::modeSlotNumber]);
            slotNumbersInUse.addUsingDefaultSort(slotNum);
        }

        // Ensure this has at least 2 mode selectors
        while (theModeSelectors.getNumChildren() < 2)
        {
            ValueTree selectorNode(IDs::modeSelectorsNode);
            selectorNode.setProperty(IDs::modeSlotNumber, theModeSelectors.getNumChildren(), nullptr);
            selectorNode.setProperty(IDs::modeSelectorRootNote, 60, nullptr); // TODO: default midi root note
            theModeSelectors.appendChild(selectorNode, nullptr);
        }

        // Make sure mode selectors have a midi root note
        for (auto selector : theModeSelectors)
        {
            if (!selector.hasProperty(IDs::modeSelectorRootNote))
                selector.setProperty(IDs::modeSelectorRootNote, 60, nullptr);
        }

        return true;
    }

    return false;
}

Array<int> SvkPreset::getSlotNumbersInUse() const
{
    return slotNumbersInUse;
}

int SvkPreset::getModeSelectorViewed() const
{
    return thePropertiesNode[IDs::modeSelectorViewed];
}

bool SvkPreset::isSlotNumberInUse(int modeSlotNumberIn) const
{
    return slotNumbersInUse.contains(modeSlotNumberIn);
}

int SvkPreset::getModeSlotsSize() const
{
    return theModeSlots.getNumChildren();
}

int SvkPreset::getSlotNumberBySelector(int selectorNumIn) const
{
    return theModeSelectors.getChild(selectorNumIn)[IDs::modeSlotNumber];
}

int SvkPreset::getMode1SlotNumber() const
{
    return getSlotNumberBySelector(0);
}
int SvkPreset::getMode2SlotNumber() const
{
    return getSlotNumberBySelector(1);
}

int SvkPreset::getModeViewedSlotNumber() const
{
    return getSlotNumberBySelector(getModeSelectorViewed());
}

int SvkPreset::getSlotNumberIndex(int slotNumIn)
{
    for (int i = 0; i < theModeSlots.getNumChildren(); i++)
    {
        if (slotNumIn == (int)(theModeSlots.getChild(i)[IDs::modeSlotNumber]))
            return i;
    }

    return -1;
}

int SvkPreset::getNextFreeSlotNumber()
{
    for (int i = 1; i < slotNumbersInUse.size(); i++)
    {
        if (slotNumbersInUse[i] - slotNumbersInUse[i - 1] > 1)
            return slotNumbersInUse[i - 1] + 1;
    }

    if (slotNumbersInUse.size() < MAX_MODE_SLOTS_INDEX + 1)
        return slotNumbersInUse.size();

    return -1;
}

int SvkPreset::getModeSelectorRootNote(int modeSelectorNum) const
{
    return theModeSelectors.getChild(modeSelectorNum)[IDs::modeSelectorRootNote];
}

ValueTree SvkPreset::getModeSlots()
{
    return theModeSlots;
}

ValueTree SvkPreset::getModeInSlot(int slotNum)
{
    return theModeSlots.getChild(getSlotNumberIndex(slotNum)).getChild(0);
}

ValueTree SvkPreset::getModeBySelector(int selectorNumIn)
{
    return theModeSlots.getChild(getSlotNumberIndex(getSlotNumberBySelector(selectorNumIn))).getChild(0);
}

ValueTree SvkPreset::getMode1()
{
    return getModeBySelector(0);
}

ValueTree SvkPreset::getMode2()
{
    return getModeBySelector(1);
}

ValueTree SvkPreset::getModeViewed()
{
    return getModeBySelector(thePropertiesNode[IDs::modeSelectorViewed]);
}

ValueTree SvkPreset::getCustomMode()
{
    return theCustomMode.getChild(0);
}

int SvkPreset::setModeSelectorSlotNum(int selectorNumIn, int slotNumIn)
{
    if (selectorNumIn >= 0 && slotNumIn >= 0)
    {
        ValueTree modeSelectorNode = ValueTree(IDs::modeSelectorsNode);

        if (selectorNumIn < theModeSelectors.getNumChildren())
            modeSelectorNode = theModeSelectors.getChild(selectorNumIn);

        else
            theModeSelectors.appendChild(modeSelectorNode, nullptr);

        modeSelectorNode.setProperty(IDs::modeSlotNumber, slotNumIn, nullptr);
        return slotNumIn;
    }

    return -1;
}

void SvkPreset::setMode1SlotNumber(int slotNumIn)
{
    setModeSelectorSlotNum(0, slotNumIn);
}

void SvkPreset::setMode2SlotNumber(int slotNumIn)
{
    setModeSelectorSlotNum(1, slotNumIn);
}

void SvkPreset::setModeSelectorRootNote(int modeSelectorNumIn, int rootNoteIn)
{
    ValueTree node = theModeSelectors.getChild(modeSelectorNumIn);

    if (node.isValid() && rootNoteIn >= 0 && rootNoteIn < 128)
        node.setProperty(IDs::modeSelectorRootNote, rootNoteIn, nullptr);
}

int SvkPreset::setModeSlot(ValueTree modeNodeIn, int slotNumberIn)
{
    if (Mode::isValidMode(modeNodeIn) && slotNumberIn >= 0 && slotNumberIn <= MAX_MODE_SLOTS_INDEX)
    {
        ValueTree slotNode = ValueTree(IDs::modeSlotsNode);
        int slotIndex = getSlotNumberIndex(slotNumberIn);
        if (slotIndex >= 0)
        {
            slotNode = theModeSlots.getChild(slotIndex);
            slotNode.removeAllChildren(nullptr);
        }
        else
        {
            slotIndex = theModeSlots.getNumChildren();
            theModeSlots.appendChild(slotNode, nullptr);
        }

        slotNode.setProperty(IDs::modeSlotNumber, slotNumberIn, nullptr);
        slotNode.appendChild(modeNodeIn.createCopy(), nullptr);

        if (slotNumbersInUse.indexOf(slotNumberIn) < 0)
            slotNumbersInUse.addUsingDefaultSort(slotNumberIn);

        return slotIndex;
    }

    return -1;
}

int SvkPreset::addModeSlot(ValueTree modeNodeIn)
{
    int slotIndex = getNextFreeSlotNumber();
    return setModeSlot(modeNodeIn, slotIndex);
}

ValueTree SvkPreset::setCustomMode(ValueTree customModeNodeIn, bool createCopy)
{
    if (Mode::isValidMode(customModeNodeIn))
    {
        ValueTree nodeToAdd = customModeNodeIn;

        if (createCopy)
            nodeToAdd = customModeNodeIn.createCopy();

        theCustomMode.removeAllChildren(nullptr);
        theCustomMode.appendChild(nodeToAdd, nullptr);

        return nodeToAdd;
    }

    return ValueTree();
}

int SvkPreset::removeModeSlot(int slotNumberIn)
{
    int slotIndex = getSlotNumberIndex(slotNumberIn);

    if (slotIndex >= 0)
    {
        theModeSlots.removeChild(slotIndex, nullptr);
        slotNumbersInUse.removeFirstMatchingValue(slotNumberIn);
    }

    return slotIndex;
}

void SvkPreset::resetModeSlots()
{
    theModeSelectors.removeAllChildren(nullptr);
    slotNumbersInUse.clear();

    addModeSlot(STD_TUNING_MODE_NODE);
    addModeSlot(STD_TUNING_MODE_NODE);

    setMode1SlotNumber(0);
    setMode2SlotNumber(1);
}

bool SvkPreset::isValidPresetNode(ValueTree presetNodeIn)
{
    bool isValid = presetNodeIn.hasType(IDs::presetNode);

    if (isValid)
    {
        isValid *= presetNodeIn.getChildWithName(IDs::modeSlotsNode).getNumChildren() > 0;

        // TODO: more checks?
    }

    return isValid;
}

SvkPreset SvkPreset::getDefaultPreset()
{
    SvkPreset defaultPreset;

    ValueTree propertiesNode = defaultPreset.parentNode.getOrCreateChildWithName(IDs::presetProperties, nullptr);
    propertiesNode.setProperty(IDs::modeSelectorViewed, 1, nullptr);

    defaultPreset.resetModeSlots();

    ValueTree pianoNode = defaultPreset.parentNode.getOrCreateChildWithName(IDs::pianoNode, nullptr);
    pianoNode.setProperty(IDs::keyboardKeysStyle, 1, nullptr);
    pianoNode.setProperty(IDs::pianoKeysShowNoteNumbers, 0, nullptr);
    pianoNode.setProperty(IDs::keyboardHighlightStyle, 1, nullptr);

    ValueTree mappingMode = defaultPreset.parentNode.getOrCreateChildWithName(IDs::midiMapNode, nullptr);
    mappingMode.setProperty(IDs::mappingMode, 1, nullptr);
    mappingMode.setProperty(IDs::autoMappingStyle, 1, nullptr);

    ValueTree customMode = defaultPreset.parentNode.getOrCreateChildWithName(IDs::modeCustomNode, nullptr);
    customMode.appendChild(Mode::createNode("1"), nullptr);

    return defaultPreset;
}

String SvkPreset::toString()
{
    return parentNode.toXmlString();
}


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

    MappingHelper.cpp
    Created: 21 Aug 2019 4:38:52pm
    Author:  Vincenzo Sicurella

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

#include "MappingHelper.h"

MappingHelper::MappingHelper(NoteMap noteMapIn)
    : noteMapping(noteMapIn)
{
    virtualKeyToMap = -1;
    waitingForKeyInput = false;
    allPeriods = false;
}

MappingHelper::~MappingHelper()
{

}

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

NoteMap MappingHelper::getCurrentNoteMapping() const
{
    return noteMapping;
}

void MappingHelper::mapMidiNoteToKey(int midiNoteIn, int keyNumberOut)
{
    noteMapping.setValue(midiNoteIn, keyNumberOut);
    listeners.call(&Listener::mappingEditorChanged, noteMapping);
}

void MappingHelper::resetMapping(NoteMap mappingIn, bool sendMessage)
{
    noteMapping = mappingIn;

    if (sendMessage)
        listeners.call(&Listener::mappingEditorChanged, noteMapping);
}

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

bool MappingHelper::isWaitingForKeyInput()
{
    return waitingForKeyInput;
}

int MappingHelper::getVirtualKeyToMap()
{
    if (waitingForKeyInput)
        return virtualKeyToMap;
    else
        return -1;
}

bool MappingHelper::isMappingAllPeriods()
{
    return allPeriods;
}

void MappingHelper::prepareKeyToMap(int virtualKeyClicked, bool allPeriodsIn)
{
    virtualKeyToMap = virtualKeyClicked;
    allPeriods = allPeriodsIn;
    waitingForKeyInput = true;
    listeners.call(&Listener::keyMappingStatusChanged, virtualKeyToMap, true);
}

void MappingHelper::mapPreparedKeyToNote(int midiNoteTriggered, bool allPeriods, Mode* modeFrom, Mode* modeTo)
{
    if (waitingForKeyInput)
    {
        if (allPeriods && modeFrom != nullptr && modeTo != nullptr)
        {
            Array<int> triggeredPeriods = getKeyInAllPeriods(midiNoteTriggered, modeFrom);
            Array<int> virtualKeyPeriods = getKeyInAllPeriods(virtualKeyToMap, modeTo);

            // TODO
        }

        else
        {
            mapMidiNoteToKey(midiNoteTriggered, virtualKeyToMap);
        }

        DBG("Midi Note " + String(midiNoteTriggered) + " is now mapped to " + String(noteMapping.getValue(midiNoteTriggered)));
        waitingForKeyInput = false;
        listeners.call(&Listener::keyMapConfirmed, virtualKeyToMap, midiNoteTriggered);
    }
}

void MappingHelper::cancelKeyMap()
{
    waitingForKeyInput = false;
    listeners.call(&Listener::keyMappingStatusChanged, virtualKeyToMap, false);
    virtualKeyToMap = -1;
    DBG("Key map cancelled.");
}

Array<int> MappingHelper::getKeyInAllPeriods(int keyNumber, Mode* modeToReference)
{
    Array<int> keyNumsOut;

    int scaleSize = modeToReference->getScaleSize();
    int totalNumPeriods = 128 / scaleSize;
    int keyPeriod = keyNumber / scaleSize;
    int keyNum;

    for (int i = 0; i < totalNumPeriods; i++)
    {
        keyNum = (i - keyPeriod) * scaleSize + keyNumber;

        if (keyNum >= 0 && keyNum < 128)
            keyNumsOut.add(keyNum);
    }

    return keyNumsOut;
}

repo_name: vsicurella/SuperVirtualKeyboard

mime_type: text/x-c


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

    NoteMap.h
    Created: 25 May 2019 10:34:43pm
    Author:  Vincenzo

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

#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "../Constants.h"

class NoteMap
{
public:

    NoteMap();
    NoteMap(int sizeIn, bool useIdentity, int nullValIn = -1);
    NoteMap(Array<int> valuesIn, int nullValIn = -1);
    NoteMap(ValueTree noteMappingNode);
    NoteMap(const NoteMap& mapToCopy);
    ~NoteMap() {}

    ValueTree getAsValueTree(Identifier parentNodeId, bool includeIdentities = false) const;

    void setValue(int keyNum, int valIn);
    void setValues(Array<int> valuesIn);

    int* setNullVal(int nullValIn);

    int getSize();

    int getKey(int valIn);
    int getValue(int keyNum);

    const Array<int>& getKeys() const;
    const Array<int>& getValues() const;

    int getNullVal();

    bool operator==(const NoteMap& mapToCompare) const { return values == mapToCompare.values; }
    bool operator!=(const NoteMap& mapToCompare) const { return values != mapToCompare.values; }

    String toString();

private:

    int size;

    Array<int> keys; // Midi Note In, Key Number Out
    Array<int> values; // Key Number In, Midi Note Out

    int nullVal;
};


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

    FractionalGrid.h
    Created: 24 Mar 2019 1:58:51pm
    Author:  Vincenzo

    Wrapper class for setting preset data in a ValueTree.

    The general idea is you can load up to 128 modes into a preset, and this will allow you to
    maniuplate the mode slots and keeps track of which mode slots are loaded,
    with mode selectors 0 and 1 being the main ones (0 = input, 1 = output).

    A custom mode has it's own slot and there can only be one at a time. To take advantage of
    mode slot features, the custom mode must be saved to file and loaded into a slot.

    Slot numbers and slot indicies do not need to match, and the slot indicies are only
    used internally

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

#pragma once

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

class SvkPreset
{

protected:
    ValueTree parentNode;
    ValueTree thePropertiesNode;
    ValueTree theModeSlots;
    ValueTree theModeSelectors;
    ValueTree theCustomMode;
    ValueTree theKeyboardNode;
    ValueTree theMidiSettingsNode;
    ValueTree theMappingsNode;

public:

    SvkPreset();
    SvkPreset(const ValueTree presetNodeIn);
    SvkPreset(const SvkPreset& presetToCopy);
    ~SvkPreset();

    /*
        Returns the parent node of the preset
        Will sort mode slots by slot number if called with true.
    */
    ValueTree getPresetNode(bool sortModeSlots = false);

    /*
        Returns the keyboard node of the preset
    */
    ValueTree getKeyboardNode();

    /*
        Returns the midi settings node of the preset
    */
    ValueTree getMidiSettingsNode();

    /*
        Returns the mappings node of the preset
    */
    ValueTree getMappingsNode();

    /*
        Recreate a preset from a ValueTree
    */
    bool restoreFromNode(ValueTree presetNodeIn, bool createCopy = false); // TODO: Revise default parameter

    /*
        Returns array of slot numbers in use
    */
    Array<int> getSlotNumbersInUse() const;

    /*
        Returns the mode selector that's currently in view
    */
    int getModeSelectorViewed() const;

    /*
        Returns true if given slot number is being used
    */
    bool isSlotNumberInUse(int modeSlotNumberIn) const;

    /*
        Returns the amount of modes loaded into slots
    */
    int getModeSlotsSize() const;

    /*
        Returns the slot number of the mode loaded by the given selector
    */
    int getSlotNumberBySelector(int selectorNumIn) const;

    /*
        Returns the slot number of the mode loaded by selector 0 (input keyboard)
    */
    int getMode1SlotNumber() const;

    /*
        Returns the slot number of the mode loaded by selector 1 (output keyboard)
    */
    int getMode2SlotNumber() const;

    /*
        Returns the mode slot number of the mode selector currently viewed
    */
    int getModeViewedSlotNumber() const;

    /*
        Return the root note of the given mode selector
    */
    int getModeSelectorRootNote(int modeSelectorNum) const;

    /*
        Returns the mode slots node
    */
    ValueTree getModeSlots();

    /*
        Returns the mode with the given slot number (which may be different from the index it's in)
    */
    ValueTree getModeInSlot(int slotNum);

    /*
        Returns the mode loaded in the given mode selector
    */
    ValueTree getModeBySelector(int selectorNumIn);

    /*
        Returns the mode loaded in selector 0 (input keyboard)
    */
    ValueTree getMode1();

    /*
        Returns the mode loaded in selector 1 (output keyboard)
    */
    ValueTree getMode2();

    /*
        Returns the mode currently being viewed
    */
    ValueTree getModeViewed();

    /*
        Returns the current custom mode
    */
    ValueTree getCustomMode();

    /*
        Finds the slot index of a given slot number, or returns -1 if not found
    */
    int getSlotNumberIndex(int slotNumIn);

    /*
        Sets the slot number used by the given selector, and returns the slot index
        If slot number doesn't exist, nothing will happen and this will return -1
    */
    int setModeSelectorSlotNum(int selectorNumIn, int slotNumIn);
    void setMode1SlotNumber(int slotNumIn);
    void setMode2SlotNumber(int slotNumIn);

    /*
        Sets the midi root note used by the mode selector
    */
    void setModeSelectorRootNote(int modeSelectorNumIn, int rootNoteIn);

    /*
        Loads the given mode into the given slot number, and returns the slot index
    */
    int setModeSlot(ValueTree modeNodeIn, int slotNumber);

    /*
        Puts the given mode in the smallest available free slot and returns the slot number
    */
    int addModeSlot(ValueTree modeNodeIn);

    /*
        Sets the custom mode and returns a reference to the mode node
    */
    ValueTree setCustomMode(ValueTree customModeNodeIn, bool createCopy = false);

    /*
        Empties the given mode slot and retunrs the slot index it was in
    */
    int removeModeSlot(int slotNumberIn);

    /*
        Replaces mode slots 1 & 2 with standard tuning, and removes other modes
        Also resets selectors 0 and 1 to respective mode slots
    */
    void resetModeSlots();

    /*
        Returns a readable version of the parent node
    */
    String toString();

    static bool isValidPresetNode(ValueTree presetNodeIn);

    static SvkPreset getDefaultPreset();

private:

    int getNextFreeSlotNumber();

private:

    Array<int> slotNumbersInUse;
};


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

 FractionalGrid.cpp
 Created: 22 Apr 2019 12:05:22pm
 Author:  Vincenzo

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

#include "FractionalGrid.h"

FractionalGrid::FractionalGrid(float columsIn, float rowsIn)
{
    columns = columsIn;
    rows = rowsIn;

    bounds = Rectangle<int>();
}

FractionalGrid::~FractionalGrid() {}

Rectangle<int> FractionalGrid::setBounds(int x, int y, int w, int h)
{
    bounds = Rectangle<int>(x, y, w, h);

    updateGrid();
    return bounds;
}

Rectangle<int> FractionalGrid::setBounds(Rectangle<int> rectIn)
{
    bounds = rectIn;

    updateGrid();
    return bounds;
}

Rectangle<int> FractionalGrid::getBounds()
{
    return bounds;
}

void FractionalGrid::scaleToColumnWidth(float columnWidthIn)
{
    float factor = columnWidthIn / getColumnWidth();
    bounds = bounds.withWidth((getColumnWidth() * factor + columnGap) * columns);
    bounds = bounds.withHeight((getRowHeight() * factor + rowGap) * rows);

    updateGrid();
}

void FractionalGrid::scaleToRowHeight(float rowHeightIn)
{
    float factor = rowHeightIn / getRowHeight();
    bounds = bounds.withWidth((getColumnWidth() * factor + columnGap) * columns);
    bounds = bounds.withHeight((getRowHeight() * factor + rowGap) * rows);

    updateGrid();
}

void FractionalGrid::scaleToProportion(float proportion, float rowSizeIn)
{
    bounds = bounds.withWidth((rowSizeIn * proportion + columnGap) * columns);
    bounds = bounds.withHeight((rowSizeIn + rowGap) * rows);

    updateGrid();
}

void FractionalGrid::updateGrid()
{
    colFraction = 1.0 / columns;
    rowFraction = 1.0 / rows;

    colSize = (bounds.getWidth() + columns * columnGap) / columns;
    rowSize = (bounds.getHeight() + rows * rowGap) / rows;
}

float FractionalGrid::getColumnWidth()
{
    return colSize;
}

float FractionalGrid::getRowHeight()
{
    return rowSize;
}

float FractionalGrid::getColumns()
{
    return columns;
}

float FractionalGrid::getRows()
{
    return rows;
}

void FractionalGrid::setColumnGap(int colGapIn)
{
    columnGap = colGapIn;
    updateGrid();
}

void FractionalGrid::setRowGap(int rowGapIn)
{
    rowGap = rowGapIn;
    updateGrid();
}

int FractionalGrid::getColumnGap()
{
    return columnGap;
}

int FractionalGrid::getRowGap()
{
    return rowGap;
}

Point<int> FractionalGrid::place_component(Component* item, int col, int row)
{
    if (bounds.getX() == 0 || bounds.getY() == 0)
        throw "Cannot have 0 size";

    Point<int> pt = Point<int>((int)(col * (colSize + columnGap)), (int)(row * (rowSize + rowGap)));
    item->setTopLeftPosition(pt);

    return pt;
}


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

    MappingHelper.h
    Created: 21 Aug 2019 4:38:52pm
    Author:  Vincenzo Sicurella

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

#pragma once
#include "MappingEditorBase.h"

class MappingHelper : public MappingEditor
{

public:

    MappingHelper(NoteMap mappingIn = NoteMap());
    ~MappingHelper();

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

    // MappingEditor implementation

    NoteMap getCurrentNoteMapping() const override;

    void mapMidiNoteToKey(int midiNoteIn, int keyNumberOut) override;

    void resetMapping(NoteMap mappingIn = NoteMap(), bool sendMessage = true) override;

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

    bool isWaitingForKeyInput();
    int getVirtualKeyToMap();
    bool isMappingAllPeriods();

    void prepareKeyToMap(int virtualKeyClicked, bool allPeriodsIn);
    void mapPreparedKeyToNote(int midiNoteTriggered, bool mapAllPeriods, Mode* modeFrom = nullptr, Mode* modeTo = nullptr);
    void cancelKeyMap();

    Array<int> getKeyInAllPeriods(int keyNumber, Mode* modeToReference);

private:

    NoteMap noteMapping;

    int virtualKeyToMap = -1;
    bool allPeriods = false;
    bool waitingForKeyInput = false;
};


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

    OwnedHashMap.h
    Created: 17 Nov 2019 4:50:00pm
    Author:  Vincenzo Sicurella

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

#pragma once

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

template <typename KeyType,
          typename ValueType,
          class HFunctionType = DefaultHashFunctions,
          class CriticalSectionToUse = DummyCriticalSection>

class OwnedHashMap : public OwnedArray<ValueType, CriticalSectionToUse>,
                    public HashMap<KeyType, ValueType*, HFunctionType, CriticalSectionToUse>
{

public:

    typedef OwnedArray<ValueType> TheArray;
    typedef HashMap<KeyType, ValueType*, HFunctionType, CriticalSectionToUse> TheHash;

    OwnedHashMap(int numberOfSlots=101, HFunctionType hashFunction=HFunctionType())
        : TheArray(), TheHash(numberOfSlots, hashFunction)
    {
    }
    ~OwnedHashMap()
    {
        this->TheHash::clear();
        this->TheArray::clear();
    }

    TheArray& getOwnedArray()
    {
        return dynamic_cast<TheArray&>(*this);
    }

    TheHash& getHashMap()
    {
        return dynamic_cast<TheHash&>(*this);
    }

    int getSize()
    {
        return this->TheArray::size();
    }

    ValueType* stash(KeyType keyIn, ValueType* valueIn)
    {
        ValueType* valueOut = valueIn;

        this->add(valueIn);
        this->TheHash::set(keyIn, valueOut);

        return valueOut;
    }

    ValueType* grab(const KeyType& keyIn) const
    {
        return this->TheHash::operator[](keyIn);
    }

    void toss(KeyType keyIn)
    {
        this->TheArray::removeObject(grab(keyIn));
        this->TheHash::remove(keyIn);
    }

    static void test()
    {
        // keep the "fun" in "function"

        OwnedHashMap<String, RangedAudioParameter> parameters;

        Identifier normal("NormalcyAmount");
        parameters.stash(normal.toString(), new AudioParameterFloat(normal.toString(), "Sounds good", 0.0f, 100.0f, 50.0f));

        Identifier weird("WeirdnessAmount");
        parameters.stash(weird.toString(), new AudioParameterFloat(weird.toString(), "What??", 0.0f, 100.0f, 50.0f));

        parameters.grab(weird.toString())->setValue(100.0f);
        parameters.grab(normal.toString())->setValue(0.0f);
        parameters.toss(normal.toString());
    }
};


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

    MappingEditorBase.h
    Created: 18 Oct 2020 8:32:13pm
    Author:  Vincenzo

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

#pragma once
#include "NoteMap.h"

class MappingEditor
{
public:

    MappingEditor() {};

    virtual NoteMap getCurrentNoteMapping() const = 0;

    virtual void mapMidiNoteToKey(int midiNoteIn, int keyNumberOut) = 0;

    virtual void resetMapping(NoteMap mappingIn, bool sendMessage) = 0;

public:

    class Listener
    {
    public:

        virtual void keyMappingStatusChanged(int keyNumber, bool preparedToMap) {};

        virtual void keyMapConfirmed(int keyNumber, int midiNote) {};

        virtual void mappingEditorChanged(NoteMap& newMapping) {};
    };

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

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

    void removeAllListeners() { listeners.clear(); }

protected:

    ListenerList<Listener> listeners;
};


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

    FractionalGrid.h
    Created: 24 Mar 2019 1:58:51pm
    Author:  Vincenzo

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

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

#pragma once

class FractionalGrid
{
protected:

    // Parameters
    float columns;
    float rows;

    Rectangle<int> bounds;

    // Properties
    double colFraction;
    double rowFraction;

    float colSize;
    float rowSize;

    int columnGap = 1;
    int rowGap = 1;

    // Methods
    void updateGrid();

public:

    FractionalGrid(float columsIn, float rowsIn);

    ~FractionalGrid();

    Rectangle<int> setBounds(int x, int y, int w, int h);

    Rectangle<int> setBounds(Rectangle<int> rectIn);

    Rectangle<int> getBounds();

    void scaleToColumnWidth(float columnWidthIn);

    void scaleToRowHeight(float rowHeightIn);

    void scaleToProportion(float proportion, float factor);

    float getColumns();

    float getRows();

    float getColumnWidth();

    float getRowHeight();

    int getColumnGap();

    int getRowGap();

    void setColumnGap(int colGapIn);

    void setRowGap(int rowGapIn);

    /* WARNING: mutates Component's position*/
    Point<int> place_component(Component* item, int col, int row);
};


text:
/*
==============================================================================
MidiFilter.h
Created: 14 May 2019 10:25:01pm
Author:  Vincenzo
==============================================================================
*/

#pragma once

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

class MidiFilter
{
    std::unique_ptr<NoteMap> midiNoteMapping;
    Point<int> noteRange;

public:

    MidiFilter();
    MidiFilter(Array<int> mapIn);
    MidiFilter(NoteMap mapIn);

    int getNoteRemapped(int midiNoteIn) const;
    int getNoteMidi(int remappedNoteIn) const;
    Array<int> getRemappedNotes();

    Point<int> getNoteRange() const;
    NoteMap* getNoteMap();

    int setNote(int noteIn, int noteOut);
    void setNoteRange(int lowestMidiNote, int highestMidiNote);

    NoteMap* setNoteMap(Array<int> mapToCopy);
    NoteMap* setNoteMap(NoteMap mapToCopy);

    int size() const;

    String toString();

    static Array<int> getStandardMap();
};


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

    MidiInputFilter.cpp
    Created: 14 May 2019 10:25:01pm
    Author:  Vincenzo

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

#include "MidiFilter.h"

MidiFilter::MidiFilter()
{
    noteRange = Point<int>(-1, 127);
    midiNoteMapping = std::make_unique<NoteMap>(128, true);
}

MidiFilter::MidiFilter(Array<int> mapIn)
{
    noteRange = Point<int>(-1, 127);
    setNoteMap(mapIn);
}

MidiFilter::MidiFilter(NoteMap mapIn)
{
    noteRange = Point<int>(-1, 127);
    setNoteMap(mapIn.getValues());
}

NoteMap* MidiFilter::setNoteMap(Array<int> mapToCopy)
{
    midiNoteMapping = std::make_unique<NoteMap>(mapToCopy);
    return midiNoteMapping.get();
}

NoteMap* MidiFilter::setNoteMap(NoteMap mapToCopy)
{
    midiNoteMapping = std::make_unique<NoteMap>(mapToCopy);
    return midiNoteMapping.get();
}

int MidiFilter::getNoteRemapped(int midiNoteIn) const
{
    int midiNoteOut = jlimit(noteRange.x, noteRange.y, midiNoteIn);

    if (midiNoteMapping.get())
        midiNoteOut = midiNoteMapping->getValue(midiNoteOut);

    return midiNoteOut;//jlimit(noteRange.x, noteRange.y, midiNoteOut);
}

int MidiFilter::getNoteMidi(int remappedNoteIn) const
{
    int remappedNote = jlimit(noteRange.x, noteRange.y, remappedNoteIn);

    int noteOut = -1;

    if (midiNoteMapping.get())
        noteOut = midiNoteMapping->getKey(remappedNote);

    return jlimit(noteRange.x, noteRange.y, noteOut);
}

Point<int> MidiFilter::getNoteRange()  const
{
    return noteRange;
}

int MidiFilter::setNote(int noteIn, int noteOut)
{
    if (midiNoteMapping.get())
        midiNoteMapping->setValue(noteIn, noteOut);

    return noteOut;
}

void MidiFilter::setNoteRange(int lowestMidiNote, int highestMidiNote)
{
    noteRange = Point<int>(lowestMidiNote, highestMidiNote);
}

NoteMap* MidiFilter::getNoteMap()
{
    return midiNoteMapping.get();
}

Array<int> MidiFilter::getRemappedNotes()
{
    return midiNoteMapping->getValues();
}

int MidiFilter::size() const
{
    int sizeOut = 0;
    if (midiNoteMapping.get())
        sizeOut = midiNoteMapping->getSize();

    return sizeOut;
}

String MidiFilter::toString()
{
    String out;

    for (int i = 0; i < midiNoteMapping->getSize(); i++)
    {
        out << "Midi Note In " << i << " --> " << midiNoteMapping->getValue(i) << "\t Midi Note Out " << i << " <-- " << midiNoteMapping->getKey(i) << "\n";
    }

    return out;
}

Array<int> MidiFilter::getStandardMap()
{
    Array <int> mapOut;

    mapOut.resize(128);

    for (int i = 0; i < 128; i++)
        mapOut.set(i, i);

    return mapOut;
}


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

    ModeMapper.h
    Created: 30 May 2019 8:02:42pm
    Author:  Vincenzo

    TODO: Lots of cleaning up, simplifying, and generalizing

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

#pragma once

#include "../../JuceLibraryCode/JuceHeader.h"
#include "../PluginModes.h"
#include "Mode.h"
#include "NoteMap.h"

class ModeMapper
{
    ValueTree mappingNode;

    int mappingStyle = 1;

    int mapByOrderNum1 = 0;
    int mapByOrderNum2 = 0;
    int mapByOrderOffset1 = 0;
    int mapByOrderOffset2 = 0;

    NoteMap previousOrderMap;

public:

    ModeMapper();

    ModeMapper(ValueTree modeMappingNodeIn);

    ValueTree getMappingNode();

    int getMode1OrderNum() const;

    int getMode2OrderNum() const;

    int getMode1OrderOffset() const;

    int getMode2OrderOffset() const;

    void setMappingStyle(int mapTypeIn);

    void setMapOrdersParameters(int order1, int order2, int offset1, int offset2);

    void setMode1OrderNum(int orderNumber);

    void setMode2OrderNum(int orderNumber);

    void setMode1OrderOffset(int orderOffset);

    void setMode2OrderOffset(int orderOffset);

    void setPreviousOrderNoteMap(NoteMap prevNoteMapIn);

    NoteMap map(const Mode& mode1, const Mode& mode2, NoteMap prevMap = NoteMap());

    // Returns certain type of mapping based off of passed in parameters
    NoteMap map(const Mode& mode1, const Mode& mode2, int mapStyleIn = -1, int order1=0, int order2=0, int offset1=0, int offset2=0,
                NoteMap prevMap = NoteMap());

    Array<int> getSelectedPeriodMap(const Mode& mode1, const Mode& mode2) const;

    //
    static NoteMap mapFull(const Mode& mode1, const Mode& mode2, Array<int> degreeMapIn = Array<int>());

    // Creates a mapping of mode1 onto mode2 via aligning key orders (white vs black vs colored)
    static NoteMap mapByOrder(const Mode& mode1, const Mode& mode2, int mode1Order=0, int mode2Order=0, int mode1Offset=0, int mode2Offset=0, NoteMap prevMap=NoteMap());

    //
    static NoteMap mapToMode1Period(const Mode& mapFrom, const Mode& mapTo, Array<int> degreeMapIn=Array<int>());

    //
    static NoteMap mapToMode1Scale(const Mode& mode1, const Mode& mode2, int mode2Order=0);

    // Maps standard keyboard layout (white keys) to new mode (white keys)
    static NoteMap stdMidiToMode(const Mode& modeMapped, int rootNoteStd = 60);

    // A basic one-period mode-based mapping
    static Array<int> getModeToModePeriodMap(const Mode& mode1, const Mode& mode2);

    // A basic one-period scale-based mapping, dependent on output mode's subscale being the same size as input mode's full scale
    static Array<int> getScaleToModePeriodMap(const Mode& mode1, const Mode& mode2);

    //
    static Array<int> degreeMapFullMode(const Mode& mode1, const Mode& mode2);
};


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

ModeLayout.cpp
Created: 19 Apr 2019 11:15:14pm
Author:  Vincenzo

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

#include "Mode.h"

Mode::Mode()
{
    stepsString = "1";
    family = "undefined";
    rootNote = 60;

    steps = parseIntStringToArray(stepsString);
    ordersDefault = unfoldStepsToOrders(steps);
    scaleSize = 1;
    modeSize = 1;
    name = getDescription();

    updateProperties();
    updateNode(true);
}

Mode::Mode(String stepsIn, String familyIn, int rootNoteIn, String nameIn, String infoIn)
{
    stepsString = stepsIn;
    family = familyIn;
    rootNote = rootNoteIn;
    info = infoIn;

    steps = parseIntStringToArray(stepsIn);
    ordersDefault = unfoldStepsToOrders(steps);
    scaleSize = ordersDefault.size();
    modeSize = steps.size();
    offset = getOffset() * -1;

    if (nameIn == "")
        name = getDescription();
    else
        name = nameIn;

    updateProperties();
    updateNode(true);
}

Mode::Mode(Array<int> stepsIn, String familyIn, int rootNoteIn, String nameIn, String infoIn)
{
    stepsString = intArrayToString(stepsIn);
    family = familyIn;
    rootNote = rootNoteIn;
    info = infoIn;

    steps = stepsIn;
    ordersDefault = unfoldStepsToOrders(steps);
    scaleSize = ordersDefault.size();
    modeSize = steps.size();
    offset = getOffset() * -1;

    if (nameIn == "")
        name = getDescription();
    else
        name = nameIn;

    updateProperties();
    updateNode(true);
}

Mode::Mode(ValueTree modeNodeIn, bool copyNode)
: Mode()
{
    if (isValidMode(modeNodeIn))
    {
        if (copyNode)
            modeNode = modeNodeIn.createCopy();
        else
            modeNode = modeNodeIn;

        stepsString = modeNode[IDs::stepString];
        steps = parseIntStringToArray(stepsString);
        ordersDefault = unfoldStepsToOrders(steps);

        scaleSize = modeNode[IDs::scaleSize];
        if (scaleSize == 0)
            scaleSize = ordersDefault.size();

        modeSize = modeNode[IDs::modeSize];
        if (modeSize == 0)
            modeSize = steps.size();

        family = modeNode[IDs::family];
        if (family == "")
            family = "undefined";

        info = modeNode[IDs::modeInfo];

        name = modeNode[IDs::modeName];
        if (name == "")
            name = getDescription();

        rootNote = modeNode[IDs::rootMidiNote];
        if (rootNote < 0 || rootNote > 127)
            rootNote = 60;

        updateProperties();
        updateNode();
    }
}

Mode::~Mode() {}

void Mode::updateNode(bool initializeNode)
{
    if (initializeNode)
        modeNode = ValueTree(IDs::modePresetNode);

    modeNode.setProperty(IDs::modeName, name, nullptr);
    modeNode.setProperty(IDs::scaleSize, scaleSize, nullptr);
    modeNode.setProperty(IDs::modeSize, modeSize, nullptr);
    modeNode.setProperty(IDs::stepString, stepsString, nullptr);
    modeNode.setProperty(IDs::family, family, nullptr);
    modeNode.setProperty(IDs::modeInfo, info, nullptr);
    modeNode.setProperty(IDs::factoryPreset, false, nullptr);
    modeNode.setProperty(IDs::rootMidiNote, rootNote, nullptr);
}

void Mode::restoreNode(ValueTree nodeIn, bool useNodeRoot)
{
    if (isValidMode(nodeIn))
    {
        modeNode = nodeIn;

        stepsString = modeNode[IDs::stepString];
        steps = parseIntStringToArray(stepsString);
        ordersDefault = unfoldStepsToOrders(steps);

        scaleSize = modeNode[IDs::scaleSize];
        if (scaleSize == 0)
            scaleSize = ordersDefault.size();

        modeSize = modeNode[IDs::modeSize];
        if (modeSize == 0)
            modeSize = steps.size();

        family = modeNode[IDs::family];
        if (family == "")
            family = "undefined";

        info = modeNode[IDs::modeInfo];

        name = modeNode[IDs::modeName];
        if (name == "")
            name = getDescription();

        if (useNodeRoot)
            rootNote = modeNode[IDs::rootMidiNote];

        if (rootNote < 0 || rootNote > 127)
            rootNote = 60;

        updateProperties();
        updateNode();
    }
}

bool Mode::isValidMode(ValueTree nodeIn)
{
    String steps = nodeIn.getProperty(IDs::stepString).toString();
    Array<int> stepsArray = Mode::parseIntStringToArray(steps);

    int length = stepsArray.size();
    int numDegrees = sumUpToIndex(stepsArray, length);

    return length > 0 && numDegrees <= 128;
}

ValueTree Mode::createNode(String stepsIn, String familyIn, String nameIn, String infoIn, int rootNoteIn, bool factoryPreset)
{
    ValueTree modeNodeOut = ValueTree(IDs::modePresetNode);
    Array<int> steps = parseIntStringToArray(stepsIn);
    Array<int> orders = unfoldStepsToOrders(steps);
    Array<int> mosClass = intervalAmounts(steps);

    if (rootNoteIn < 0 || rootNoteIn > 127)
        rootNoteIn = 60;

    modeNodeOut.setProperty(IDs::scaleSize, orders.size(), nullptr);
    modeNodeOut.setProperty(IDs::modeSize, steps.size(), nullptr);
    modeNodeOut.setProperty(IDs::stepString, stepsIn, nullptr);
    modeNodeOut.setProperty(IDs::family, familyIn, nullptr);
    modeNodeOut.setProperty(IDs::modeInfo, infoIn, nullptr);
    modeNodeOut.setProperty(IDs::rootMidiNote, rootNoteIn, nullptr);
    modeNodeOut.setProperty(IDs::factoryPreset, factoryPreset, nullptr);

    if (nameIn == "")
    {
        String modeName = familyIn + "[" + String(steps.size()) + "] " + String(orders.size());
        modeNodeOut.setProperty(IDs::modeName, modeName, nullptr);
    }
    else
        modeNodeOut.setProperty(IDs::modeName, nameIn, nullptr);

    return modeNodeOut;
}

ValueTree Mode::createNode(Array<int> stepsIn, String familyIn, String nameIn, String infoIn, int rootNoteIn, bool factoryPreset)
{
    ValueTree modeNodeOut = ValueTree(IDs::modePresetNode);
    Array<int> orders = unfoldStepsToOrders(stepsIn);
    Array<int> mosClass = intervalAmounts(stepsIn);
    String stepsStr = intArrayToString(stepsIn);

    if (rootNoteIn < 0 || rootNoteIn > 127)
        rootNoteIn = 60;

    modeNodeOut.setProperty(IDs::scaleSize, orders.size(), nullptr);
    modeNodeOut.setProperty(IDs::modeSize, stepsIn.size(), nullptr);
    modeNodeOut.setProperty(IDs::stepString, stepsStr, nullptr);
    modeNodeOut.setProperty(IDs::family, familyIn, nullptr);
    modeNodeOut.setProperty(IDs::modeInfo, infoIn, nullptr);
    modeNodeOut.setProperty(IDs::rootMidiNote, rootNoteIn, nullptr);
    modeNodeOut.setProperty(IDs::factoryPreset, factoryPreset, nullptr);

    if (nameIn == "")
    {
        String modeName = familyIn + "[" + String(stepsStr.length()) + "] " + String(orders.size());
        modeNodeOut.setProperty(IDs::modeName, modeName, nullptr);
    }
    else
        modeNodeOut.setProperty(IDs::modeName, nameIn, nullptr);

    return modeNodeOut;
}

void Mode::updateProperties()
{
    offset = -getOffset();
    fixedOffset = (rootNote / scaleSize) * scaleSize + rootNote % scaleSize;

    orders = repeatArray(ordersDefault, 128, offset);
    modeDegrees = ordersToModalDegrees(orders);
    scaleDegrees = generateScaleDegrees(scaleSize, offset);
    mosClass = intervalAmounts(steps);
    keyboardOrdersSizes = intervalAmounts(orders);
    stepsOfOrders = repeatIndicies(foldOrdersToSteps(orders));
}

void Mode::setFamily(String familyIn)
{
    family = familyIn;
    modeNode.setProperty(IDs::family, family, nullptr);
}

void Mode::setName(String nameIn)
{
    name = nameIn;
    modeNode.setProperty(IDs::modeName, name, nullptr);
}

void Mode::setInfo(String infoIn)
{
    info = infoIn;
    modeNode.setProperty(IDs::modeInfo, infoIn, nullptr);
}

void Mode::setRootNote(int rootNoteIn)
{
    if (rootNote != rootNoteIn)
    {
        rootNote = rootNoteIn;
        updateProperties();
    }

    modeNode.setProperty(IDs::rootMidiNote, rootNote, nullptr);
}

void Mode::rotate(int rotateAmt)
{
    int amt = totalModulus(rotateAmt, modeSize);

    Array<int> newSteps;
    for (int i = 0; i < steps.size(); i++)
        newSteps.add(steps[(amt + i) % steps.size()]);

    steps = newSteps;
    stepsString = intArrayToString(steps);

    ValueTree degreeColors = modeNode.getChildWithName(IDs::pianoKeyColorsNode).getChildWithName(IDs::pianoKeyColorsDegree);
    for (auto degreeNode : degreeColors)
    {
        int newDeg = totalModulus((int)degreeNode[IDs::pianoKeyColorsDegree] - rotateAmt, scaleSize);
        degreeNode.setProperty(IDs::pianoKeyColorsDegree, newDeg, nullptr);
    }

    updateProperties();
}

void Mode::addTag(String tagIn)
{
    tags.add(tagIn);
}

int Mode::removeTag(String tagIn)
{
    int index = tags.indexOf(tagIn);
    tags.remove(index);

    return index;
}

int Mode::getRootNote() const
{
    return rootNote;
}

int Mode::getOffset() const
{
    return totalModulus(rootNote, scaleSize);
}

int Mode::getScaleSize() const
{
    return scaleSize;
}

int Mode::getModeSize() const
{
    return modeSize;
}

Array<int> Mode::getIntervalSizeCount() const
{
    return intervalAmounts(steps);
}

Array<int> Mode::getStepsOfOrders()  const
{
    return stepsOfOrders;
}

int Mode::getNoteStep(int noteNumberIn) const
{
    return stepsOfOrders[noteNumberIn];
}

String Mode::getFamily() const
{
    return family;
}

String Mode::getName() const
{
    return name;
}

String Mode::getInfo() const
{
    return info;
}

StringArray Mode::getTags() const
{
    return tags;
}

Array<int> Mode::getSteps(int rotationIn)  const
{
    if (rotationIn == 0)
        return steps;
    else
    {
        Array<int> rotatedSteps;
        rotationIn = totalModulus(rotationIn, modeSize);

        for (int i = 0; i < modeSize; i++)
            rotatedSteps.add(steps[(i + rotationIn) % modeSize]);

        return rotatedSteps;
    }
}

String Mode::getStepsString(int rotationIn)  const
{
    if (rotationIn == 0)
        return stepsString;
    else
    {
        // lol feelin lazy about this boring function
        Array<int> stepsRotated = getSteps(rotationIn);
        return intArrayToString(stepsRotated);
    }
}

Array<int>* Mode::getKeyboardOrdersSizes()
{
    return &keyboardOrdersSizes;
}

int Mode::getKeyboardOrdersSize(int ordersIn) const
{
    int o = ordersIn % getMaxStep();
    return keyboardOrdersSizes[o];
}

Array<int> Mode::getOrdersDefault() const
{
    return ordersDefault;
}

Array<int> Mode::getOrders() const
{
    return orders;
}

int Mode::getOrder(int noteNumberIn) const
{
    return orders[noteNumberIn];
}

Array<float> Mode::getModalDegrees() const
{
    return modeDegrees;
}

Array<int> Mode::getScaleDegrees() const
{
    return scaleDegrees;
}

int Mode::getScaleDegree(int midiNoteIn) const
{
    return totalModulus(midiNoteIn - rootNote, scaleSize);
}

float Mode::getModeDegree(int midiNoteIn) const
{
    return modeDegrees[midiNoteIn % 128];
}

int Mode::getFixedDegree(int midiNoteIn) const
{
    return midiNoteIn - fixedOffset;
}

int Mode::fixedDegreeToNoteNumber(int fixedDegreeIn) const
{
    return fixedDegreeIn + fixedOffset;
}

Point<int> Mode::getPeriodsAndDegree(int midiNoteIn) const
{
    return Point<int>((midiNoteIn - rootNote) / scaleSize, (midiNoteIn - rootNote) % scaleSize);
}

int Mode::getMidiNote(int scaleDegreeIn) const
{
    int periods = scaleDegreeIn / scaleSize;
    int offsetDeg = totalModulus(scaleDegreeIn - rootNote, scaleSize);

    return periods * scaleSize + offsetDeg;
}

int Mode::getMidiNote(int periodIn, int scaleDegreeIn) const
{
    scaleDegreeIn = totalModulus(scaleDegreeIn - rootNote, scaleSize);

    return periodIn * scaleSize + scaleDegreeIn;
}

int Mode::getMaxStep() const
{
    int step = 1;

    for (int i = 0; i < steps.size(); i++)
    {
        if (steps[i] > step)
            step = steps[i];
    }

    return step;
}

Array<int> Mode::getNotesOfOrder(int order) const
{
    Array<int> notesOut;

    for (int i = 0; i < orders.size(); i++)
    {
        if (orders.getUnchecked(i) == order)
            notesOut.add(i);
    }

    return notesOut;
}

Array<int> Mode::getMOSClass() const
{
    return mosClass;
}

String Mode::getDescription()  const
{
    return family + "[" + String(modeSize) + "] " + String(scaleSize);
}

String Mode::getScaleDescription()  const
{
    return String(scaleSize) + " " + getDescription().dropLastCharacters(2);
}

String Mode::getModeDescription()  const
{
    return String(modeSize) + " " + family + " " + String(scaleSize);
}

int Mode::indexOfTag(String tagNameIn)
{
    return tags.indexOf(tagNameIn);
}

int Mode::isSimilarTo(Mode* modeToCompare) const
{
    Array<int> mode1Steps = steps;
    Array<int> mode2Steps = modeToCompare->getSteps();

    int rotations = 0;

    if (mode1Steps.size() != mode2Steps.size())
        return -1;

    while (mode1Steps != mode2Steps || rotations < mode1Steps.size())
    {
        mode2Steps.move(0, mode2Steps.size());
    }

    return (rotations % (mode1Steps.size() + 1)) - 1;
}

Array<Array<int>> Mode::getNotesOrders()
{
    Array<Array<int>> notesOut;
    notesOut.resize(getMaxStep());

    for (int i = 0; i < getMaxStep(); i++)
    {
        notesOut.getReference(i).addArray(getNotesOfOrder(i));
    }

    return notesOut;
}

Array<Array<int>> Mode::getNotesInScaleDegrees()
{
    Array<Array<int>> notesOut;
    notesOut.resize(getScaleSize());

    return notesOut;
}

Array<Array<int>> Mode::getNotesInModalDegrees()
{
    Array<Array<int>> notesOut;
    notesOut.resize(getModeSize());

    return notesOut;
}

Array<int> Mode::parseIntStringToArray(String stepsIn)
{
    Array<int> stepsOut;

    std::string theSteps = stepsIn.toStdString();

    char c;
    int step;
    int digits;
    int i = 0;

    while (i < stepsIn.length())
    {
        digits = 0;
        c = stepsIn[i];

        while ((c >= 48 && c < 58) && (i + digits) < stepsIn.length())
        {
            digits++;
            c = stepsIn[i + digits];
        }

        if (digits > 0)
        {
            step = stepsIn.substring(i, i + digits).getIntValue();
            stepsOut.add(step);
            i += digits + 1;
        }
        else
            i++;
    }

    stepsOut.minimiseStorageOverheads();
    return stepsOut;
}

Array<int> Mode::repeatIndicies(Array<int> arrayToRepeat, Array<int> repeatMask)
{
    Array<int> stepsOut;

    if (repeatMask.size() == 0)
        repeatMask = arrayToRepeat;

    for (int i = 0; i < arrayToRepeat.size(); i++)
    {
        for (int s = 0; s < repeatMask[i % repeatMask.size()]; s++)
            stepsOut.add(arrayToRepeat[i]);
    }

    stepsOut.minimiseStorageOverheads();
    return stepsOut;
}

String Mode::intArrayToString(Array<int> stepsIn)
{
    String out;

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

        if (i < stepsIn.size() - 1)
            out += ' ';
    }

    return out;
}

Array<int> Mode::unfoldStepsToOrders(Array<int> stepsIn)
{
    Array<int> ordersOut;

    for (int i = 0; i < stepsIn.size(); i++)
    {
        for (int j = 0; j < stepsIn[i]; j++)
            ordersOut.add(j);
    }

    ordersOut.minimiseStorageOverheads();
    return ordersOut;
}

// needs revision to allow for notes to start in the middle of a step
Array<int> Mode::repeatArray(Array<int> ordersIn, int sizeIn, int offsetIn)
{
    Array<int> ordersOut;
    int period = ordersIn.size();
    int off = totalModulus(offsetIn, ordersIn.size());

    int index;

    for (int i = 0; i < sizeIn; i++)
    {
        index = (off + i) % period;

        // adjust first few notes if it starts in the middle of a whole step
        if (i == 0 && ordersIn[index] != 0)
        {
            int localOffset = ordersIn[index];

            while (ordersIn[index] != 0)
            {
                ordersOut.add(ordersIn[index] - localOffset);
                i++;
                index = (off + i) % period;
            }
        }

        ordersOut.add(ordersIn[index]);
    }

    jassert(ordersOut.size() == 128);
    ordersOut.minimiseStorageOverheads();
    return ordersOut;
}

Array<int> Mode::foldOrdersToSteps(Array<int> layoutIn)
{
    Array<int> stepsOut;

    int i = 0;
    int j = 0;
    int step = 0;

    while (i < layoutIn.size())
    {
        if (layoutIn[i] == 0 || i == 0)
        {
            j = i + 1;
            step = 1;

            while (layoutIn[j] != 0 && j < layoutIn.size())
            {
                j++;
                step++;
            }
        }
        i = j;
        stepsOut.add(step);
    }
    stepsOut.minimiseStorageOverheads();
    return stepsOut;
}

Array<float> Mode::ordersToModalDegrees(Array<int> ordersIn)
{
    Array<float> degreesOut;
    float deg = -1;

    int stepSize = 0;
    int i = 0;
    while (degreesOut.size() < ordersIn.size())
    {
        stepSize++;

        if ((i + stepSize) == (ordersIn.size() - 1) ||
            ordersIn[i + stepSize] == 0)
        {
            deg++;

            for (int j = 0; j < stepSize; j++)
                degreesOut.add(deg + (j / (float) stepSize));

            i += stepSize;
            stepSize = 0;
        }
    }

    degreesOut.minimiseStorageOverheads();
    return degreesOut;
}

Array<int> Mode::generateScaleDegrees(int scaleSize, int offset)
{
    Array<int> degreesOut;

    for (int i = 0; i < scaleSize; i++)
    {
        degreesOut.add(totalModulus(i - offset, scaleSize));
    }

    degreesOut.minimiseStorageOverheads();
    return degreesOut;
}

Array<int> Mode::intervalAmounts(Array<int> stepsIn)
{
    HashMap<int, int> intervalCount;
    Array<int> stepTypes;

    for (auto step : stepsIn)
    {
        if (intervalCount.contains(step))
            intervalCount.set(step, intervalCount[step] + 1);
        else
        {
            intervalCount.set(step, 1);
            stepTypes.add(step);
        }
    }

    stepTypes.sort();

    Array<int> intervalAmountsOut;

    for (int i = stepTypes.size() - 1; i >= 0; i--)
    {
        intervalAmountsOut.add(intervalCount[stepTypes[i]]);
    }

    return intervalAmountsOut;
}

Array<int> Mode::sumArray(Array<int> stepsIn, int offsetIn, bool includePeriod)
{
    Array<int> sumsOut;
    int sum = -offsetIn;

    sumsOut.add(sum);

    for (int i = 1; i < stepsIn.size(); i++)
    {
        sum += stepsIn[i - 1];
        sumsOut.add(sum);
    }

    if (includePeriod)
        sumsOut.add(sum + stepsIn[stepsIn.size() - 1]);

    //DBGArray(sumsOut, "Sums Out");

    sumsOut.minimiseStorageOverheads();
    return sumsOut;
}


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

    ModeMapper.cpp
    Created: 30 May 2019 8:02:42pm
    Author:  Vincenzo

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

#include "ModeMapper.h"

ModeMapper::ModeMapper()
{
    mappingNode = ValueTree(IDs::midiMapNode);
    setMappingStyle(0);
    setMapOrdersParameters(0, 0, 0, 0);
}

ModeMapper::ModeMapper(ValueTree modeMappingNodeIn)
{
    mappingNode = modeMappingNodeIn;

    mappingStyle = mappingNode[IDs::autoMappingStyle];

    mapByOrderNum1 = mappingNode[IDs::mode1OrderMapping];
    mapByOrderNum2 = mappingNode[IDs::mode2OrderMapping];

    mapByOrderOffset1 = mappingNode[IDs::mode1OrderOffsetMapping];
    mapByOrderOffset2 = mappingNode[IDs::mode2OrderOffsetMapping];
}

ValueTree ModeMapper::getMappingNode()
{
    return mappingNode;
}

int ModeMapper::getMode1OrderNum() const
{
    return mapByOrderNum1;
}

int ModeMapper::getMode2OrderNum() const
{
    return mapByOrderNum2;
}

int ModeMapper::getMode1OrderOffset() const
{
    return mapByOrderOffset1;
}

int ModeMapper::getMode2OrderOffset() const
{
    return mapByOrderOffset2;
}

void ModeMapper::setMappingStyle(int mapTypeIn)
{
    mappingStyle = mapTypeIn;
    mappingNode.setProperty(IDs::autoMappingStyle, mapTypeIn, nullptr);
}

void ModeMapper::setMapOrdersParameters(int order1, int order2, int offset1, int offset2)
{
    setMode1OrderNum(order1);
    setMode2OrderNum(order2);
    setMode1OrderOffset(offset1);
    setMode2OrderOffset(offset2);
}

void ModeMapper::setMode1OrderNum(int orderNumber)
{
    mapByOrderNum1 = orderNumber;
    mappingNode.setProperty(IDs::mode1OrderMapping, mapByOrderNum1, nullptr);
}

void ModeMapper::setMode2OrderNum(int orderNumber)
{
    mapByOrderNum2 = orderNumber;
    mappingNode.setProperty(IDs::mode2OrderMapping, mapByOrderNum2, nullptr);
}

void ModeMapper::setMode1OrderOffset(int orderOffset)
{
    mapByOrderOffset1 = orderOffset;
    mappingNode.setProperty(IDs::mode1OrderOffsetMapping, mapByOrderOffset1, nullptr);
}

void ModeMapper::setMode2OrderOffset(int orderOffset)
{
    mapByOrderOffset2 = orderOffset;
    mappingNode.setProperty(IDs::mode2OrderOffsetMapping, mapByOrderOffset2, nullptr);
}

void ModeMapper::setPreviousOrderNoteMap(NoteMap prevNoteMapIn)
{
    previousOrderMap = prevNoteMapIn;
}

NoteMap ModeMapper::map(const Mode& mode1, const Mode& mode2, NoteMap prevMap)
{
    return map(mode1, mode2, mappingStyle, mapByOrderNum1, mapByOrderNum2, mapByOrderOffset1, mapByOrderOffset2, prevMap);
}

NoteMap ModeMapper::map(const Mode& mode1, const Mode& mode2, int mapStyleIn, int order1, int order2, int offset1, int offset2,
                        NoteMap prevMap)
{
    NoteMap mapOut;

    if (mapStyleIn < 0)
        mapStyleIn = mappingStyle;
    else
        setMappingStyle(mapStyleIn);

    mappingNode.setProperty(IDs::autoMappingStyle, mappingStyle, nullptr);

    switch (mappingStyle)
    {
        case ModeToScale:
            mapOut = mapToMode1Scale(mode1, mode2);
            break;

        case ModeByOrder:
            setMapOrdersParameters(order1, order2, offset1, offset2);
            previousOrderMap = prevMap;
            mapOut = mapByOrder(mode1, mode2, mapByOrderNum1, mapByOrderNum2, mapByOrderOffset1, mapByOrderOffset2, previousOrderMap);
            break;

        default:
            mapOut = mapFull(mode1, mode2);
            break;
    }

    return mapOut;
}

Array<int> ModeMapper::getSelectedPeriodMap(const Mode& mode1, const Mode& mode2) const
{
    Array<int> mapOut;
    NoteMap fullMap;

    switch (mappingStyle)
    {
    case ModeToScale:
        if (mode1.getScaleSize() == mode2.getModeSize())
            mapOut = getScaleToModePeriodMap(mode1, mode2);
        else // TODO: improve
            mapOut = mapFull(mode1, mode2).getValues();
        break;

    case ModeByOrder: // TODO: improve
        mapOut = mapFull(mode1, mode2, previousOrderMap.getValues()).getValues();
        break;

    default: // ModeToMode
        if (mode1.getModeSize() == mode2.getModeSize())
            mapOut = getModeToModePeriodMap(mode1, mode2);
        else
            mapOut = degreeMapFullMode(mode1, mode2);
        break;
    }

    return mapOut;
}

NoteMap ModeMapper::mapFull(const Mode& mode1, const Mode& mode2, Array<int> degreeMapIn)
{
    if (degreeMapIn.size() != mode1.getOrders().size())
        degreeMapIn = degreeMapFullMode(mode1, mode2);
    else
        degreeMapIn = getModeToModePeriodMap(mode1, mode2);

    NoteMap mappingOut;

    int midiNote;

    for (int m = 0; m < mappingOut.getSize(); m++)
    {
        midiNote = degreeMapIn[m];

        if (midiNote >= 0)
        {
            mappingOut.setValue(m, midiNote);
        }
        else
        {
            mappingOut.setValue(m, -1);
        }
    }

    //DBG("Root note from mode 1 is " + String(mode1.getRootNote()) +
    //    " and it produces the note " + String(mappingOut.getValue(mode1.getRootNote())) +
    //    ". Root note from mode 2 is " + String(mode2.getRootNote()));

    return mappingOut;
}

NoteMap ModeMapper::mapByOrder(const Mode& mode1, const Mode& mode2, int mode1Order, int mode2Order, int mode1Offset, int mode2Offset, NoteMap prevMap)
{
    mode1Order = jlimit(0, mode1.getMaxStep() - 1, mode1Order);
    mode2Order = jlimit(0, mode2.getMaxStep() - 1, mode2Order);

    Array<int> midiNotesFrom = mode1.getNotesOfOrder(mode1Order);
    Array<int> midiNotesTo = mode2.getNotesOfOrder(mode2Order);

    mode1Offset = jlimit(0, midiNotesFrom.size() - 1, mode1Offset);
    mode2Offset = jlimit(0, midiNotesTo.size() - 1, mode2Offset);

    int rootNoteFrom = mode1.getRootNote();
    int rootNoteTo = mode2.getRootNote();

    int rootIndexFrom = mode1.getNotesOfOrder().indexOf(rootNoteFrom);
    int rootIndexTo = mode2.getNotesOfOrder().indexOf(rootNoteTo);
    int rootIndexOffset = rootIndexTo - rootIndexFrom + mode2Offset - mode1Offset;

    int mode1Index, mode2Index;
    int noteFrom, noteTo;

    for (int m = 0; m < midiNotesFrom.size(); m++)
    {
        // might make more sense to not have a mode1Offset
        mode1Index = totalModulus(m + mode1Offset, midiNotesFrom.size());
        mode2Index = totalModulus(m + rootIndexOffset + mode2Offset, midiNotesTo.size());

        noteFrom = midiNotesFrom[mode1Index];
        noteTo = midiNotesTo[mode2Index];

        if (noteFrom > 0)
            prevMap.setValue(noteFrom, noteTo);
    }

    return prevMap;
}

NoteMap ModeMapper::mapToMode1Period(const Mode& mode1, const Mode& mode2, Array<int> degreeMapIn)
{
    // ensure degree map is the same size as Mode1 Period
    if (degreeMapIn.size() != mode1.getScaleSize())
        degreeMapIn = getModeToModePeriodMap(mode1, mode2);

    int rootNoteFrom = mode1.getRootNote();
    int rootNoteTo = mode2.getRootNote();
    int rootNoteOffset = rootNoteTo - rootNoteFrom;

    int degreeOffset = rootNoteTo - (ceil(rootNoteTo / (float) mode2.getScaleSize())) * degreeMapIn.size();
    int midiOffset = (int)(ceil(rootNoteTo / (float) mode2.getScaleSize())) * mode2.getScaleSize() - rootNoteTo + rootNoteOffset;

    //DBG("Degree Offset is " + String(degreeOffset));
    //DBG("Midi Offset is " + String(midiOffset));

    NoteMap mappingOut;

    int mapIndex;
    int periods;
    int midiNote;

    for (int m = 0; m < mappingOut.getSize(); m++)
    {
        mapIndex = m - degreeOffset;

        if (mapIndex >= 0)
        {
            periods = mapIndex / degreeMapIn.size();
            midiNote = degreeMapIn[mapIndex % degreeMapIn.size()] + periods * mode2.getScaleSize() - midiOffset;
        }
        else
            midiNote = -1;

        mappingOut.setValue(m, midiNote);

        //if (m == rootNoteFrom)
        //    DBG("Root Note From (" + String(rootNoteFrom) + ") produces note " + String(mappingOut.getValue(rootNoteFrom)) + " and comapre that to the Root note to (" + String(rootNoteTo) + ")");
    }

    return mappingOut;
}

NoteMap ModeMapper::mapToMode1Scale(const Mode& mode1, const Mode& mode2, int mode2Order)
{
    Array<int> mode2ModalNotes = mode2.getNotesOfOrder(mode2Order);

    int mode1RootIndex = mode1.getRootNote();
    int mode2RootIndex = mode2ModalNotes.indexOf(mode2.getRootNote());
    int rootIndexOffset = mode2RootIndex - mode1RootIndex;

    NoteMap mappingOut;

    int degToAdd;
    int mode2ModeIndex = 0;

    for (int m = 0; m < mode1.getOrders().size(); m++)
    {
        mode2ModeIndex = m + rootIndexOffset;

        if (mode2ModeIndex < 0)
        {
            degToAdd = mode2ModeIndex;
        }
        else if (mode2ModeIndex >= mode2ModalNotes.size())
        {
            degToAdd = 128;
        }
        else
        {
            degToAdd = mode2ModalNotes[mode2ModeIndex];
        }

        mappingOut.setValue(m, degToAdd);

        //if (m == mode1.getRootNote())
        //    DBG("Root Note From (" + String(mode1.getRootNote()) + ") produces note " + String(mappingOut.getValue(mode1.getRootNote())) + " and comapre that to the Root note to (" + String(mode2.getRootNote()) + ")");
    }

    return mappingOut;
}

NoteMap ModeMapper::stdMidiToMode(const Mode& modeMapped, int rootNoteStd)
{
    Mode meantone7_12(STD_TUNING_MODE_NODE);

    if (modeMapped.getModeSize() == meantone7_12.getModeSize())
        return mapToMode1Period(meantone7_12, modeMapped);
    else
        return mapFull(meantone7_12, modeMapped);
}

Array<int> ModeMapper::getModeToModePeriodMap(const Mode& mode1, const Mode& mode2)
{
    Array<int> degreeMapOut;

    Array<int> mode1Steps = mode1.getSteps();
    Array<int> mode2Steps = mode2.getSteps();

    int mode1ModeSize = mode1.getModeSize();
    int mode2ModeSize = mode2.getModeSize();

    int mode2ScaleIndex = 0;

    int degToAdd;
    int degSub;
    float stepFraction;

    float mode1Step;
    float mode2Step;

    for (int m1 = 0; m1 < mode2ModeSize; m1++)
    {
        mode1Step = mode1Steps[m1 % mode1ModeSize];
        mode2Step = mode2Steps[m1];

        degreeMapOut.add(mode2ScaleIndex);

        for (int d1 = 1; d1 < mode1Step; d1++)
        {
            stepFraction = d1 / mode1Step;

            // round up to the next mode degree
            degSub = mode2Step;

            // find closest degree
            for (int d2 = 1; d2 < mode2Step; d2++)
            {
                if (abs(d2 / mode2Step - stepFraction) < abs(degSub / mode2Step - stepFraction))
                    degSub = d2;
            }

            // resulting degree is the sum of previous steps, plus the next closest sub degree within the modal step
            degToAdd = mode2ScaleIndex + degSub;
            degreeMapOut.add(degToAdd);
        }

        mode2ScaleIndex += mode2Step;
    }

    //DBGArray(degreeMapOut, "Mode1 -> Mode2 Scale Degrees");
    return degreeMapOut;
}

Array<int> ModeMapper::getScaleToModePeriodMap(const Mode& mode1, const Mode& mode2)
{
    Array<int> degreeMapOut;

    Array<int> mode2Steps = mode2.getSteps();
    int mode2ScaleIndex = 0;

    for (int i = 0; i < mode2Steps.size(); i++)
    {
        degreeMapOut.add(mode2ScaleIndex);
        mode2ScaleIndex += mode2Steps[i];
    }

    //DBGArray(degreeMapOut, "Mode1 -> Mode2 Scale Degrees");
    return degreeMapOut;
}

Array<int> ModeMapper::degreeMapFullMode(const Mode& mode1, const Mode& mode2)
{
    Array<int> degreeMapOut;

    //DBG("Mode1 Root: " + String(mode1.getRootNote()) + "\tMode2Root: " + String(mode2.getRootNote()));

    Array<int> mode1Steps = Mode::foldOrdersToSteps(mode1.getOrders());
    Array<int> mode2Steps = Mode::foldOrdersToSteps(mode2.getOrders());

    Array<int> mode1MidiNotes = Mode::sumArray(mode1Steps, 0, true);
    Array<int> mode2MidiNotes = Mode::sumArray(mode2Steps, 0, true);

    int mode1RootIndex = mode1MidiNotes.indexOf(mode1.getRootNote());
    int mode2RootIndex = mode2MidiNotes.indexOf(mode2.getRootNote());
    int rootIndexOffset = mode2RootIndex - mode1RootIndex;

    //DBG("Mode1 Root Index: " + String(mode1RootIndex) + "\tMode2Root: " + String(mode2RootIndex));

    //DBGArray(mode2MidiNotes, "Mode2 Midi Notes");

    int mode2ScaleIndex = 0;

    if (rootIndexOffset > 0)
    {
        mode2ScaleIndex = sumUpToIndex(mode2Steps, rootIndexOffset);
    }

    int degToAdd, degSub;
    float stepFraction;

    float mode1Step, mode2Step;

    int mode2ModeIndex = 0;

    for (int m = 0; m < mode1Steps.size(); m++)
    {
        mode2ModeIndex = m + rootIndexOffset;

        // this is assuming that the order will always start at 0
        mode1Step = mode1Steps[m];

        if (mode2ModeIndex < 0)
        {
            mode2Step = 0;
            degToAdd = mode2ModeIndex;
        }
        else if (mode2ModeIndex >= mode2Steps.size())
        {
            mode2Step = 0;
            degToAdd = 128;
        }
        else
        {
            mode2Step = mode2Steps[mode2ModeIndex];
            degToAdd = mode2ScaleIndex;
        }

        degreeMapOut.add(degToAdd);

        for (int d1 = 1; d1 < mode1Step; d1++)
        {
            stepFraction = d1 / mode1Step;

            // round up to the next mode degree
            degSub = mode2Step;

            // find closest degree
            for (int d2 = 1; d2 < mode2Step; d2++)
            {
                if (abs(d2 / mode2Step - stepFraction) < abs(degSub / mode2Step - stepFraction))
                    degSub = d2;
            }

            // resulting degree is the sum of previous steps, plus the next closest sub degree within the modal step
            degToAdd = mode2ScaleIndex + degSub;

            if (mode2ModeIndex < 0)
                degreeMapOut.add(mode2ModeIndex);
            else
                degreeMapOut.add(degToAdd);
        }

        mode2ScaleIndex += mode2Step;
    }

    return degreeMapOut;
}


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

    ModeLayout.h
    Created: 31 Mar 2019 2:16:09pm
    Author:  Vincenzo

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

#pragma once

#include "../PluginIDs.h"
#include "../CommonFunctions.h"

/*
    A class for representing the layout of a virtualKeyboard
    based on a given mode. The mode can be applied to different scales.
*/

class Mode
{
    String name;
    int scaleSize;
    int modeSize;
    String family;
    String info;
    StringArray tags;

    int rootNote = 60;
    int offset;
    int fixedOffset;

    String stepsString;
    Array<int> steps;
    Array<int> ordersDefault;
    Array<int> mosClass;

    Array<int> orders;
    Array<int> scaleDegrees;
    Array<float> modeDegrees;

    // Keyboard Convenience
    Array<int> stepsOfOrders; // each index is the step which the note is associated with
    Array<int> keyboardOrdersSizes; // amount of keys in each key order groupings

public:

    ValueTree modeNode;

    Mode();
    Mode(String stepsIn, String familyIn="undefined", int rootNoteIn=60, String nameIn="", String infoIn="");
    Mode(Array<int> stepsIn, String familyIn="undefined", int rootNoteIn=60, String nameIn="", String infoIn="");
    Mode(ValueTree modeNodeIn, bool copyNode=false);

    ~Mode();

    void updateNode(bool initializeNode=false);
    void restoreNode(ValueTree nodeIn, bool useNodeRoot=true);

    static bool isValidMode(ValueTree nodeIn);

    static ValueTree createNode(String stepsIn, String familyIn = "undefined", String nameIn = "", String infoIn="", int rootNoteIn=60, bool factoryPreset = false);
    static ValueTree createNode(Array<int> stepsIn, String familyIn = "undefined", String nameIn = "", String infoIn="", int rootNoteIn=60, bool factoryPreset = false);

    /*
        Sets temperament family name.
    */
    void setFamily(String familyIn);

    /*
        Sets custom name of the mode.
    */
    void setName(String nameIn);

    /*
        Sets info regarding the mode.
    */
    void setInfo(String infoIn);

    /*
        Recalculates mode properties based on parameter change
     */
    void updateProperties();

    /*
        Sets the offset of the mode and updates parameters so that the
        offset replaces the current visualization of the mode
    */
    void setRootNote(int rootNoteIn);

    /*
        Rotates the scale by moving the index of the first interval
        by the rotation amount given.
    */
    void rotate(int rotateAmt);

    void addTag(String tagIn);

    int removeTag(String tagIn);

    int getRootNote() const;

    int getOffset()  const;

    int getScaleSize() const;

    int getModeSize() const;

    Array<int> getIntervalSizeCount() const;

    String getFamily() const;

    String getName() const;

    String getInfo() const;

    StringArray getTags() const;

    Array<int>* getKeyboardOrdersSizes();

    int getKeyboardOrdersSize(int ordersIn) const;

    Array<int> getStepsOfOrders() const;

    int getNoteStep(int noteNumberIn) const;

    Array<int> getSteps(int rotationIn=0) const;

    String getStepsString(int rotationIn=0) const;

    Array<int> getOrdersDefault() const;

    Array<int> getOrders() const;

    int getOrder(int noteNumberIn) const;

    Array<float> getModalDegrees() const;

    Array<int> getScaleDegrees() const;

    int getScaleDegree(int midiNoteIn) const;

    float getModeDegree(int midiNoteIn) const;

    int getFixedDegree(int midiNoteIn) const;

    int fixedDegreeToNoteNumber(int fixedDegreeIn) const;

    Point<int> getPeriodsAndDegree(int midiNoteIn) const;

    int getMidiNote(int scaleDegreeIn) const;

    int getMidiNote(int periodIn, int scaleDegreeIn) const;

    int getMaxStep() const;

    Array<int> getMOSClass() const;

    String getDescription() const;

    String getScaleDescription() const;

    String getModeDescription() const;

    Array<int> getNotesOfOrder(int order = 0) const;

    int indexOfTag(String tagNameIn);

    /*
     Returns -1 if modes are different, otherwise returns the number of rotations
     needed to make the modes equivalent.
     */
    int isSimilarTo(Mode* modeToCompare) const;

    /*
        Returns a table of note numbers (with current root) organized by key order.
    */
    Array<Array<int>> getNotesOrders();

    /*
        Returns a table of note numbers (with current root) organized by scale degree.
    */
    Array<Array<int>> getNotesInScaleDegrees();

    /*
        Returns a table of note numbers (with current root) organized by modal degree.
    */
    Array<Array<int>> getNotesInModalDegrees();

    /*
    Simply parses a string reprsenting step sizes and returns a vector
    */
    static Array<int> parseIntStringToArray(String stepsIn);

    /*
    Takes in step vector like {2, 2 , 1 , 2 , 2 , 2 , 1}
    Returns a string like "2 2 1 2 2 2 1"
    */
    static String intArrayToString(Array<int> stepsIn);

    /*
     Takes in a vector like {2, 2, 1, 2, 2, 2, 1} and a mask like {0, 0, 2, 0, 0, 0, 2}
     Returns a vector of each index repeated by the mask {2, 2, 1, 1, 2, 2, 2, 1, 1}
     If no mask supplied, then it uses it's own magnitudes.
     */
    static Array<int> repeatIndicies(Array<int> arrayToRepeat, Array<int> repeatMask=Array<int>());

    /*
    Takes in step vector like {2, 2, 1, 2, 2, 2, 1}
    Returns a vector of key orders {0, 1, 0, 1, 0, 0, 1,...}
    */
    static Array<int> unfoldStepsToOrders(Array<int> stepsIn);

    /*
    Takes in array of a scale layout of note orders (1:1) {0, 1, 0, 1 , 0, 0, 1,...}
    and returns scale step size layout "2, 2, 1, 2,..."
    */
    static Array<int> foldOrdersToSteps(Array<int> layoutIn);

    /*
        Takes in a period-size vector, a new vector size, and an offset
        Returns a vector at new size using original vector's period for repetition with the given offset
    */
    // needs revision to allow for notes to start in the middle of a step
    static Array<int> repeatArray(Array<int> ordersIn, int sizeIn, int offsetIn=0);

    /*
    Takes in array of a scale layout of note orders (1:1) {0, 1, 0, 1 , 0, 0, 1,...}
    and returns scale step size layout "0, 0.5, 1, 1.5,..."
    */
    static Array<float> ordersToModalDegrees(Array<int> ordersIn);

    /*
    Simply creates an array of scale degrees based off of scale size and offset
    */
    static Array<int> generateScaleDegrees(int scaleSize, int offset = 0);

    /*
    Takes in a vector like {2, 2, 1, 2, 2, 2, 1}
    Returns a vector of sizes large->small like {5, 2}
    */
    static Array<int> intervalAmounts(Array<int> stepsIn);

    /*
    Takes in a vector like {2, 2, 1, 2, 2, 2, 1}
    And returns a sum of the previous indicies at each index like {0, 2, 4, 5, 7, 9, 11}
    */
    static Array<int> sumArray(Array<int> stepsIn, int offsetIn = 0, bool includePeriod = false);

};


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

    NoteMap.cpp
    Created: 25 May 2019 10:34:43pm
    Author:  Vincenzo

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

#include "NoteMap.h"

NoteMap::NoteMap()
    :  NoteMap(128, true, -1)
{
}

NoteMap::NoteMap(int sizeIn, bool useIdentity, int nullValIn)
{
    if (sizeIn > 0)
        size = sizeIn;
    else
        size = 128;

    keys.clear();
    values.clear();

    nullVal = nullValIn;
    int defaultVal = nullVal;

    for (int i = 0; i < size; i++)
    {
        if (useIdentity)
            defaultVal = i;

        keys.add(defaultVal);
        values.add(defaultVal);
    }
}

NoteMap::NoteMap(Array<int> valuesIn, int nullValIn)
{
    nullVal = nullValIn;
    setValues(valuesIn);
}

NoteMap::NoteMap(ValueTree noteMappingNode)
    : NoteMap(noteMappingNode[IDs::mappingSize], true, -1)
{
    for (auto child : noteMappingNode)
    {
        if (child.hasType(IDs::noteMapNode))
        {
            int key = child[IDs::mappingMidiNoteIn];
            if (key < size)
                setValue(key, child[IDs::pianoKeyNumber]);
        }
    }
}

NoteMap::NoteMap(const NoteMap& mapToCopy)
{
    size = mapToCopy.size;
    nullVal = mapToCopy.nullVal;

    keys.clear();
    values.clear();

    for (int i = 0; i < size; i++)
    {
        keys.add(mapToCopy.keys.getUnchecked(i));
        values.add(mapToCopy.values.getUnchecked(i));
    }

}

ValueTree NoteMap::getAsValueTree(Identifier parentNodeId, bool includeIdentities) const
{
    ValueTree node(parentNodeId);
    node.setProperty(IDs::mappingSize, size, nullptr);

    for (int i = 0; i < size; i++)
    {
        if (includeIdentities || values[i] != i)
        {
            ValueTree value(IDs::noteMapNode);
            value.setProperty(IDs::mappingMidiNoteIn, i, nullptr);
            value.setProperty(IDs::pianoKeyNumber, values[i], nullptr);
            node.appendChild(value, nullptr);
        }
    }

    return node;
}

void NoteMap::setValue(int keyNum, int valIn)
{
    int oldVal = values.getUnchecked(keyNum);

    if (oldVal >= 0)
        keys.set(oldVal, nullVal);

    values.set(keyNum, valIn);

    if (valIn >= 0)
        keys.set(valIn, keyNum);
}

void NoteMap::setValues(Array<int> valuesIn)
{
    size = valuesIn.size();

    values.clear();
    keys.clear();

    values.resize(size);
    keys.resize(size);

    for (int i = 0; i < size; i++)
    {
        setValue(i, valuesIn[i]);
    }
}

int* NoteMap::setNullVal(int nullValIn)
{
    nullVal = nullValIn;
    return &nullVal;
}

int NoteMap::getSize()
{
    return size;
}

int NoteMap::getKey(int valIn)
{
    return keys.getUnchecked(valIn);
}
int NoteMap::getValue(int keyNum)
{
    return values.getUnchecked(keyNum);
}

const Array<int>& NoteMap::getKeys() const
{
    return keys;
}

const Array<int>& NoteMap::getValues() const
{
    return values;
}

int NoteMap::getNullVal()
{
    return nullVal;
}

String NoteMap::toString()
{
    String toStr = "";

    for (int i = 0; i < size; i++)
    {
        toStr += String(i) + "\t: " + String(values.getUnchecked(i)) + "\n";
    }

    return toStr;
}


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

    This file was auto-generated!

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

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

#pragma once

#include <JuceHeader.h>

#include "../PluginProcessor.h"
#include "Components/PluginControlComponent.h"

class SvkPluginEditor : public AudioProcessorEditor, public SvkPluginState::Listener, protected ValueTree::Listener
{
public:
    SvkPluginEditor (SvkAudioProcessor&);
    ~SvkPluginEditor();

    void paint (Graphics&) override;
    void resized() override;

    void presetLoaded(ValueTree presetNodeIn) override;

protected:

    void valueTreePropertyChanged(ValueTree&, const Identifier&) override;

private:
    SvkAudioProcessor& processor;
    SvkPluginState* pluginState;
    ValueTree pluginEditorNode;

    std::unique_ptr<PluginControlComponent> controlComponent;

    int defaultMinWidth = 750;
    int defaultMinHeight = 172;
    int defaultMaxWidth = 10e4;
    int defaultMaxHeight = 10e4;

    TooltipWindow tooltip;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SvkPluginEditor)
};


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

    This file was auto-generated!

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

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

#include "PluginEditor.h"
#include "../PluginProcessor.h"

using namespace VirtualKeyboard;

//==============================================================================
SvkPluginEditor::SvkPluginEditor(SvkAudioProcessor& p)
    : AudioProcessorEditor(&p), processor(p)
{
    setName("SuperVirtualKeyboard");
    setResizable(true, true);
    setBroughtToFrontOnMouseClick(true);

    setMouseClickGrabsKeyboardFocus(true);
    addMouseListener(this, true);

    pluginState = processor.getPluginState();
    pluginEditorNode = pluginState->getPluginEditorNode();

    // Intialization
    if (!pluginEditorNode.isValid())
    {
        pluginEditorNode = ValueTree(IDs::pluginEditorNode);
        pluginEditorNode.setProperty(IDs::windowBoundsW, 1000, nullptr);
        pluginEditorNode.setProperty(IDs::windowBoundsH, 210, nullptr);
        pluginEditorNode.setProperty(IDs::viewportPosition, 60, nullptr);
        pluginEditorNode.setProperty(IDs::settingsOpen, false, nullptr);
        pluginState->pluginStateNode.appendChild(pluginEditorNode, nullptr);
    }

    controlComponent.reset(new PluginControlComponent(pluginState));
    addAndMakeVisible(controlComponent.get());

    setSize(controlComponent->getWidth(), controlComponent->getHeight());
    setResizeLimits(defaultMinWidth, defaultMinHeight, defaultMaxWidth, defaultMaxHeight);

    if (pluginEditorNode[IDs::settingsOpen])
        controlComponent->showSettingsDialog();

    pluginState->addListener(this);
    pluginEditorNode.addListener(this);
}

SvkPluginEditor::~SvkPluginEditor()
{
    pluginEditorNode.removeListener(this);
    pluginState->removeListener(this);
    controlComponent = nullptr;
}

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

void SvkPluginEditor::paint(Graphics& g)
{
    g.fillAll(Colour(0xff323e44));
}

void SvkPluginEditor::resized()
{
    controlComponent->setBounds(0, 0, getWidth(), getHeight());
}

void SvkPluginEditor::presetLoaded(ValueTree presetNodeIn)
{
    if (pluginEditorNode.isValid())
        pluginEditorNode.removeListener(this);

    pluginEditorNode = pluginState->getPluginEditorNode();

    controlComponent->loadPresetNode(presetNodeIn);
    if (pluginEditorNode[IDs::settingsOpen])
        controlComponent->showSettingsDialog();

    pluginEditorNode.addListener(this);
}

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

void SvkPluginEditor::valueTreePropertyChanged(ValueTree& parent, const Identifier& property)
{
    if (parent == pluginEditorNode && property == IDs::windowBoundsH)
    {
        Viewport* viewport = controlComponent->getViewport();
        VirtualKeyboard::Keyboard* keyboard = controlComponent->getKeyboard();

        int viewportHeight = viewport->getHeight() - viewport->getScrollBarThickness();
        int keyboardWidth = keyboard->getPianoWidth(viewportHeight);

        // Not sure if this is a great solution, but should keep mins < maxes.
        if (viewportHeight > 1 && keyboardWidth > 1)
        {
            setResizeLimits(
                defaultMinWidth,
                jmax(defaultMinHeight, getHeight() - viewportHeight + 100 + 1),
                jmax(defaultMaxWidth, controlComponent->getViewport()->getX() * 2 + keyboardWidth),
                defaultMaxHeight
            );
        }
    }
}

// Would like to turn these command descriptions into tooltips...or something

//void SvkPluginEditor::getCommandInfo(CommandID commandID, ApplicationCommandInfo &result)
//{
//    switch (commandID)
//    {
//    case IDs::CommandIDs::savePresetToFile:
//        result.setInfo("Save Preset", "Save your custom layout to a file.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::saveMode:
//        result.setInfo("Save Mode", "Save the currently viewed mode.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::showSaveMenu:
//        result.setInfo("Show Saving Options", "Save current mode or whole preset.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::loadPreset:
//        result.setInfo("Load Preset", "Load a custom layout from a file.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::loadMode:
//        result.setInfo("Load Mode", "Load only the mode of a preset.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::showLoadMenu:
//        result.setInfo("Show Loading Options", "Load a mode or whole preset.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::exportReaperMap:
//        result.setInfo("Export for Reaper", "Exports the current preset to a MIDI Note Name text file for use in Reaper's piano roll.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::exportAbletonMap:
//        result.setInfo("Export for Ableton", "Exports the mode mapping to a MIDI file for to use in Ableton's piano roll for folding.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::showExportMenu:
//        result.setInfo("Show Export Options", "Shows different ways you can export a mode or preset.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::showSettingsDialog:
//        result.setInfo("Show Settings Dialog", "Change default directories", "Settings", 0);
//        break;
//    case IDs::CommandIDs::commitCustomScale:
//        result.setInfo("Commit custom scale", "Registers the entered scale steps as the current custom scale.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::setMode1:
//        result.setInfo("Set Mode 1", "Loads the mode into the Mode 1 slot.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::setMode2:
//        result.setInfo("Set Mode 2", "Loads the mode into the Mode 2 slot.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::setMode1RootNote:
//        result.setInfo("Set Mode 1 Root", "Applies the selected root note for Mode 1.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::setMode2RootNote:
//        result.setInfo("Set Mode 2 Root", "Applies the selected root note for Mode 2.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::setModeViewed:
//        result.setInfo("Set Mode Viewed", "Shows the mode slot on the keyboard.", "Keyboard", 0);
//        break;
//    case IDs::CommandIDs::showModeInfo:
//        result.setInfo("Show Mode Info", "Shows information regarding the selected Mode.", "Mode", 0);
//        break;
//    case IDs::CommandIDs::setMappingStyle:
//        result.setInfo("Mapping Style", "Choose a mapping style for remapping MIDI notes.", "Midi", 0);
//        break;
//    case IDs::CommandIDs::showMapOrderEdit:
//        result.setInfo("Edit Mappings by Order", "Choose how to map modes with the order mapping method.", "Preset", 0);
//        break;
//    case IDs::CommandIDs::applyMapping:
//        result.setInfo("Apply Mapping", "Map incoming MIDI notes to Mode 2 with the selected mapping style.", "Midi", 0);
//        break;
//    case IDs::CommandIDs::setMappingMode:
//        result.setInfo("Auto Map to Scale", "Remap Midi notes when scale changes", "Midi", 0);
//        break;
//    case IDs::CommandIDs::beginColorEditing:
//        result.setInfo("Change Keyboard Colors", "Allows you to change the default colors for the rows of keys.", "Keyboard", 0);
//        break;
    //case IDs::CommandIDs::setPeriodShift:
    //    result.setInfo("Shift by Mode 2 Period", "Shift the outgoing MIDI notes by the selected number of Mode 2 periods.", "Midi", 0);
    //    break;
    //case IDs::CommandIDs::setMidiChannelOut:
    //    result.setInfo("Set MIDI Channel Out", "Set the outgoing MIDI Notes to the selected MIDI Channel.", "Midi", 0);
    //    break;
    //case IDs::CommandIDs::showMidiNoteNumbers:
    //    result.setInfo("Show Midi Note Numbers", "Shows incoming MIDI notes on Mode 1 and outgoing MIDI Notes on Mode 2.", "Keyboard", 0);
    //    break;
    //case IDs::CommandIDs::setKeyStyle:
    //    result.setInfo("Set Key Style", "Sets the selected style for drawing overlapping degrees between mode degrees.", "Keyboard", 0);
    //    break;
    //case IDs::CommandIDs::setHighlightStyle:
    //    result.setInfo("Set Highlight Style", "Sets the selected style for drawing triggered notes.", "Keyboard", 0);
    //    break;
//    default:
//        break;
//    }
//}


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

    SvkUiPanel.h
    Created: 26 Dec 2019 3:54:15pm
    Author:  Vincenzo Sicurella

    A base class for a UI Panel in SuperVirtualKeyboard, which currently just requires
    a helper function to be defined for connecting parameters to the AudioProcessor.

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

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

//typedef AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment;
//typedef AudioProcessorValueTreeState::ComboBoxAttachment ComboBoxAttachment;
//typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment;

class SvkUiPanel : public Component
{
protected:

    //AudioProcessorValueTreeState& processorTree;

public:

    SvkUiPanel(/*AudioProcessorValueTreeState& processorTreeIn*/)
      //  : processorTree(processorTreeIn)
        {};

    virtual ~SvkUiPanel() = 0;

    //virtual void connectToProcessor() = 0;

    virtual void paint(Graphics& g) override;

    virtual void resized() override;

};


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

    VectorResources.h
    Created: 10 Nov 2019 10:50:29pm
    Author:  Vincenzo Sicurella

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

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

static void drawSaveIcon(Graphics& g, Rectangle<int> bounds, Colour backgroundFill=Colour(),
                         Colour outLineColor=Colours::lightgrey)
{
    g.setColour(backgroundFill);
    g.fillRoundedRectangle(bounds.toFloat(), 3);

    g.setColour(outLineColor);
    g.drawRoundedRectangle(bounds.toFloat(), 3, 1);

    g.setColour(Colours::whitesmoke);

    int boundsSize = bounds.getWidth();
    float imgSize = boundsSize * 7.0f / 8.0f;
    float thickness = imgSize / 32.0f;
    Rectangle<float> boundsReduced = bounds.reduced(bounds.getWidth() - imgSize).toFloat();

    Path outline = Path();

    outline.addLineSegment(Line<float>(boundsReduced.getTopLeft().toFloat(), boundsReduced.getBottomLeft().toFloat()), thickness);
    outline.addLineSegment(Line<float>(boundsReduced.getBottomLeft().toFloat(), boundsReduced.getBottomRight().toFloat()), thickness);

    float corner = imgSize / 6.0f;
    Point<float> cornerBottom = boundsReduced.getTopRight().withY(  boundsReduced.getTopRight().getY() + corner);
    Point<float> cornerLeft = boundsReduced.getTopRight().withX(  boundsReduced.getTopRight().getX() - corner);

    outline.addLineSegment(Line<float>(boundsReduced.getBottomRight().toFloat(), cornerBottom), thickness);
    outline.addLineSegment(Line<float>(cornerBottom, cornerLeft), thickness);
    outline.addLineSegment(Line<float>(cornerLeft, boundsReduced.getTopLeft().toFloat()), thickness);

    outline = outline.createPathWithRoundedCorners(2.0f);
    g.fillPath(outline);

    g.drawRect(boundsReduced.reduced(corner, 0).withTrimmedBottom(boundsReduced.getHeight() * 2.0f/3.0f));

    float lineWidth = bounds.getWidth() * 1.0f / 8.0f;
    float lineX1 = boundsReduced.getTopLeft().getX() + lineWidth;
    float lineX2 = boundsReduced.getTopRight().getX() - lineWidth;
    float height = boundsReduced.getHeight();

    g.drawHorizontalLine(boundsReduced.getTopLeft().getY() + height * 0.6f, lineX1, lineX2);
    g.drawHorizontalLine(boundsReduced.getTopLeft().getY() + height * 0.7f, lineX1, lineX2);
    g.drawHorizontalLine(boundsReduced.getTopLeft().getY() + height * 0.8f, lineX1, lineX2);
}

static void drawLoadIcon(Graphics& g, Rectangle<int> bounds, Colour backgroundFill=Colour(),
                         Colour outLineColor=Colours::whitesmoke)
{
    g.setColour(backgroundFill);
    g.fillRoundedRectangle(bounds.toFloat(), 3);

    g.setColour(outLineColor);
    g.drawRoundedRectangle(bounds.toFloat(), 3, 1);

    g.setColour(Colours::whitesmoke);

    int boundsSize = bounds.getWidth();
    float imgSize = boundsSize * 7.0f / 8.0f;
    float thickness = imgSize / 32.0f;
    float tabHeight = imgSize / 9.0f;
    float tabWidth = imgSize / 3.0f;
    Rectangle<float> boundsReduced = bounds.reduced(bounds.getWidth() - imgSize).toFloat();
    boundsReduced.reduce(0, imgSize/20);

    Path outline = Path();
    outline.addLineSegment(Line<float>(boundsReduced.getTopLeft(), boundsReduced.getBottomLeft()), thickness);
    outline.addLineSegment(Line<float>(boundsReduced.getBottomLeft(), boundsReduced.getBottomRight()), thickness);

    Point<float> tabCornerBottomRight = boundsReduced.getTopRight().translated(0, tabHeight);
    Point<float> tabCornerBottomLeft = boundsReduced.getTopLeft().translated(tabWidth*1.15, tabHeight);
    Point<float> tabCornerTop = boundsReduced.getTopLeft().translated(tabWidth, 0);

    outline.addLineSegment(Line<float>(boundsReduced.getBottomRight(), tabCornerBottomRight), thickness);
    outline.addLineSegment(Line<float>(tabCornerBottomRight, tabCornerBottomLeft), thickness);
    outline.addLineSegment(Line<float>(tabCornerBottomLeft, tabCornerTop), thickness);
    outline.addLineSegment(Line<float>(tabCornerTop, boundsReduced.getTopLeft()), thickness);

    outline = outline.createPathWithRoundedCorners(2.0f);
    g.fillPath(outline);
}

static void drawSettingsIcon(Graphics& g, Rectangle<int> bounds, Colour backgroundFill=Colour(),
                             Colour outLineColor=Colours::whitesmoke)
{
    g.setColour(backgroundFill);
    g.fillRoundedRectangle(bounds.toFloat(), 3);

    g.setColour(outLineColor);
    g.drawRoundedRectangle(bounds.toFloat(), 3, 1);

    g.setColour(Colours::whitesmoke);

    int boundsSize = bounds.getWidth();
    float imgSize = boundsSize * 7.0f / 8.0f;
    float thickness = imgSize / 36.0f;

    Rectangle<float> boundsReduced = bounds.reduced(bounds.getWidth() - imgSize).toFloat();

    Path gear = Path();

    int n = 7;
    float radiusBody = imgSize * 3.0f / 11.0f;
    float radiusSpoke = radiusBody * 12.0f/8.0f;

    Point<float> bodyRight;
    Point<float> bodyLeft;
    Point<float> spokeRight;
    Point<float> spokeLeft;
    Point<float> nextSpoke;
    Point<float> center = bounds.getCentre().toFloat();

    float r = 0;
    float rStep = 6.28318530718f / n;
    float spokeDif = rStep * 4.0f/19.0f;
    float bodDif = spokeDif * 5.0f / 3.0f;
    float rStepNext;

    for (int i = 0; i < n; i++)
    {
        r = rStep * i;

        bodyRight = Point<float>(cosf(r-bodDif) * radiusBody + center.getX(), sinf(r-bodDif) * radiusBody + center.getY());
        spokeRight = Point<float>(cosf(r-spokeDif) * radiusSpoke + center.getX(), sinf(r-spokeDif) * radiusSpoke + center.getY());

        bodyLeft = Point<float>(cosf(r+bodDif) * radiusBody + center.getX(), sinf(r+bodDif) * radiusBody + center.getY());
        spokeLeft = Point<float>(cosf(r+spokeDif) * radiusSpoke + center.getX(), sinf(r+spokeDif) * radiusSpoke + center.getY());

        rStepNext = rStep * (i + 1);
        nextSpoke = Point<float>(cosf(rStepNext-bodDif) * radiusBody + center.getX(), sinf(rStepNext-bodDif) * radiusBody + center.getY());

        gear.addLineSegment(Line<float>(bodyRight, spokeRight), thickness);
        gear.addLineSegment(Line<float>(spokeRight, spokeLeft), thickness);
        gear.addLineSegment(Line<float>(spokeLeft, bodyLeft), thickness);
        gear.addLineSegment(Line<float>(bodyLeft, nextSpoke), thickness);
    }

    gear = gear.createPathWithRoundedCorners(thickness);
    g.fillPath(gear);
    float cR = radiusBody / 2.0f;

    g.drawEllipse(center.getX() - cR, center.getY() - cR, cR * 2, cR * 2, thickness);
}