mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-07 15:25:01 +01:00
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:
parent
5ad7361b28
commit
b67b18483c
3 changed files with 147 additions and 10 deletions
|
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
namespace Evoral {
|
namespace Evoral {
|
||||||
template <typename T> class EventSink;
|
template <typename T> class EventSink;
|
||||||
|
template <typename T> class EventList;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ARDOUR {
|
namespace ARDOUR {
|
||||||
|
|
@ -87,6 +88,7 @@ class LIBARDOUR_API MidiStateTracker : public MidiNoteTracker
|
||||||
void reset ();
|
void reset ();
|
||||||
|
|
||||||
void flush (MidiBuffer&, samplepos_t, bool 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:
|
private:
|
||||||
uint8_t program[16];
|
uint8_t program[16];
|
||||||
|
|
|
||||||
|
|
@ -352,7 +352,9 @@ MidiPlaylist::render (MidiChannelFilter* filter)
|
||||||
Evoral::EventList<samplepos_t> evlist;
|
Evoral::EventList<samplepos_t> evlist;
|
||||||
|
|
||||||
if (all_transparent) {
|
if (all_transparent) {
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 regions to read\n", regs.size()));
|
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("\t%1 regions to read\n", regs.size()));
|
||||||
|
|
||||||
for (auto i = regs.rbegin(); i != regs.rend(); ++i) {
|
for (auto i = regs.rbegin(); i != regs.rend(); ++i) {
|
||||||
boost::shared_ptr<MidiRegion> mr = *i;
|
boost::shared_ptr<MidiRegion> mr = *i;
|
||||||
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("render from %1\n", mr->name()));
|
DEBUG_TRACE (DEBUG::MidiPlaylistIO, string_compose ("render from %1\n", mr->name()));
|
||||||
|
|
@ -388,14 +390,16 @@ MidiPlaylist::render (MidiChannelFilter* filter)
|
||||||
}
|
}
|
||||||
tmp.sort (cmp);
|
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) {
|
for (Evoral::EventList<samplepos_t>::iterator e = tmp.begin(); e != tmp.end(); ++e) {
|
||||||
Evoral::Event<samplepos_t>* ev (*e);
|
Evoral::Event<samplepos_t>* ev (*e);
|
||||||
timepos_t t (ev->time());
|
timepos_t t (ev->time());
|
||||||
|
|
||||||
if (ev->event_type () == Evoral::NO_EVENT) {
|
if (ev->event_type () == Evoral::NO_EVENT) {
|
||||||
/* reached region bound of an opaque region above this region. */
|
/* 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)) {
|
} else if (region_is_audible_at (mr, t)) {
|
||||||
/* no opaque region above this event */
|
/* no opaque region above this event */
|
||||||
uint8_t* evbuf = ev->buffer();
|
uint8_t* evbuf = ev->buffer();
|
||||||
|
|
@ -403,15 +407,10 @@ MidiPlaylist::render (MidiChannelFilter* filter)
|
||||||
; /* skip note off */
|
; /* skip note off */
|
||||||
} else {
|
} else {
|
||||||
evlist.write (ev->time(), ev->event_type(), ev->size(), evbuf);
|
evlist.write (ev->time(), ev->event_type(), ev->size(), evbuf);
|
||||||
if (3 == ev->size()) {
|
mtr.track (evbuf);
|
||||||
mtr.track (evbuf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* there is an opaque region above this event, skip this event,
|
/* there is an opaque region above this event, skip this event. */
|
||||||
* and for good measure resolve notes.
|
|
||||||
*/
|
|
||||||
mtr.resolve_notes (evlist, ev->time());
|
|
||||||
}
|
}
|
||||||
delete ev;
|
delete ev;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
#include "pbd/compose.h"
|
#include "pbd/compose.h"
|
||||||
|
|
||||||
#include "evoral/EventSink.h"
|
#include "evoral/EventList.h"
|
||||||
|
|
||||||
#include "ardour/debug.h"
|
#include "ardour/debug.h"
|
||||||
#include "ardour/midi_source.h"
|
#include "ardour/midi_source.h"
|
||||||
|
|
@ -244,6 +244,7 @@ MidiStateTracker::reset ()
|
||||||
|
|
||||||
for (size_t n = 0; n < n_channels; ++n) {
|
for (size_t n = 0; n < n_channels; ++n) {
|
||||||
program[n] = 0x80;
|
program[n] = 0x80;
|
||||||
|
bender[n] = 0x8000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t chn = 0; chn < n_channels; ++chn) {
|
for (size_t chn = 0; chn < n_channels; ++chn) {
|
||||||
|
|
@ -324,6 +325,7 @@ MidiStateTracker::track (const uint8_t* evbuf)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MIDI_CMD_BENDER:
|
case MIDI_CMD_BENDER:
|
||||||
|
bender[chan] = ((evbuf[2]<<7) | evbuf[1]) & 0x3fff;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MIDI_CMD_COMMON_RESET:
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue