|  | /*
 * Carla Native Plugins
 * Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
 */
#ifndef MIDI_BASE_HPP_INCLUDED
#define MIDI_BASE_HPP_INCLUDED
#include "CarlaMIDI.h"
#include "CarlaMutex.hpp"
#include "LinkedList.hpp"
#include "CarlaJuceUtils.hpp"
#include "CarlaMathUtils.hpp"
// -----------------------------------------------------------------------
#define MAX_EVENT_DATA_SIZE          4
#define MIN_PREALLOCATED_EVENT_COUNT 100
#define MAX_PREALLOCATED_EVENT_COUNT 1000
// -----------------------------------------------------------------------
struct RawMidiEvent {
    uint64_t time;
    uint8_t  size;
    uint8_t  data[MAX_EVENT_DATA_SIZE];
};
// -----------------------------------------------------------------------
class AbstractMidiPlayer
{
public:
    virtual ~AbstractMidiPlayer() {}
    virtual void writeMidiEvent(const uint8_t port, const long double timePosFrame, const RawMidiEvent* const event) = 0;
};
// -----------------------------------------------------------------------
class MidiPattern
{
public:
    MidiPattern(AbstractMidiPlayer* const player) noexcept
        : kPlayer(player),
          fMidiPort(0),
          fStartTime(0),
          fMutex(),
          fData()
    {
        CARLA_SAFE_ASSERT(kPlayer != nullptr);
    }
    ~MidiPattern() noexcept
    {
        clear();
    }
    // -------------------------------------------------------------------
    // add data, time always counts from 0
    void addControl(const uint64_t time, const uint8_t channel, const uint8_t control, const uint8_t value)
    {
        RawMidiEvent* const ctrlEvent(new RawMidiEvent());
        ctrlEvent->time    = time;
        ctrlEvent->size    = 3;
        ctrlEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
        ctrlEvent->data[1] = control;
        ctrlEvent->data[2] = value;
        appendSorted(ctrlEvent);
    }
    void addChannelPressure(const uint64_t time, const uint8_t channel, const uint8_t pressure)
    {
        RawMidiEvent* const pressureEvent(new RawMidiEvent());
        pressureEvent->time    = time;
        pressureEvent->size    = 2;
        pressureEvent->data[0] = uint8_t(MIDI_STATUS_CHANNEL_PRESSURE | (channel & MIDI_CHANNEL_BIT));
        pressureEvent->data[1] = pressure;
        appendSorted(pressureEvent);
    }
    void addNote(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity, const uint32_t duration)
    {
        addNoteOn(time, channel, pitch, velocity);
        addNoteOff(time+duration, channel, pitch, velocity);
    }
    void addNoteOn(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity)
    {
        RawMidiEvent* const noteOnEvent(new RawMidiEvent());
        noteOnEvent->time    = time;
        noteOnEvent->size    = 3;
        noteOnEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_ON | (channel & MIDI_CHANNEL_BIT));
        noteOnEvent->data[1] = pitch;
        noteOnEvent->data[2] = velocity;
        appendSorted(noteOnEvent);
    }
    void addNoteOff(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity = 0)
    {
        RawMidiEvent* const noteOffEvent(new RawMidiEvent());
        noteOffEvent->time    = time;
        noteOffEvent->size    = 3;
        noteOffEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_OFF | (channel & MIDI_CHANNEL_BIT));
        noteOffEvent->data[1] = pitch;
        noteOffEvent->data[2] = velocity;
        appendSorted(noteOffEvent);
    }
    void addNoteAftertouch(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t pressure)
    {
        RawMidiEvent* const noteAfterEvent(new RawMidiEvent());
        noteAfterEvent->time    = time;
        noteAfterEvent->size    = 3;
        noteAfterEvent->data[0] = uint8_t(MIDI_STATUS_POLYPHONIC_AFTERTOUCH | (channel & MIDI_CHANNEL_BIT));
        noteAfterEvent->data[1] = pitch;
        noteAfterEvent->data[2] = pressure;
        appendSorted(noteAfterEvent);
    }
    void addProgram(const uint64_t time, const uint8_t channel, const uint8_t bank, const uint8_t program)
    {
        RawMidiEvent* const bankEvent(new RawMidiEvent());
        bankEvent->time    = time;
        bankEvent->size    = 3;
        bankEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & MIDI_CHANNEL_BIT));
        bankEvent->data[1] = MIDI_CONTROL_BANK_SELECT;
        bankEvent->data[2] = bank;
        RawMidiEvent* const programEvent(new RawMidiEvent());
        programEvent->time    = time;
        programEvent->size    = 2;
        programEvent->data[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (channel & MIDI_CHANNEL_BIT));
        programEvent->data[1] = program;
        appendSorted(bankEvent);
        appendSorted(programEvent);
    }
    void addPitchbend(const uint64_t time, const uint8_t channel, const uint8_t lsb, const uint8_t msb)
    {
        RawMidiEvent* const pressureEvent(new RawMidiEvent());
        pressureEvent->time    = time;
        pressureEvent->size    = 3;
        pressureEvent->data[0] = uint8_t(MIDI_STATUS_PITCH_WHEEL_CONTROL | (channel & MIDI_CHANNEL_BIT));
        pressureEvent->data[1] = lsb;
        pressureEvent->data[2] = msb;
        appendSorted(pressureEvent);
    }
    void addRaw(const uint64_t time, const uint8_t* const data, const uint8_t size)
    {
        RawMidiEvent* const rawEvent(new RawMidiEvent());
        rawEvent->time = time;
        rawEvent->size = size;
        carla_copy<uint8_t>(rawEvent->data, data, size);
        appendSorted(rawEvent);
    }
    // -------------------------------------------------------------------
    // remove data
    void removeRaw(const uint64_t time, const uint8_t* const data, const uint8_t size)
    {
        const CarlaMutexLocker sl(fMutex);
        for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin2(); it.valid(); it.next())
        {
            const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
            CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
            if (rawMidiEvent->time != time)
                continue;
            if (rawMidiEvent->size != size)
                continue;
            if (std::memcmp(rawMidiEvent->data, data, size) != 0)
                continue;
            delete rawMidiEvent;
            fData.remove(it);
            return;
        }
        carla_stderr("MidiPattern::removeRaw(" P_INT64 ", %p, %i) - unable to find event to remove", time, data, size);
    }
    // -------------------------------------------------------------------
    // clear
    void clear() noexcept
    {
        const CarlaMutexLocker sl(fMutex);
        for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin2(); it.valid(); it.next())
            delete it.getValue(nullptr);
        fData.clear();
    }
    // -------------------------------------------------------------------
    // play on time
    void play(const uint64_t timePosFrame, const uint32_t frames)
    {
        play(static_cast<long double>(timePosFrame), static_cast<double>(frames));
    }
    void play(long double timePosFrame, const double frames)
    {
        if (! fMutex.tryLock())
            return;
        if (fStartTime != 0)
            timePosFrame += static_cast<long double>(fStartTime);
        for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin2(); it.valid(); it.next())
        {
            const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
            CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
            if (timePosFrame > rawMidiEvent->time)
                continue;
            if (timePosFrame + frames <= rawMidiEvent->time)
                continue;
            kPlayer->writeMidiEvent(fMidiPort, static_cast<long double>(rawMidiEvent->time)-timePosFrame, rawMidiEvent);
        }
        fMutex.unlock();
    }
    // -------------------------------------------------------------------
    // configure
    void setMidiPort(const uint8_t port) noexcept
    {
        fMidiPort = port;
    }
    void setStartTime(const uint64_t time) noexcept
    {
        fStartTime = time;
    }
    // -------------------------------------------------------------------
    // special
    const CarlaMutex& getLock() const noexcept
    {
        return fMutex;
    }
    LinkedList<const RawMidiEvent*>::Itenerator iteneratorBegin() const noexcept
    {
        return fData.begin2();
    }
    // -------------------------------------------------------------------
    // state
    char* getState() const
    {
        static const std::size_t maxTimeSize = 20;                        // std::strlen("18446744073709551615");
        static const std::size_t maxDataSize = 4 + 4*MAX_EVENT_DATA_SIZE; // std::strlen("0xFF:127:127:127");
        static const std::size_t maxMsgSize  = maxTimeSize + 3 /* sep + size + sep */ + maxDataSize + 1 /* newline */;
        const CarlaMutexLocker sl(fMutex);
        if (fData.count() == 0)
            return nullptr;
        char* const data((char*)std::calloc(1, fData.count()*maxMsgSize+1));
        CARLA_SAFE_ASSERT_RETURN(data != nullptr, nullptr);
        char* dataWrtn = data;
        int wrtn;
        for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin2(); it.valid(); it.next())
        {
            const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));
            CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr);
            wrtn = std::snprintf(dataWrtn, maxTimeSize+6, P_UINT64 ":%u:", rawMidiEvent->time, rawMidiEvent->size);
            CARLA_SAFE_ASSERT_BREAK(wrtn > 0);
            dataWrtn += wrtn;
            wrtn = std::snprintf(dataWrtn, 5, "0x%02X", rawMidiEvent->data[0]);
            CARLA_SAFE_ASSERT_BREAK(wrtn > 0);
            dataWrtn += wrtn;
            for (uint8_t i=1, size=rawMidiEvent->size; i<size; ++i)
            {
                wrtn = std::snprintf(dataWrtn, 5, ":%03u", rawMidiEvent->data[i]);
                CARLA_SAFE_ASSERT_BREAK(wrtn > 0);
                dataWrtn += wrtn;
            }
            *dataWrtn++ = '\n';
        }
        *dataWrtn = '\0';
        return data;
    }
    void setState(const char* const data)
    {
        CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
        const size_t dataLen  = std::strlen(data);
        const char*  dataRead = data;
        const char*  needle;
        RawMidiEvent midiEvent;
        char    tmpBuf[24];
        ssize_t tmpSize;
        clear();
        const CarlaMutexLocker sl(fMutex);
        for (size_t dataPos=0; dataPos < dataLen && *dataRead != '\0';)
        {
            // get time
            needle = std::strchr(dataRead, ':');
            if (needle == nullptr)
                return;
            carla_zeroStruct(midiEvent);
            tmpSize = needle - dataRead;
            CARLA_SAFE_ASSERT_RETURN(tmpSize > 0,);
            CARLA_SAFE_ASSERT_RETURN(tmpSize < 24,);
            {
                const size_t uSize = static_cast<size_t>(tmpSize);
                std::strncpy(tmpBuf, dataRead, uSize);
                tmpBuf[tmpSize] = '\0';
                dataRead += uSize+1U;
                dataPos += uSize+1U;
            }
            const long long time = std::atoll(tmpBuf);
            CARLA_SAFE_ASSERT_RETURN(time >= 0,);
            midiEvent.time = static_cast<uint64_t>(time);
            // get size
            needle = std::strchr(dataRead, ':');
            CARLA_SAFE_ASSERT_RETURN(needle != nullptr,);
            tmpSize = needle - dataRead;
            CARLA_SAFE_ASSERT_RETURN(tmpSize > 0 && tmpSize < 24,);
            {
                const size_t uSize = static_cast<size_t>(tmpSize);
                std::strncpy(tmpBuf, dataRead, uSize);
                tmpBuf[tmpSize] = '\0';
                dataRead += uSize+1U;
                dataPos += uSize+1U;
            }
            const int midiDataSize = std::atoi(tmpBuf);
            CARLA_SAFE_ASSERT_RETURN(midiDataSize > 0 && midiDataSize <= MAX_EVENT_DATA_SIZE,);
            midiEvent.size = static_cast<uint8_t>(midiDataSize);
            // get events
            for (int i=0; i<midiDataSize; ++i)
            {
                CARLA_SAFE_ASSERT_RETURN(dataRead-data >= 4,);
                tmpSize = i==0 ? 4 : 3;
                const size_t uSize = static_cast<size_t>(tmpSize);
                std::strncpy(tmpBuf, dataRead, uSize);
                tmpBuf[tmpSize] = '\0';
                dataRead += uSize+1U;
                dataPos += uSize+1U;
                long mdata;
                if (i == 0)
                {
                    mdata = std::strtol(tmpBuf, nullptr, 16);
                    CARLA_SAFE_ASSERT_RETURN(mdata >= 0x80 && mdata <= 0xFF,);
                }
                else
                {
                    mdata = std::atoi(tmpBuf);
                    CARLA_SAFE_ASSERT_RETURN(mdata >= 0 && mdata < MAX_MIDI_VALUE,);
                }
                midiEvent.data[i] = static_cast<uint8_t>(mdata);
            }
            for (int i=midiDataSize; i<MAX_EVENT_DATA_SIZE; ++i)
                midiEvent.data[i] = 0;
            RawMidiEvent* const event(new RawMidiEvent());
            carla_copyStruct(*event, midiEvent);
            fData.append(event);
        }
    }
    // -------------------------------------------------------------------
private:
    AbstractMidiPlayer* const kPlayer;
    uint8_t  fMidiPort;
    uint64_t fStartTime;
    CarlaMutex fMutex;
    LinkedList<const RawMidiEvent*> fData;
    void appendSorted(const RawMidiEvent* const event)
    {
        const CarlaMutexLocker sl(fMutex);
        if (fData.isEmpty())
        {
            fData.append(event);
            return;
        }
        for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin2(); it.valid(); it.next())
        {
            const RawMidiEvent* const oldEvent(it.getValue(nullptr));
            CARLA_SAFE_ASSERT_CONTINUE(oldEvent != nullptr);
            if (event->time >= oldEvent->time)
                continue;
            fData.insertAt(event, it);
            return;
        }
        fData.append(event);
    }
    CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPattern)
};
// -----------------------------------------------------------------------
#endif // MIDI_BASE_HPP_INCLUDED
 |