13
0

Implement MIDI region automation (CC, PGM, PB) opaqueness

This adds a special case of "flush/resolve" to restore the
state of an upper layer opaque MIDI region while at the same time
resolving notes of a lower layer region.
This commit is contained in:
Robin Gareus 2022-09-10 02:39:16 +02:00
parent 5ad7361b28
commit b67b18483c
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
3 changed files with 147 additions and 10 deletions

View File

@ -29,6 +29,7 @@
namespace Evoral {
template <typename T> class EventSink;
template <typename T> class EventList;
}
namespace ARDOUR {
@ -87,6 +88,7 @@ class LIBARDOUR_API MidiStateTracker : public MidiNoteTracker
void reset ();
void flush (MidiBuffer&, samplepos_t, bool reset);
void resolve_state (Evoral::EventSink<samplepos_t>&, Evoral::EventList<samplepos_t> const&, samplepos_t time, bool reset = true);
private:
uint8_t program[16];

View File

@ -352,7 +352,9 @@ MidiPlaylist::render (MidiChannelFilter* filter)
Evoral::EventList<samplepos_t> evlist;
if (all_transparent) {
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 regions to read\n", regs.size()));
for (auto i = regs.rbegin(); i != regs.rend(); ++i) {
boost::shared_ptr<MidiRegion> mr = *i;
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("render from %1\n", mr->name()));
@ -388,14 +390,16 @@ MidiPlaylist::render (MidiChannelFilter* filter)
}
tmp.sort (cmp);
MidiNoteTracker mtr;
MidiStateTracker mtr;
Evoral::EventList<samplepos_t> const slist (evlist);
for (Evoral::EventList<samplepos_t>::iterator e = tmp.begin(); e != tmp.end(); ++e) {
Evoral::Event<samplepos_t>* ev (*e);
timepos_t t (ev->time());
if (ev->event_type () == Evoral::NO_EVENT) {
/* reached region bound of an opaque region above this region. */
mtr.resolve_notes (evlist, ev->time());
mtr.resolve_state (evlist, slist, ev->time());
} else if (region_is_audible_at (mr, t)) {
/* no opaque region above this event */
uint8_t* evbuf = ev->buffer();
@ -403,15 +407,10 @@ MidiPlaylist::render (MidiChannelFilter* filter)
; /* skip note off */
} else {
evlist.write (ev->time(), ev->event_type(), ev->size(), evbuf);
if (3 == ev->size()) {
mtr.track (evbuf);
}
mtr.track (evbuf);
}
} else {
/* there is an opaque region above this event, skip this event,
* and for good measure resolve notes.
*/
mtr.resolve_notes (evlist, ev->time());
/* there is an opaque region above this event, skip this event. */
}
delete ev;
}

View File

@ -22,7 +22,7 @@
#include "pbd/compose.h"
#include "evoral/EventSink.h"
#include "evoral/EventList.h"
#include "ardour/debug.h"
#include "ardour/midi_source.h"
@ -244,6 +244,7 @@ MidiStateTracker::reset ()
for (size_t n = 0; n < n_channels; ++n) {
program[n] = 0x80;
bender[n] = 0x8000;
}
for (size_t chn = 0; chn < n_channels; ++chn) {
@ -324,6 +325,7 @@ MidiStateTracker::track (const uint8_t* evbuf)
break;
case MIDI_CMD_BENDER:
bender[chan] = ((evbuf[2]<<7) | evbuf[1]) & 0x3fff;
break;
case MIDI_CMD_COMMON_RESET:
@ -369,3 +371,137 @@ MidiStateTracker::flush (MidiBuffer& dst, samplepos_t time, bool reset)
}
}
}
/* return 0 if event is not found
* return 1 if event is found before time t
* return -1 if event is found at time t
*/
static int
find_event (Evoral::EventList<samplepos_t> const& evlist, samplepos_t time, uint8_t* buf)
{
for (auto const& e : evlist) {
Evoral::Event<samplepos_t>* ev (e);
timepos_t t (ev->time ());
if (t > time) {
break;
}
uint8_t const* evbuf = ev->buffer ();
if (evbuf[0] == buf[0]) {
if (buf[1] != 0x80 && evbuf[1] != buf[1]) {
continue;
}
for (uint32_t i = 1; i < ev->size (); ++i) {
buf[i] = evbuf[i];
}
return t == time ? -1 : 1;
}
}
return 0;
}
void
MidiStateTracker::resolve_state (Evoral::EventSink<samplepos_t>& dst, Evoral::EventList<samplepos_t> const& evlist, samplepos_t time, bool reset)
{
/* XXX implement me */
uint8_t buf[3];
const size_t n_channels = 16;
const size_t n_controls = 127;
resolve_notes (dst, time);
for (size_t chn = 0; chn < n_channels; ++chn) {
/* restore CC */
for (size_t ctl = 0; ctl < n_controls; ++ctl) {
if ((control[chn][ctl] & 0x80) == 0) {
if (reset) {
control[chn][ctl] = 0x80;
}
buf[0] = MIDI_CMD_CONTROL | chn;
buf[1] = ctl;
switch (find_event (evlist, time, buf)) {
case 1:
/* (event found before tme)
* restore prior CC (notably bank select)
*
* Layer 1: [CX....] [.......]
* Layer 2: [.....CY.......]
* restore CX: ^
*/
dst.write (time, Evoral::MIDI_EVENT, 3, buf);
break;
case 0:
/* (no event was found before, or at tme)
* The goal is to reset a conroller, unless there already
* is an CC event at the start of above region (case -1:).
*
* Layer 1: [......] [CZ......]
* Layer 2: [.....CY.......]
* reset, unless CZ exist: ^
*/
switch (ctl) {
/* clang-format off */
case 0x01: buf[2] = 0x00; break; /* mod wheel MSB */
case 0x21: buf[2] = 0x00; break; /* mod wheel LSB */
case 0x02: buf[2] = 0x00; break; /* breath MSB */
case 0x22: buf[2] = 0x00; break; /* breath LSB */
case 0x07: buf[2] = 0x7f; break; /* volume MSB */
case 0x27: buf[2] = 0x7f; break; /* volume LSB */
case 0x08: buf[2] = 0x40; break; /* balance MSB */
case 0x28: buf[2] = 0x00; break; /* balance LSB */
case 0x0a: buf[2] = 0x40; break; /* pan MSB */
case 0x2a: buf[2] = 0x00; break; /* pan LSB */
case 0x40: buf[2] = 0x00; break; /* sustain */
case 0x41: buf[2] = 0x00; break; /* portamento */
case 0x42: buf[2] = 0x00; break; /* sostenuto */
case 0x43: buf[2] = 0x00; break; /* soft pedal */
case 0x44: buf[2] = 0x00; break; /* legato switch */
/* clang-format on */
default:
/* do not reset other controls */
continue;
}
dst.write (time, Evoral::MIDI_EVENT, 3, buf);
break;
default:
/* do nothing */
break;
}
}
}
/* If the program was modified, replay the most recent event found in evlist before *time*.
*
* Layer 1: [P1....] [.......]
* Layer 2: [.....P2.......]
* restore P1: ^
*/
if ((program[chn] & 0x80) == 0) {
buf[0] = MIDI_CMD_PGM_CHANGE | chn;
buf[1] = 0x80;
if (find_event (evlist, time, buf) > 0) {
dst.write (time, Evoral::MIDI_EVENT, 2, buf);
}
if (reset) {
program[chn] = 0x80;
}
}
/* reset pitch-bend */
if ((bender[chn] & 0x8000) == 0) {
buf[0] = MIDI_CMD_BENDER | chn;
buf[1] = 0x80;
/* .. unless there is a PB event at the start */
if (find_event (evlist, time, buf) >= 0) {
buf[1] = 0x00;
buf[2] = 0x40;
dst.write (time, Evoral::MIDI_EVENT, 3, buf);
}
if (reset) {
bender[chn] = 0x8000;
}
}
}
}