Working with MPE of a MidiClip

I am building a piano roll with MPE support and I’m struggling to figure out how to get and set additional the per note data. I’m guessing it has something to do with MidiExpression but I’m unclear on how it is intended to be used.

Does anyone have any information about this?

You use MidiExpression::createAndAddExpressionToNote.

Basically you have your te::MidiNote state and pass it to createAndAddExpressionToNote along with the details of the expression you want to set.
E.g.

MidiExpression::createAndAddExpressionToNote (note.state, IDs::PITCHBEND, 0.5_bp, 48.0f, undoManager);

Does that help?

I think that helps to clarify how to set the values, but how do I read the MPE data from a recorded note. At first I thought I could poll the CC events for each channel, but it looks like a te::MidiClip is single channel only.

We use this in our Waveform editor:

//==============================================================================
struct MidiExpressionListSorter
{
    static int compareElements (const MidiExpression* first, const MidiExpression* second) noexcept
    {
        auto p1 = first->getBeatPosition();
        auto p2 = second->getBeatPosition();

        return p1 == p2 ? 0 : (p1 > p2 ? 1 : -1);
    }
};

struct MidiExpressionList : public ValueTreeObjectList<MidiExpression>
{
    MidiExpressionList (const Identifier& type_, const juce::ValueTree& v)
      : ValueTreeObjectList (v), type (type_)
    {
        rebuildObjects();
    }

    ~MidiExpressionList()
    {
        ValueTreeObjectList::freeObjects();
    }

    MidiExpression* getEventFor (const juce::ValueTree& v)
    {
        for (auto m : objects)
            if (m->state == v)
                return m;

        return nullptr;
    }

    bool isSuitableType (const juce::ValueTree& v) const override       { return v.hasType (type); }
    MidiExpression* createNewObject (const juce::ValueTree& v) override { return new MidiExpression (v); }
    void deleteObject (MidiExpression* m) override                      { delete m; }
    void newObjectAdded (MidiExpression*) override                      { triggerSort(); }
    void objectRemoved (MidiExpression*) override                       { triggerSort(); }
    void objectOrderChanged() override                                  { triggerSort(); }

    void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier& i) override
    {
        if (i == IDs::b)
            triggerSort();
    }

    void triggerSort()
    {
        const juce::ScopedLock sl (lock);
        needsSorting = true;
    }

    const juce::Array<MidiExpression*>& getSortedList()
    {
        TRACKTION_ASSERT_MESSAGE_THREAD

        const juce::ScopedLock sl (lock);

        if (needsSorting)
        {
            needsSorting = false;

            sortedEvents = objects;

            MidiExpressionListSorter sorter;
            sortedEvents.sort (sorter);
        }

        return sortedEvents;
    }

    const Identifier type;
    bool needsSorting = true;
    juce::Array<MidiExpression*> sortedEvents;
    juce::CriticalSection lock;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiExpressionList)
};

Then you can get the values using getSortedList()[index]->getValue()

That kind of thing.

2 Likes