From a0302bd7fce57dcd89c58ee732f078ef6d74b9e0 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 17 Nov 2025 11:07:13 -0700 Subject: [PATCH] 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 --- gtk2_ardour/midi_streamview.cc | 2 +- gtk2_ardour/midi_view.cc | 188 +++++++++++++++++++++------------ gtk2_ardour/midi_view.h | 13 +-- 3 files changed, 129 insertions(+), 74 deletions(-) diff --git a/gtk2_ardour/midi_streamview.cc b/gtk2_ardour/midi_streamview.cc index 5a7ab3c2ad..0027a88865 100644 --- a/gtk2_ardour/midi_streamview.cc +++ b/gtk2_ardour/midi_streamview.cc @@ -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 (rec_regions.back().second); - mrv->extend_active_notes (); + mrv->extend_unfinished_live_notes (); } diff --git a/gtk2_ardour/midi_view.cc b/gtk2_ardour/midi_view.cc index 2da91537fb..3eb8ff5aa7 100644 --- a/gtk2_ardour/midi_view.cc +++ b/gtk2_ardour/midi_view.cc @@ -114,8 +114,8 @@ MidiView::MidiView (std::shared_ptr mt, uint32_t basic_color) : _editing_context (ec) , _midi_context (bg) - , _active_notes (nullptr) - , active_note_end (std::numeric_limits::max()) + , _unfinished_live_notes (nullptr) + , live_note_end (std::numeric_limits::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::max()) + , _unfinished_live_notes (nullptr) + , live_note_end (std::numeric_limits::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::max(); + live_note_end = std::numeric_limits::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::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::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 buf = tb->get_gui_feed_buffer(); + bool note_range_changed = false; for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) { const Evoral::Event& ev = *i; @@ -4747,36 +4765,53 @@ MidiView::clip_data_recorded (samplecnt_t total_duration) std::shared_ptr note (new NoteType (ev.channel(), time_beats, std::numeric_limits::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 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 w) std::shared_ptr 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& ev = *i; @@ -4833,27 +4869,32 @@ MidiView::data_recorded (std::weak_ptr w) assert (note->end_time() == std::numeric_limits::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 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 diff --git a/gtk2_ardour/midi_view.h b/gtk2_ardour/midi_view.h index 7bdf171f49..ab4fbba508 100644 --- a/gtk2_ardour/midi_view.h +++ b/gtk2_ardour/midi_view.h @@ -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 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 _finished_live_notes; + Temporal::timecnt_t live_note_end; ArdourCanvas::Container* _note_group; ARDOUR::MidiModel::NoteDiffCommand* _note_diff_command; NoteBase* _ghost_note;