|
@@ -1,6 +1,6 @@ |
|
|
/* |
|
|
/* |
|
|
* Carla Native Plugins |
|
|
* Carla Native Plugins |
|
|
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> |
|
|
|
|
|
|
|
|
* Copyright (C) 2012-2015 Filipe Coelho <falktx@falktx.com> |
|
|
* |
|
|
* |
|
|
* This program is free software; you can redistribute it and/or |
|
|
* This program is free software; you can redistribute it and/or |
|
|
* modify it under the terms of the GNU General Public License as |
|
|
* modify it under the terms of the GNU General Public License as |
|
@@ -25,10 +25,14 @@ |
|
|
#include "CarlaJuceUtils.hpp" |
|
|
#include "CarlaJuceUtils.hpp" |
|
|
#include "CarlaMathUtils.hpp" |
|
|
#include "CarlaMathUtils.hpp" |
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
#define MAX_EVENT_DATA_SIZE 4 |
|
|
#define MAX_EVENT_DATA_SIZE 4 |
|
|
#define MIN_PREALLOCATED_EVENT_COUNT 100 |
|
|
#define MIN_PREALLOCATED_EVENT_COUNT 100 |
|
|
#define MAX_PREALLOCATED_EVENT_COUNT 1000 |
|
|
#define MAX_PREALLOCATED_EVENT_COUNT 1000 |
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
struct RawMidiEvent { |
|
|
struct RawMidiEvent { |
|
|
uint64_t time; |
|
|
uint64_t time; |
|
|
uint8_t size; |
|
|
uint8_t size; |
|
@@ -42,53 +46,60 @@ struct RawMidiEvent { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
class AbstractMidiPlayer |
|
|
class AbstractMidiPlayer |
|
|
{ |
|
|
{ |
|
|
public: |
|
|
public: |
|
|
virtual ~AbstractMidiPlayer() {} |
|
|
virtual ~AbstractMidiPlayer() {} |
|
|
virtual void writeMidiEvent(const uint64_t timePosFrame, const RawMidiEvent* const event) = 0; |
|
|
|
|
|
|
|
|
virtual void writeMidiEvent(const uint8_t port, const uint64_t timePosFrame, const RawMidiEvent* const event) = 0; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
class MidiPattern |
|
|
class MidiPattern |
|
|
{ |
|
|
{ |
|
|
public: |
|
|
public: |
|
|
MidiPattern(AbstractMidiPlayer* const player) |
|
|
|
|
|
|
|
|
MidiPattern(AbstractMidiPlayer* const player) noexcept |
|
|
: kPlayer(player), |
|
|
: kPlayer(player), |
|
|
|
|
|
fMidiPort(0), |
|
|
|
|
|
fStartTime(0), |
|
|
fMutex(), |
|
|
fMutex(), |
|
|
fData(), |
|
|
fData(), |
|
|
leakDetector_MidiPattern() |
|
|
leakDetector_MidiPattern() |
|
|
//fStartTime(0), |
|
|
|
|
|
//fDuration(0) |
|
|
|
|
|
{ |
|
|
{ |
|
|
CARLA_ASSERT(kPlayer != nullptr); |
|
|
|
|
|
|
|
|
CARLA_SAFE_ASSERT(kPlayer != nullptr); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
~MidiPattern() |
|
|
|
|
|
|
|
|
~MidiPattern() noexcept |
|
|
{ |
|
|
{ |
|
|
fData.clear(); |
|
|
|
|
|
|
|
|
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) |
|
|
void addControl(const uint64_t time, const uint8_t channel, const uint8_t control, const uint8_t value) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* ctrlEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const ctrlEvent(new RawMidiEvent()); |
|
|
ctrlEvent->time = time; |
|
|
ctrlEvent->time = time; |
|
|
ctrlEvent->size = 3; |
|
|
ctrlEvent->size = 3; |
|
|
ctrlEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & 0x0F)); |
|
|
ctrlEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & 0x0F)); |
|
|
ctrlEvent->data[1] = control; |
|
|
ctrlEvent->data[1] = control; |
|
|
ctrlEvent->data[2] = value; |
|
|
ctrlEvent->data[2] = value; |
|
|
|
|
|
|
|
|
append(ctrlEvent); |
|
|
|
|
|
|
|
|
appendSorted(ctrlEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addChannelPressure(const uint64_t time, const uint8_t channel, const uint8_t pressure) |
|
|
void addChannelPressure(const uint64_t time, const uint8_t channel, const uint8_t pressure) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* pressureEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const pressureEvent(new RawMidiEvent()); |
|
|
pressureEvent->time = time; |
|
|
pressureEvent->time = time; |
|
|
pressureEvent->size = 2; |
|
|
pressureEvent->size = 2; |
|
|
pressureEvent->data[0] = uint8_t(MIDI_STATUS_CHANNEL_PRESSURE | (channel & 0x0F)); |
|
|
pressureEvent->data[0] = uint8_t(MIDI_STATUS_CHANNEL_PRESSURE | (channel & 0x0F)); |
|
|
pressureEvent->data[1] = pressure; |
|
|
pressureEvent->data[1] = pressure; |
|
|
|
|
|
|
|
|
append(pressureEvent); |
|
|
|
|
|
|
|
|
appendSorted(pressureEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addNote(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity, const uint32_t duration) |
|
|
void addNote(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity, const uint32_t duration) |
|
@@ -99,143 +110,173 @@ public: |
|
|
|
|
|
|
|
|
void addNoteOn(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity) |
|
|
void addNoteOn(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* noteOnEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const noteOnEvent(new RawMidiEvent()); |
|
|
noteOnEvent->time = time; |
|
|
noteOnEvent->time = time; |
|
|
noteOnEvent->size = 3; |
|
|
noteOnEvent->size = 3; |
|
|
noteOnEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_ON | (channel & 0x0F)); |
|
|
noteOnEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_ON | (channel & 0x0F)); |
|
|
noteOnEvent->data[1] = pitch; |
|
|
noteOnEvent->data[1] = pitch; |
|
|
noteOnEvent->data[2] = velocity; |
|
|
noteOnEvent->data[2] = velocity; |
|
|
|
|
|
|
|
|
append(noteOnEvent); |
|
|
|
|
|
|
|
|
appendSorted(noteOnEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addNoteOff(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity = 0) |
|
|
void addNoteOff(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t velocity = 0) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* noteOffEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const noteOffEvent(new RawMidiEvent()); |
|
|
noteOffEvent->time = time; |
|
|
noteOffEvent->time = time; |
|
|
noteOffEvent->size = 3; |
|
|
noteOffEvent->size = 3; |
|
|
noteOffEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_OFF | (channel & 0x0F)); |
|
|
noteOffEvent->data[0] = uint8_t(MIDI_STATUS_NOTE_OFF | (channel & 0x0F)); |
|
|
noteOffEvent->data[1] = pitch; |
|
|
noteOffEvent->data[1] = pitch; |
|
|
noteOffEvent->data[2] = velocity; |
|
|
noteOffEvent->data[2] = velocity; |
|
|
|
|
|
|
|
|
append(noteOffEvent); |
|
|
|
|
|
|
|
|
appendSorted(noteOffEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addNoteAftertouch(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t pressure) |
|
|
void addNoteAftertouch(const uint64_t time, const uint8_t channel, const uint8_t pitch, const uint8_t pressure) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* noteAfterEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const noteAfterEvent(new RawMidiEvent()); |
|
|
noteAfterEvent->time = time; |
|
|
noteAfterEvent->time = time; |
|
|
noteAfterEvent->size = 3; |
|
|
noteAfterEvent->size = 3; |
|
|
noteAfterEvent->data[0] = uint8_t(MIDI_STATUS_POLYPHONIC_AFTERTOUCH | (channel & 0x0F)); |
|
|
noteAfterEvent->data[0] = uint8_t(MIDI_STATUS_POLYPHONIC_AFTERTOUCH | (channel & 0x0F)); |
|
|
noteAfterEvent->data[1] = pitch; |
|
|
noteAfterEvent->data[1] = pitch; |
|
|
noteAfterEvent->data[2] = pressure; |
|
|
noteAfterEvent->data[2] = pressure; |
|
|
|
|
|
|
|
|
append(noteAfterEvent); |
|
|
|
|
|
|
|
|
appendSorted(noteAfterEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addProgram(const uint64_t time, const uint8_t channel, const uint8_t bank, const uint8_t program) |
|
|
void addProgram(const uint64_t time, const uint8_t channel, const uint8_t bank, const uint8_t program) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* bankEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const bankEvent(new RawMidiEvent()); |
|
|
bankEvent->time = time; |
|
|
bankEvent->time = time; |
|
|
bankEvent->size = 3; |
|
|
bankEvent->size = 3; |
|
|
bankEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & 0x0F)); |
|
|
bankEvent->data[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (channel & 0x0F)); |
|
|
bankEvent->data[1] = MIDI_CONTROL_BANK_SELECT; |
|
|
bankEvent->data[1] = MIDI_CONTROL_BANK_SELECT; |
|
|
bankEvent->data[2] = bank; |
|
|
bankEvent->data[2] = bank; |
|
|
|
|
|
|
|
|
RawMidiEvent* programEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const programEvent(new RawMidiEvent()); |
|
|
programEvent->time = time; |
|
|
programEvent->time = time; |
|
|
programEvent->size = 2; |
|
|
programEvent->size = 2; |
|
|
programEvent->data[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (channel & 0x0F)); |
|
|
programEvent->data[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (channel & 0x0F)); |
|
|
programEvent->data[1] = program; |
|
|
programEvent->data[1] = program; |
|
|
|
|
|
|
|
|
append(bankEvent); |
|
|
|
|
|
append(programEvent); |
|
|
|
|
|
|
|
|
appendSorted(bankEvent); |
|
|
|
|
|
appendSorted(programEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addPitchbend(const uint64_t time, const uint8_t channel, const uint8_t lsb, const uint8_t msb) |
|
|
void addPitchbend(const uint64_t time, const uint8_t channel, const uint8_t lsb, const uint8_t msb) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* pressureEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const pressureEvent(new RawMidiEvent()); |
|
|
pressureEvent->time = time; |
|
|
pressureEvent->time = time; |
|
|
pressureEvent->size = 3; |
|
|
pressureEvent->size = 3; |
|
|
pressureEvent->data[0] = uint8_t(MIDI_STATUS_PITCH_WHEEL_CONTROL | (channel & 0x0F)); |
|
|
pressureEvent->data[0] = uint8_t(MIDI_STATUS_PITCH_WHEEL_CONTROL | (channel & 0x0F)); |
|
|
pressureEvent->data[1] = lsb; |
|
|
pressureEvent->data[1] = lsb; |
|
|
pressureEvent->data[2] = msb; |
|
|
pressureEvent->data[2] = msb; |
|
|
|
|
|
|
|
|
append(pressureEvent); |
|
|
|
|
|
|
|
|
appendSorted(pressureEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void addRaw(const uint64_t time, const uint8_t* data, const uint8_t size) |
|
|
|
|
|
|
|
|
void addRaw(const uint64_t time, const uint8_t* const data, const uint8_t size) |
|
|
{ |
|
|
{ |
|
|
RawMidiEvent* rawEvent(new RawMidiEvent()); |
|
|
|
|
|
|
|
|
RawMidiEvent* const rawEvent(new RawMidiEvent()); |
|
|
rawEvent->time = time; |
|
|
rawEvent->time = time; |
|
|
rawEvent->size = size; |
|
|
rawEvent->size = size; |
|
|
|
|
|
|
|
|
carla_copy<uint8_t>(rawEvent->data, data, size); |
|
|
carla_copy<uint8_t>(rawEvent->data, data, size); |
|
|
|
|
|
|
|
|
append(rawEvent); |
|
|
|
|
|
|
|
|
appendSorted(rawEvent); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
// clear |
|
|
|
|
|
|
|
|
|
|
|
void clear() noexcept |
|
|
|
|
|
{ |
|
|
|
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
|
|
|
|
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin(); it.valid(); it.next()) |
|
|
|
|
|
delete it.getValue(nullptr); |
|
|
|
|
|
|
|
|
|
|
|
fData.clear(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void play(uint64_t timePosFrame, uint32_t frames) |
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
// play on time |
|
|
|
|
|
|
|
|
|
|
|
void play(uint64_t timePosFrame, const uint32_t frames) |
|
|
{ |
|
|
{ |
|
|
if (! fMutex.tryLock()) |
|
|
if (! fMutex.tryLock()) |
|
|
return; |
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
timePosFrame += fStartTime; |
|
|
|
|
|
|
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin(); it.valid(); it.next()) |
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin(); it.valid(); it.next()) |
|
|
{ |
|
|
{ |
|
|
const RawMidiEvent* const rawMidiEvent(it.getValue()); |
|
|
|
|
|
|
|
|
const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr)); |
|
|
|
|
|
CARLA_SAFE_ASSERT_CONTINUE(rawMidiEvent != nullptr); |
|
|
|
|
|
|
|
|
if (timePosFrame > rawMidiEvent->time) |
|
|
if (timePosFrame > rawMidiEvent->time) |
|
|
continue; |
|
|
continue; |
|
|
if (timePosFrame + frames <= rawMidiEvent->time) |
|
|
if (timePosFrame + frames <= rawMidiEvent->time) |
|
|
continue; |
|
|
continue; |
|
|
|
|
|
|
|
|
kPlayer->writeMidiEvent(timePosFrame, rawMidiEvent); |
|
|
|
|
|
|
|
|
kPlayer->writeMidiEvent(fMidiPort, timePosFrame, rawMidiEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fMutex.unlock(); |
|
|
fMutex.unlock(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void clear() |
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
// configure |
|
|
|
|
|
|
|
|
|
|
|
void setMidiPort(const uint8_t port) noexcept |
|
|
{ |
|
|
{ |
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
fData.clear(); |
|
|
|
|
|
|
|
|
fMidiPort = port; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void setStartTime(const uint64_t time) noexcept |
|
|
|
|
|
{ |
|
|
|
|
|
fStartTime = time; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
private: |
|
|
private: |
|
|
AbstractMidiPlayer* const kPlayer; |
|
|
AbstractMidiPlayer* const kPlayer; |
|
|
|
|
|
|
|
|
//uint32_t fStartTime; // unused |
|
|
|
|
|
//uint32_t fDuration; // unused |
|
|
|
|
|
|
|
|
uint8_t fMidiPort; |
|
|
|
|
|
uint64_t fStartTime; |
|
|
|
|
|
|
|
|
CarlaMutex fMutex; |
|
|
CarlaMutex fMutex; |
|
|
LinkedList<const RawMidiEvent*> fData; |
|
|
LinkedList<const RawMidiEvent*> fData; |
|
|
|
|
|
|
|
|
void append(const RawMidiEvent* const event) |
|
|
|
|
|
|
|
|
void appendSorted(const RawMidiEvent* const event) |
|
|
{ |
|
|
{ |
|
|
|
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
|
|
|
if (fData.isEmpty()) |
|
|
if (fData.isEmpty()) |
|
|
{ |
|
|
{ |
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
fData.append(event); |
|
|
fData.append(event); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin(); it.valid(); it.next()) |
|
|
for (LinkedList<const RawMidiEvent*>::Itenerator it = fData.begin(); it.valid(); it.next()) |
|
|
{ |
|
|
{ |
|
|
const RawMidiEvent* const oldEvent(it.getValue()); |
|
|
|
|
|
|
|
|
const RawMidiEvent* const oldEvent(it.getValue(nullptr)); |
|
|
|
|
|
CARLA_SAFE_ASSERT_CONTINUE(oldEvent != nullptr); |
|
|
|
|
|
|
|
|
if (event->time >= oldEvent->time) |
|
|
if (event->time >= oldEvent->time) |
|
|
continue; |
|
|
continue; |
|
|
|
|
|
|
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
fData.insertAt(event, it); |
|
|
fData.insertAt(event, it); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const CarlaMutexLocker sl(fMutex); |
|
|
|
|
|
fData.append(event); |
|
|
fData.append(event); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPattern) |
|
|
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MidiPattern) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
#endif // MIDI_BASE_HPP_INCLUDED |
|
|
#endif // MIDI_BASE_HPP_INCLUDED |