From c2094085e338030082c47a738fda2a886a2571f4 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 14 Oct 2024 21:43:36 -0600 Subject: [PATCH] sort of get MIDI notes to extend during clip recording Also, robustify MidiView against a missing MidiModel member variable --- gtk2_ardour/midi_cue_editor.cc | 30 ++++-- gtk2_ardour/midi_cue_editor.h | 5 +- gtk2_ardour/midi_view.cc | 167 +++++++++++++++++++++++++++------ gtk2_ardour/midi_view.h | 10 +- 4 files changed, 169 insertions(+), 43 deletions(-) diff --git a/gtk2_ardour/midi_cue_editor.cc b/gtk2_ardour/midi_cue_editor.cc index 8c49310416..8c402a029e 100644 --- a/gtk2_ardour/midi_cue_editor.cc +++ b/gtk2_ardour/midi_cue_editor.cc @@ -80,6 +80,9 @@ MidiCueEditor::MidiCueEditor() view = new MidiCueView (nullptr, 0, *data_group, *this, *bg, 0xff0000ff); + bg->set_view (view); + prh->set_view (view); + _verbose_cursor = new VerboseCursor (*this); // _playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event, X_("playhead")); @@ -433,19 +436,33 @@ MidiCueEditor::toolbox () } void -MidiCueEditor::data_captured () +MidiCueEditor::data_captured (timecnt_t total_duration) +{ + data_capture_duration = total_duration; + + if (!idle_update_queued.exchange (1)) { + Glib::signal_idle().connect (sigc::mem_fun (*this, &MidiCueEditor::idle_data_captured)); + } +} + +bool +MidiCueEditor::idle_data_captured () { if (view) { - view->clip_data_recorded(); + view->clip_data_recorded (data_capture_duration); } + idle_update_queued.store (0); + return false; } void MidiCueEditor::set_box (std::shared_ptr b) { capture_connections.drop_connections (); + idle_update_queued.store (0); + if (b) { - b->Captured.connect (capture_connections, invalidator (*this), boost::bind (&MidiCueEditor::data_captured, this), gui_context()); + b->Captured.connect (capture_connections, invalidator (*this), boost::bind (&MidiCueEditor::data_captured, this, _1), gui_context()); /* Don't bind a shared_ptr within the lambda */ TriggerBox* tb (b.get()); b->RecEnableChanged.connect (capture_connections, invalidator (*this), [&, tb]() { rec_enable_change (tb); }, gui_context()); @@ -472,17 +489,12 @@ void MidiCueEditor::set_region (std::shared_ptr r) { if (!r) { - bg->set_view (nullptr); - prh->set_view (nullptr); -#warning paul unset view model + view->set_region (nullptr); return; } view->set_region (r); - bg->set_view (view); - prh->set_view (view); - double w, h; prh->size_request (w, h); _timeline_origin = w; diff --git a/gtk2_ardour/midi_cue_editor.h b/gtk2_ardour/midi_cue_editor.h index a3eb970326..c73fdc938c 100644 --- a/gtk2_ardour/midi_cue_editor.h +++ b/gtk2_ardour/midi_cue_editor.h @@ -207,8 +207,11 @@ class MidiCueEditor : public CueEditor void bindings_changed (); void rec_enable_change (ARDOUR::TriggerBox*); - void data_captured (); + void data_captured (Temporal::timecnt_t); + bool idle_data_captured (); + std::atomic idle_update_queued; PBD::ScopedConnectionList capture_connections; + Temporal::timecnt_t data_capture_duration; }; diff --git a/gtk2_ardour/midi_view.cc b/gtk2_ardour/midi_view.cc index f2e42280b3..9dd3871f43 100644 --- a/gtk2_ardour/midi_view.cc +++ b/gtk2_ardour/midi_view.cc @@ -807,6 +807,10 @@ MidiView::show_list_editor () void MidiView::create_note_at (timepos_t const & t, double y, Temporal::Beats length, uint32_t state, bool shift_snap) { + if (!_model) { + return; + } + if (length < Temporal::Beats::one_tick()) { return; } @@ -866,6 +870,10 @@ MidiView::display_model (std::shared_ptr model) void MidiView::start_note_diff_command (string name) { + if (!_model) { + return; + } + if (!_note_diff_command) { _editing_context.begin_reversible_command (name); _note_diff_command = _model->new_note_diff_command (name); @@ -923,6 +931,10 @@ MidiView::apply_note_diff (bool as_subcommand, bool was_copy) return; } + if (!_model) { + return; + } + bool add_or_remove = _note_diff_command->adds_or_removes(); if (!was_copy && add_or_remove) { @@ -1022,6 +1034,10 @@ MidiView::find_canvas_sys_ex (MidiModel::SysExPtr s) void MidiView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { + if (!_model) { + return; + } + MidiModel::Notes notes; _model->get_notes (notes, op, val, chan_mask); @@ -1059,6 +1075,8 @@ MidiView::redisplay (bool view_only) void MidiView::model_changed() { + assert (_model); + if (!display_is_enabled()) { return; } @@ -1292,6 +1310,10 @@ MidiView::display_patch_changes_on_channel (uint8_t channel, bool active_channel return; } + if (!_model) { + return; + } + for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) { std::shared_ptr p; @@ -1357,6 +1379,10 @@ MidiView::display_sysexes() return; } + if (!_model) { + return; + } + bool have_periodic_system_messages = false; bool display_periodic_messages = true; @@ -1546,8 +1572,9 @@ MidiView::begin_write() } _active_notes = new Note*[128]; for (unsigned i = 0; i < 128; ++i) { - _active_notes[i] = 0; + _active_notes[i] = nullptr; } + active_note_end = timecnt_t (Temporal::BeatTime); } @@ -1557,10 +1584,11 @@ void MidiView::end_write() { std::cerr << "MV::end write\n"; - delete[] _active_notes; + delete [] _active_notes; _active_notes = nullptr; _marked_for_selection.clear(); _marked_for_velocity.clear(); + active_note_end = timecnt_t (Temporal::BeatTime); } /** Extend active notes to rightmost edge of region (if length is changed) @@ -1572,17 +1600,28 @@ MidiView::extend_active_notes() return; } + extend_active_notes (_midi_region->length()); +} + +void +MidiView::extend_active_notes (timecnt_t const & duration) +{ + if (!_midi_region) { + return; + } + if (!_active_notes) { return; } - for (unsigned i = 0; i < 128; ++i) { + for (int i = 0; i < 128; ++i) { if (_active_notes[i]) { - _active_notes[i]->set_x1 (_editing_context.duration_to_pixels (_midi_region->length())); + _active_notes[i]->set_x1 (_editing_context.duration_to_pixels (duration)); } } } + void MidiView::play_midi_note(std::shared_ptr note) { @@ -1679,13 +1718,17 @@ MidiView::update_sustained (Note* ev) if (note->end_time() == std::numeric_limits::max()) { if (_active_notes && note->note() < 128) { - Note* const old_rect = _active_notes[note->note()]; - if (old_rect) { + Note* const old_nb = _active_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. */ - old_rect->set_x1 (x1); - old_rect->set_outline_all (); + note. Finish the old rectangle here. + */ + old_nb->set_x1 (x1); + old_nb->set_outline_all (); } + /* 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; } /* outline all but right edge */ @@ -1725,27 +1768,21 @@ MidiView::clip_capture_update_sustained (Note *ev, double& x0, double& x1, doubl /* normal note */ -#warning paul make this use the distance captured so far - const Temporal::Beats source_end (4,0); - - if (!_extensible && note->end_time() > source_end) { - note_end = timepos_t (source_end); + timepos_t ane = active_note_end.end(); + if (note_end > ane) { + note_end = ane; } -#warning paul this needs to use the correct part of the tempo map, which will start at SlotArmInfo::start_samples - const samplepos_t note_end_samples = note_end.samples(); - - x1 = std::max(1., _editing_context.sample_to_pixel (note_end_samples)); + x1 = x0 + std::max (1., _editing_context.duration_to_pixels (note_start.distance (note_end))); } else { /* nascent note currently being recorded, noteOff has not yet arrived */ -#warning paul make this use the distance captured so far - x1 = std::max(1., _editing_context.duration_to_pixels (timecnt_t (Temporal::Beats (1, 0)))); + x1 = x0 + std::max (1., _editing_context.duration_to_pixels (note_start.distance (active_note_end.end()))); } - y1 = y0 + std::max(1., floor(note_height()) - 1); + y1 = y0 + std::max (1., floor(note_height()) - 1); } void @@ -1998,6 +2035,12 @@ patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Temporal::Beats void MidiView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const { + if (!_model) { + key.set_bank(0); + key.set_program(0); + return; + } + // The earliest event not before time MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time); @@ -2020,6 +2063,10 @@ MidiView::get_patch_key_at (Temporal::Beats time, uint8_t channel, MIDI::Name::P void MidiView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch) { + if (!_model) { + return; + } + string name = _("alter patch change"); MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); @@ -2042,6 +2089,10 @@ MidiView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKe void MidiView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & new_change) { + if (!_model) { + return; + } + string name = _("alter patch change"); MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name); @@ -2086,6 +2137,10 @@ MidiView::add_patch_change (timecnt_t const & t, Evoral::PatchChangenew_patch_change_diff_command (name); @@ -2103,6 +2158,10 @@ MidiView::add_patch_change (timecnt_t const & t, Evoral::PatchChangenew_patch_change_diff_command (_("move patch change")); c->change_time (pc.patch (), t); _model->apply_diff_command_as_commit (_editing_context.history(), c); @@ -2113,6 +2172,10 @@ MidiView::move_patch_change (PatchChange& pc, Temporal::Beats t) void MidiView::delete_patch_change (PatchChange* pc) { + if (!_model) { + return; + } + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change")); c->remove (pc->patch ()); _model->apply_diff_command_as_commit (_editing_context.history(), c); @@ -2316,6 +2379,10 @@ MidiView::select_notes (list notes, bool allow_audition) void MidiView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) { + if (!_model) { + return; + } + uint8_t low_note = 127; uint8_t high_note = 0; MidiModel::Notes& notes (_model->notes()); @@ -2389,6 +2456,10 @@ MidiView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool ad void MidiView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask) { + if (!_model) { + return; + } + MidiModel::Notes& notes (_model->notes()); _optimization_iterator = _events.begin(); @@ -3118,6 +3189,10 @@ MidiView::finish_resizing (NoteBase* primary, bool at_front, double delta_x, boo return; } + if (!_model) { + return; + } + _note_diff_command = _model->new_note_diff_command (_("resize notes")); /* we are a subcommand, so we don't want to use start_note_diff() which begins a new command */ /* XX why doesn't snap_pixel_to_sample() handle this properly? */ @@ -3860,6 +3935,10 @@ MidiView::cut_copy_clear (Editing::CutCopyOp op) return; } + if (!_model) { + return; + } + bool as_subcommand = false; /* Editor::cut_copy already started an undo operation, @@ -3975,6 +4054,10 @@ MidiView::paste_internal (timepos_t const & pos, unsigned paste_count, float tim return; } + if (!_model) { + return; + } + if (mcb.empty()) { return; } @@ -4048,6 +4131,10 @@ MidiView::goto_next_note (bool add_to_selection) return; } + if (!_model) { + return; + } + bool use_next = false; uint16_t const channel_mask = _midi_track->get_playback_channel_mask(); @@ -4104,6 +4191,10 @@ MidiView::goto_previous_note (bool add_to_selection) return; } + if (!_model) { + return; + } + bool use_next = false; uint16_t const channel_mask = _midi_track->get_playback_channel_mask (); @@ -4388,10 +4479,8 @@ MidiView::set_step_edit_cursor_width (Temporal::Beats beats) } void -MidiView::clip_data_recorded () +MidiView::clip_data_recorded (timecnt_t const & total_duration) { - std::cerr << "cd recorded, mt " << _midi_track << std::endl; - if (!_midi_track) { return; } @@ -4401,6 +4490,14 @@ MidiView::clip_data_recorded () return; } + if (_active_notes) { + for (int n = 0; n < 128; ++n) { + if (_active_notes[n]) { + update_sustained (_active_notes[n]); + } + } + } + std::shared_ptr tb = _midi_track->triggerbox(); assert (tb); @@ -4428,8 +4525,6 @@ MidiView::clip_data_recorded () std::shared_ptr note (new NoteType (ev.channel(), time_beats, std::numeric_limits::max() - time_beats, ev.note(), ev.velocity())); - assert (note->end_time() == std::numeric_limits::max()); - NoteBase* nb = add_note (note, true); nb->item()->set_fill_color (UIConfiguration::instance().color ("recording note")); nb->item()->set_outline_color (UIConfiguration::instance().color ("recording note")); @@ -4454,10 +4549,12 @@ MidiView::clip_data_recorded () _active_notes[note]->set_x1 (_editing_context.sample_to_pixel (timepos_t (ev.time ()).samples())); _active_notes[note]->set_outline_all (); - _active_notes[note] = 0; + _active_notes[note] = nullptr; } } } + + active_note_end = total_duration; } /** Called when a diskstream on our track has received some data. Update the view, if applicable. @@ -4546,7 +4643,7 @@ MidiView::data_recorded (std::weak_ptr w) // 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] = 0; + _active_notes[note] = nullptr; } } @@ -4608,6 +4705,10 @@ MidiView::edit_patch_change (PatchChange* pc) void MidiView::delete_sysex (SysEx* sysex) { + if (!_model) { + return; + } + MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex")); c->remove (sysex->sysex ()); _model->apply_diff_command_as_commit (_editing_context.history(), c); @@ -4666,6 +4767,10 @@ MidiView::show_verbose_cursor (string const & text, double xoffset, double yoffs uint8_t MidiView::get_channel_for_add (MidiModel::TimeType time) const { + if (!_model) { + return 0; + } + /* first, use the user-specified channel in the editor */ if (_editing_context.draw_channel() != Editing::DRAW_CHAN_AUTO) { return _editing_context.draw_channel(); @@ -4692,6 +4797,10 @@ MidiView::get_channel_for_add (MidiModel::TimeType time) const uint8_t MidiView::get_velocity_for_add (MidiModel::TimeType time) const { + if (!_model) { + return 0; + } + if (_editing_context.draw_velocity() != Editing::DRAW_VEL_AUTO) { return _editing_context.draw_velocity(); } @@ -4735,7 +4844,7 @@ ChannelMode MidiView::get_channel_mode () const { if (!_midi_track) { - return AllChannels;; + return AllChannels; } return _midi_track->get_playback_channel_mode(); } diff --git a/gtk2_ardour/midi_view.h b/gtk2_ardour/midi_view.h index f36b534d5f..3dcd95df26 100644 --- a/gtk2_ardour/midi_view.h +++ b/gtk2_ardour/midi_view.h @@ -173,9 +173,10 @@ class MidiView : public virtual sigc::trackable, public LineMerger */ void display_sysexes(); - void begin_write(); - void end_write(); - void extend_active_notes(); + void begin_write (); + void end_write (); + void extend_active_notes (); + void extend_active_notes (Temporal::timecnt_t const &); virtual void begin_drag_edit (std::string const & why); void end_drag_edit (); @@ -340,7 +341,7 @@ class MidiView : public virtual sigc::trackable, public LineMerger EditingContext& editing_context() const { return _editing_context; } MidiViewBackground& midi_context() const { return _midi_context; } - void clip_data_recorded (); + void clip_data_recorded (Temporal::timecnt_t const &); virtual void select_self (bool add) {} virtual void unselect_self () {} @@ -492,6 +493,7 @@ class MidiView : public virtual sigc::trackable, public LineMerger PatchChanges _patch_changes; SysExes _sys_exes; Note** _active_notes; + Temporal::timecnt_t active_note_end; ArdourCanvas::Container* _note_group; ARDOUR::MidiModel::NoteDiffCommand* _note_diff_command; NoteBase* _ghost_note;