MidiView: fix display of out-of-range live (recording) notes

This involves better handling of note range changes, and also a renaming of
member variables to better reflect their function.

It adds _finished_live_notes to hold notes that were recorded live but have
ended, since these also need to be updated as zooming or note range changes
This commit is contained in:
Paul Davis 2025-11-17 11:07:13 -07:00
parent 68c69fb89d
commit a0302bd7fc
3 changed files with 129 additions and 74 deletions

View file

@ -434,7 +434,7 @@ MidiStreamView::update_rec_box ()
region->set_length (timecnt_t (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start()));
MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
mrv->extend_active_notes ();
mrv->extend_unfinished_live_notes ();
}

View file

@ -114,8 +114,8 @@ MidiView::MidiView (std::shared_ptr<MidiTrack> mt,
uint32_t basic_color)
: _editing_context (ec)
, _midi_context (bg)
, _active_notes (nullptr)
, active_note_end (std::numeric_limits<Temporal::Beats>::max())
, _unfinished_live_notes (nullptr)
, live_note_end (std::numeric_limits<Temporal::Beats>::max())
, _note_group (new ArdourCanvas::Container (&parent))
, _note_diff_command (nullptr)
, _ghost_note (nullptr)
@ -153,8 +153,8 @@ MidiView::MidiView (MidiView const & other)
, _editing_context (other.editing_context())
, _midi_context (other.midi_context())
, _midi_region (other.midi_region())
, _active_notes (nullptr)
, active_note_end (std::numeric_limits<Temporal::Beats>::max())
, _unfinished_live_notes (nullptr)
, live_note_end (std::numeric_limits<Temporal::Beats>::max())
, _note_group (new ArdourCanvas::Container (other._note_group->parent()))
, _note_diff_command (0)
, _ghost_note (nullptr)
@ -1162,7 +1162,7 @@ MidiView::model_changed()
EC_LOCAL_TEMPO_SCOPE_ARG (_editing_context);
if (_active_notes) {
if (_unfinished_live_notes) {
// Currently recording
const samplecnt_t zoom = _editing_context.get_current_zoom();
if (zoom != _last_display_zoom) {
@ -1325,7 +1325,7 @@ MidiView::view_changed()
return;
}
if (_active_notes) {
if (_unfinished_live_notes) {
/* Recording */
@ -1599,7 +1599,7 @@ MidiView::~MidiView ()
delete _list_editor;
if (_active_notes) {
if (_unfinished_live_notes) {
end_write();
}
_entered_note = 0;
@ -1680,25 +1680,33 @@ MidiView::begin_write()
XXX this should not happen.
*/
if (_active_notes) {
if (_unfinished_live_notes) {
for (auto n = 0; n < 128; ++n) {
if (_active_notes[n]) {
auto iter = _events.find (_active_notes[n]->note());
if (_unfinished_live_notes[n]) {
auto iter = _events.find (_unfinished_live_notes[n]->note());
if (iter != _events.end()) {
_events.erase (iter);
}
}
}
delete [] _active_notes;
delete [] _unfinished_live_notes;
}
for (auto & n : _finished_live_notes) {
auto iter = _events.find (n->note());
if (iter != _events.end()) {
_events.erase (iter);
}
}
/* reallocate */
_active_notes = new Note*[128];
for (unsigned i = 0; i < 128; ++i) {
_active_notes[i] = nullptr;
_unfinished_live_notes = new Note*[128];
for (size_t i = 0; i < 128; ++i) {
_unfinished_live_notes[i] = nullptr;
}
active_note_end = timecnt_t (Temporal::BeatTime);
_finished_live_notes.clear ();
live_note_end = timecnt_t (Temporal::BeatTime);
}
@ -1711,42 +1719,43 @@ MidiView::end_write()
owned by _events. Just delete the container used for active
notes only.
*/
delete [] _active_notes;
_active_notes = nullptr;
delete [] _unfinished_live_notes;
_unfinished_live_notes = nullptr;
_finished_live_notes.clear ();
_marked_for_selection.clear();
_marked_for_velocity.clear();
active_note_end = std::numeric_limits<Temporal::Beats>::max();
live_note_end = std::numeric_limits<Temporal::Beats>::max();
}
/** Extend active notes to rightmost edge of region (if length is changed)
*/
void
MidiView::extend_active_notes()
MidiView::extend_unfinished_live_notes()
{
if (!_midi_region) {
return;
}
extend_active_notes (_midi_region->length());
extend_unfinished_live_notes (_midi_region->length());
}
void
MidiView::extend_active_notes (timecnt_t const & duration)
MidiView::extend_unfinished_live_notes (timecnt_t const & duration)
{
if (!_midi_region) {
return;
}
if (!_active_notes) {
if (!_unfinished_live_notes) {
return;
}
double x1 = _editing_context.duration_to_pixels (duration);
for (int i = 0; i < 128; ++i) {
if (_active_notes[i]) {
_active_notes[i]->set_x1 (x1);
if (_unfinished_live_notes[i]) {
_unfinished_live_notes[i]->set_x1 (x1);
}
}
}
@ -1854,8 +1863,8 @@ MidiView::update_sustained (Note* ev)
ev->set_velocity (note->velocity()/127.0);
if (note->end_time() == std::numeric_limits<Temporal::Beats>::max()) {
if (_active_notes && note->note() < 128) {
Note* const old_nb = _active_notes[note->note()];
if (_unfinished_live_notes && note->note() < 128) {
Note* const old_nb = _unfinished_live_notes[note->note()];
if (old_nb && (old_nb != ev)) {
/* There is an active note on this key, so we have a stuck
note. Finish the old rectangle here.
@ -1866,7 +1875,7 @@ MidiView::update_sustained (Note* ev)
/* XXX we now leak old_nb if it was set since there are
* no other references to it, plus it will remain on-screen
*/
_active_notes[note->note()] = ev;
_unfinished_live_notes[note->note()] = ev;
}
/* outline all but right edge */
ev->set_outline_what (ArdourCanvas::Rectangle::What (
@ -1878,6 +1887,14 @@ MidiView::update_sustained (Note* ev)
ev->set_outline_all ();
}
bool visible ((y0 >= 0) && (y1 <= _midi_context.contents_height()));
if (!visible) {
ev->hide ();
} else {
ev->show ();
}
color_note (ev, note->channel());
ev->set_ignore_events (!note_editable (ev));
}
@ -1916,7 +1933,7 @@ MidiView::clip_capture_update_sustained (Note *ev, double& x0, double& x1, doubl
/* normal note */
timepos_t ane = active_note_end.end();
timepos_t ane = live_note_end.end();
if (ane.beats() != std::numeric_limits<Temporal::Beats>::max()) {
if (note_end > ane) {
@ -1930,7 +1947,7 @@ MidiView::clip_capture_update_sustained (Note *ev, double& x0, double& x1, doubl
/* nascent note currently being recorded, noteOff has not yet arrived */
x1 = x0 + std::max (1., _editing_context.duration_to_pixels (note_start.distance (active_note_end.end())));
x1 = x0 + std::max (1., _editing_context.duration_to_pixels (note_start.distance (live_note_end.end())));
}
y1 = y0 + std::max (1., note_height() - 1.);
@ -4707,15 +4724,15 @@ MidiView::clip_data_recorded (samplecnt_t total_duration)
return;
}
if (!_active_notes) {
if (!_unfinished_live_notes) {
/* left over idle callback, but we're not recording */
return;
}
if (_active_notes) {
if (_unfinished_live_notes) {
for (int n = 0; n < 128; ++n) {
if (_active_notes[n]) {
update_sustained (_active_notes[n]);
if (_unfinished_live_notes[n]) {
update_sustained (_unfinished_live_notes[n]);
}
}
}
@ -4724,6 +4741,7 @@ MidiView::clip_data_recorded (samplecnt_t total_duration)
assert (tb);
std::shared_ptr<MidiBuffer> buf = tb->get_gui_feed_buffer();
bool note_range_changed = false;
for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
@ -4747,36 +4765,53 @@ MidiView::clip_data_recorded (samplecnt_t total_duration)
std::shared_ptr<NoteType> note (new NoteType (ev.channel(), time_beats, std::numeric_limits<Temporal::Beats>::max() - time_beats, ev.note(), ev.velocity()));
NoteBase* nb = add_note (note, true);
if (ev.note() < _midi_context.lowest_note()) {
_midi_context.update_data_note_range (ev.note(), _midi_context.highest_note());
note_range_changed |= set_note_range (ev.note(), _midi_context.highest_note());
} else if (ev.note() > _midi_context.highest_note()) {
_midi_context.update_data_note_range (_midi_context.lowest_note(), ev.note());
note_range_changed |= set_note_range (_midi_context.lowest_note(), ev.note());
}
const int y = _midi_context.note_to_y (note->note());
bool visible = (y >= 0) && (y <= _midi_context.contents_height());
NoteBase* nb = add_note (note, visible);
nb->item()->set_fill_color (UIConfiguration::instance().color ("recording note"));
nb->item()->set_outline_color (UIConfiguration::instance().color ("recording note"));
/* fix up our note range */
if (ev.note() < _midi_context.lowest_note()) {
set_note_range (ev.note(), _midi_context.highest_note());
} else if (ev.note() > _midi_context.highest_note()) {
set_note_range (_midi_context.lowest_note(), ev.note());
}
} else if (ev.type() == MIDI_CMD_NOTE_OFF) {
// XXX WAS resolve_note (ev.note (), time_beats);
uint8_t note = ev.note ();
Temporal::Beats end_time = time_beats;
if (_active_notes && _active_notes[note]) {
if (_unfinished_live_notes && _unfinished_live_notes[note]) {
/* Set note length so update_note() works. Note this is a local note
for recording, not from a model, so we can safely mess with it. */
_active_notes[note]->note()->set_length (end_time - _active_notes[note]->note()->time());
_unfinished_live_notes[note]->note()->set_length (end_time - _unfinished_live_notes[note]->note()->time());
_active_notes[note]->set_x1 (_editing_context.sample_to_pixel (timepos_t (ev.time ()).samples()));
_active_notes[note]->set_outline_all ();
_active_notes[note] = nullptr;
_unfinished_live_notes[note]->set_x1 (_editing_context.sample_to_pixel (timepos_t (ev.time ()).samples()));
_unfinished_live_notes[note]->set_outline_all ();
_unfinished_live_notes[note] = nullptr;
}
}
}
active_note_end = timecnt_t (total_duration);
if (note_range_changed) {
assert (_unfinished_live_notes);
for (size_t n = 0; n < 128; ++n) {
if (_unfinished_live_notes[n]) {
update_note (_unfinished_live_notes[n]);
}
}
for (auto & n : _finished_live_notes) {
update_note (n);
}
}
live_note_end = timecnt_t (total_duration);
}
/** Called when a diskstream on our track has received some data. Update the view, if applicable.
@ -4793,7 +4828,7 @@ MidiView::data_recorded (std::weak_ptr<MidiSource> w)
return;
}
if (!_active_notes) {
if (!_unfinished_live_notes) {
/* we aren't actively being recorded to */
return;
}
@ -4807,6 +4842,7 @@ MidiView::data_recorded (std::weak_ptr<MidiSource> w)
std::shared_ptr<MidiBuffer> buf = _midi_track->get_gui_feed_buffer ();
samplepos_t back = max_samplepos;
bool note_range_changed = false;
for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
@ -4833,27 +4869,32 @@ MidiView::data_recorded (std::weak_ptr<MidiSource> w)
assert (note->end_time() == std::numeric_limits<Temporal::Beats>::max());
NoteBase* nb = add_note (note, true);
if (ev.note() < _midi_context.lowest_note()) {
_midi_context.update_data_note_range (ev.note(), _midi_context.highest_note());
note_range_changed |= set_note_range (ev.note(), _midi_context.highest_note());
} else if (ev.note() > _midi_context.highest_note()) {
_midi_context.update_data_note_range (_midi_context.lowest_note(), ev.note());
note_range_changed |= set_note_range (_midi_context.lowest_note(), ev.note());
}
const int y = _midi_context.note_to_y (note->note());
bool visible = (y >= 0) && (y <= _midi_context.contents_height());
NoteBase* nb = add_note (note, visible);
nb->item()->set_fill_color (UIConfiguration::instance().color ("recording note"));
nb->item()->set_outline_color (UIConfiguration::instance().color ("recording note"));
/* fix up our note range */
if (ev.note() < _midi_context.lowest_note()) {
set_note_range (ev.note(), _midi_context.highest_note());
} else if (ev.note() > _midi_context.highest_note()) {
set_note_range (_midi_context.lowest_note(), ev.note());
}
} else if (ev.type() == MIDI_CMD_NOTE_OFF) {
// XXX WAS resolve_note (ev.note (), time_beats);
uint8_t note = ev.note ();
Temporal::Beats end_time = time_beats;
if (_active_notes && _active_notes[note]) {
if (_unfinished_live_notes && _unfinished_live_notes[note]) {
/* Set note length so update_note() works. Note this is a local note
for recording, not from a model, so we can safely mess with it. */
_active_notes[note]->note()->set_length (end_time - _active_notes[note]->note()->time());
_unfinished_live_notes[note]->note()->set_length (end_time - _unfinished_live_notes[note]->note()->time());
/* End time is relative to the source being recorded. */
@ -4863,15 +4904,28 @@ MidiView::data_recorded (std::weak_ptr<MidiSource> w)
// - and then take the samples() value of that and convert it to pixels
//
// Much simpler to just use ev.time() which is already the absolute position (in sample-time)
_active_notes[note]->set_x1 (_editing_context.sample_to_pixel ((src->time_since_capture_start (timepos_t (ev.time ()))).samples()));
_active_notes[note]->set_outline_all ();
_active_notes[note] = nullptr;
_unfinished_live_notes[note]->set_x1 (_editing_context.sample_to_pixel ((src->time_since_capture_start (timepos_t (ev.time ()))).samples()));
_unfinished_live_notes[note]->set_outline_all ();
_finished_live_notes.push_back (_unfinished_live_notes[note]);
_unfinished_live_notes[note] = nullptr;
}
}
back = ev.time ();
}
if (note_range_changed) {
assert (_unfinished_live_notes);
for (size_t n = 0; n < 128; ++n) {
if (_unfinished_live_notes[n]) {
update_note (_unfinished_live_notes[n]);
}
}
for (auto & n : _finished_live_notes) {
update_note (n);
}
}
_midi_context.record_layer_check (_midi_region, back);
}
@ -5297,7 +5351,7 @@ struct NoteExtentInfo
void
MidiView::join_notes_on_channel (int chn)
{
NoteExtentInfo ninfo[127];
NoteExtentInfo ninfo[128];
for (auto & s : _selection) {
@ -5329,7 +5383,7 @@ MidiView::join_notes_on_channel (int chn)
}
}
for (size_t n = 0; n < 127; ++n) {
for (size_t n = 0; n < 128; ++n) {
NoteExtentInfo& ni (ninfo[n]);
@ -5445,16 +5499,16 @@ MidiView::height() const
return _midi_context.height();
}
void
bool
MidiView::set_note_range (uint8_t low, uint8_t high)
{
_midi_context.apply_note_range (low, high, true);
return _midi_context.apply_note_range (low, high, true);
}
void
bool
MidiView::maybe_set_note_range (uint8_t low, uint8_t high)
{
_midi_context.maybe_apply_note_range (low, high, true);
return _midi_context.maybe_apply_note_range (low, high, true);
}
void

View file

@ -183,8 +183,8 @@ class MidiView : public virtual sigc::trackable, public LineMerger
void begin_write ();
void end_write ();
void extend_active_notes ();
void extend_active_notes (Temporal::timecnt_t const &);
void extend_unfinished_live_notes ();
void extend_unfinished_live_notes (Temporal::timecnt_t const &);
virtual void begin_drag_edit (std::string const & why);
void end_drag_edit ();
@ -308,8 +308,8 @@ class MidiView : public virtual sigc::trackable, public LineMerger
void show_list_editor ();
void set_note_range (uint8_t low, uint8_t high);
void maybe_set_note_range (uint8_t low, uint8_t high);
bool set_note_range (uint8_t low, uint8_t high);
bool maybe_set_note_range (uint8_t low, uint8_t high);
virtual void set_visibility_note_range (MidiViewBackground::VisibleNoteRange, bool);
typedef std::set<NoteBase*> Selection;
@ -513,8 +513,9 @@ class MidiView : public virtual sigc::trackable, public LineMerger
CopyDragEvents _copy_drag_events;
PatchChanges _patch_changes;
SysExes _sys_exes;
Note** _active_notes;
Temporal::timecnt_t active_note_end;
Note** _unfinished_live_notes;
std::vector<Note*> _finished_live_notes;
Temporal::timecnt_t live_note_end;
ArdourCanvas::Container* _note_group;
ARDOUR::MidiModel::NoteDiffCommand* _note_diff_command;
NoteBase* _ghost_note;