From c9c62d89e5aab6c92605446567471d910767672d Mon Sep 17 00:00:00 2001 From: Chris Ahlstrom Date: Tue, 5 Sep 2023 14:38:15 -0400 Subject: [PATCH] Laying groundwork for data event grab handles. --- TODO | 4 +- doc/latex/tex/references.tex | 8 +- libseq66/include/midi/event.hpp | 25 ++++- libseq66/include/midi/eventlist.hpp | 8 ++ libseq66/include/play/sequence.hpp | 9 +- libseq66/src/midi/event.cpp | 38 +++++++ libseq66/src/midi/eventlist.cpp | 158 ++++++++++++++++++++++++---- libseq66/src/play/sequence.cpp | 22 +++- seq_qt5/include/qseqdata.hpp | 13 ++- seq_qt5/src/qseqdata.cpp | 14 ++- 10 files changed, 271 insertions(+), 28 deletions(-) diff --git a/TODO b/TODO index a545f138..6bffe860 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,13 @@ TO DO for Seq66 0.99.9 Chris Ahlstrom -2019-04-13 to 2023-09-04 +2019-04-13 to 2023-09-05 Misc: - Open t.midi, start playing, stop, then add more notes. They are way longer than the snap value! + - When enabling a MIDI Control/Display port, also enable it + automatically. See issue #116. - Fix and test new record/quantize handling, and verify that MIDI automation of these displays in the pattern editor. - Add automation for these and add some to nanomap.ctrl: diff --git a/doc/latex/tex/references.tex b/doc/latex/tex/references.tex index 0782d3c1..8594b88d 100644 --- a/doc/latex/tex/references.tex +++ b/doc/latex/tex/references.tex @@ -6,7 +6,7 @@ % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 -% \update 2023-06-30 +% \update 2023-09-05 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % @@ -175,6 +175,12 @@ \section{References} \url{https://www.rncbc.org/drupal/node/76}. 2008. + \bibitem{piseq} + sferamusic + \emph{HOW-TO: DIY hybrid Sequencer / MIDI USB Hub ....} + \url{https://www.rncbc.org/drupal/node/7://steemit.com/music/@sferamusic/how-to-diy-hybrid-sequencer-midi-usb-hub-aka-piseq-using-raspberry-pi-and-external-midi-controllers-part-1}. + 2017. + \bibitem{portmidi} PortMedia team. \emph{Platform Independent Library for MIDI I/O.} diff --git a/libseq66/include/midi/event.hpp b/libseq66/include/midi/event.hpp index 9401ca4f..45c70227 100644 --- a/libseq66/include/midi/event.hpp +++ b/libseq66/include/midi/event.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 - * \updates 2023-09-01 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * This module also declares/defines the various constants, status-byte @@ -52,6 +52,8 @@ #include "midi/midibytes.hpp" /* seq66::midibyte alias, etc. */ +#undef SEQ66_STAZED_SELECT_EVENT_HANDLE /* nowhere near ready! */ + /** * Defines the number of data bytes in MIDI status data. * @@ -584,6 +586,11 @@ class event return mask_status(m) == EVENT_CONTROL_CHANGE; } + static bool is_note_on_msg (midibyte m) + { + return m >= EVENT_NOTE_ON || m < EVENT_AFTERTOUCH; + } + /** * Static test for messages that involve notes only: Note On and * Note Off, useful in note-event linking. @@ -1286,6 +1293,16 @@ class event return is_selected() && is_note_on(); } + bool is_controller () const + { + return is_controller_msg(m_status); + } + + bool is_pitchbend () const + { + return is_pitchbend_msg(m_status); + } + bool is_playable () const { return is_playable_msg(m_status) || is_tempo(); @@ -1297,7 +1314,11 @@ class event } bool is_desired (midibyte status, midibyte cc) const; - bool is_desired_ex (midibyte status, midibyte cc) const; /* EXPERIMENT */ +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + bool is_data_in_handle_range (midibyte target) const; + bool is_desired (midibyte status, midibyte cc, midibyte data) const; +#endif + bool is_desired_ex (midibyte status, midibyte cc) const; /** * Some keyboards send Note On with velocity 0 for Note Off, so we diff --git a/libseq66/include/midi/eventlist.hpp b/libseq66/include/midi/eventlist.hpp index 65483bad..08d485df 100644 --- a/libseq66/include/midi/eventlist.hpp +++ b/libseq66/include/midi/eventlist.hpp @@ -434,6 +434,14 @@ class eventlist midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, select action ); +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + int select_event_handle + ( + midipulse tick_s, midipulse tick_f, + midibyte astatus, midibyte cc, + midibyte data + ); +#endif int select_note_events ( midipulse tick_s, int note_h, diff --git a/libseq66/include/play/sequence.hpp b/libseq66/include/play/sequence.hpp index 2981bd52..dc4f63bc 100644 --- a/libseq66/include/play/sequence.hpp +++ b/libseq66/include/play/sequence.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-30 - * \updates 2023-08-31 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * The functions add_list_var() and add_long_list() have been replaced by @@ -1708,6 +1708,13 @@ class sequence ( midibyte status, midibyte cc, bool inverse = false ); +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + int select_event_handle + ( + midipulse tick_s, midipulse tick_f, + midibyte status, midibyte cc, eventlist::select action + ); +#endif /** * New convenience function. What about Aftertouch events? I think we diff --git a/libseq66/src/midi/event.cpp b/libseq66/src/midi/event.cpp index 536842d8..fca348d9 100644 --- a/libseq66/src/midi/event.cpp +++ b/libseq66/src/midi/event.cpp @@ -445,6 +445,44 @@ event::is_desired (midibyte status, midibyte cc) const return result; } +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + +bool +event::is_data_in_handle_range (midibyte target) const +{ + static const midibyte delta = 2; /* seq32 provision */ + static const midibyte max = c_midibyte_value_max - delta; + midibyte datum = is_one_byte() ? d0() : d1() ; + bool result = target >= delta && target <= max; + if (result) + result = datum >= (target - delta) && datum <= (target + delta); + + return result; +} + +bool +event::is_desired (midibyte status, midibyte cc, midibyte data) const +{ + bool result; + if (is_tempo_status(status)) + { + result = is_tempo(); + } + else + { + result = mask_status(status) == mask_status(m_status); + if (result && (event::is_controller_msg(status))) + { + result = m_data[0] == cc; + if (result) + result = is_data_in_handle_range(); /* check d0/d1() */ + } + } + return result; +} + +#endif // defined SEQ66_STAZED_SELECT_EVENT_HANDLE + /** * We should also match tempo events here. But we have to treat them * differently from the matched status events. diff --git a/libseq66/src/midi/eventlist.cpp b/libseq66/src/midi/eventlist.cpp index 6fb0091e..078adb25 100644 --- a/libseq66/src/midi/eventlist.cpp +++ b/libseq66/src/midi/eventlist.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-19 - * \updates 2023-08-20 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * This container now can indicate if certain Meta events (time-signaure or @@ -619,7 +619,7 @@ eventlist::remove_unlinked_notes () * notes (ON or OFF) that are >= m_length. So we must wrap if > m_length * and trim if == m_length. Compare to trim_timestamp(). * - * \param status + * \param astatus * Indicates the type of event to be quantized. * * \param cc @@ -640,7 +640,7 @@ eventlist::remove_unlinked_notes () bool eventlist::quantize_events ( - midibyte status, midibyte cc, + midibyte astatus, midibyte cc, int snap, int divide ) { @@ -653,14 +653,14 @@ eventlist::quantize_events { midibyte d0, d1; er.get_data(d0, d1); - bool match = er.match_status(status); + bool match = er.match_status(astatus); bool canselect = false; if (er.is_marked()) /* ignore marked events */ { er.unmark(); continue; } - if (status == EVENT_CONTROL_CHANGE) + if (astatus == EVENT_CONTROL_CHANGE) canselect = match && d0 == cc; /* correct status and cc */ else canselect = match; /* correct status, any cc */ @@ -1122,7 +1122,7 @@ eventlist::reverse_events (bool inplace, bool relink) * Note that we do not need to call verify_and_link() here, since we are not * altering the timestamps or the note values. * - * \param status + * \param astatus * The kind of event to be randomized. * * \param range @@ -1133,14 +1133,14 @@ eventlist::reverse_events (bool inplace, bool relink) */ bool -eventlist::randomize_selected (midibyte status, int range) +eventlist::randomize_selected (midibyte astatus, int range) { bool result = false; if (range > 0) { for (auto & e : m_events) { - if (e.is_selected_status(status)) + if (e.is_selected_status(astatus)) { if (e.randomize(range)) result = true; @@ -1708,7 +1708,7 @@ eventlist::any_selected_notes () const * given CC value. One exception is tempo events, which are selected * based on the event::is_tempo() test. * - * \param status + * \param astatus * The desired status value to count. Note that tempo is 0x51. * * \param cc @@ -1720,12 +1720,12 @@ eventlist::any_selected_notes () const */ int -eventlist::count_selected_events (midibyte status, midibyte cc) const +eventlist::count_selected_events (midibyte astatus, midibyte cc) const { int result = 0; for (auto & er : m_events) { - if (er.is_selected() && er.is_desired(status, cc)) + if (er.is_selected() && er.is_desired(astatus, cc)) ++result; } return result; @@ -1763,12 +1763,12 @@ eventlist::any_selected_events () const */ bool -eventlist::any_selected_events (midibyte status, midibyte cc) const +eventlist::any_selected_events (midibyte astatus, midibyte cc) const { bool result = false; for (auto & er : m_events) { - if (er.is_selected() && er.is_desired(status, cc)) + if (er.is_selected() && er.is_desired(astatus, cc)) { result = true; break; @@ -1867,7 +1867,7 @@ eventlist::unselect_all () * \param tick_f * The finish time of the selection. * - * \param status + * \param astatus * The desired event in the selection. Now, as a new feature, tempo * events are also selectable, in addition to events selected by this * parameter. Oh, and now time-signature events. @@ -1887,15 +1887,15 @@ int eventlist::select_events ( midipulse tick_s, midipulse tick_f, - midibyte status, midibyte cc, select action + midibyte astatus, midibyte cc, select action ) { int result = 0; for (auto & er : m_events) { - if (event_in_range(er, status, tick_s, tick_f)) + if (event_in_range(er, astatus, tick_s, tick_f)) { - if (er.is_desired(status, cc)) + if (er.is_desired(astatus, cc)) { if (action == select::selecting) { @@ -1942,6 +1942,124 @@ eventlist::select_events return result; } +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + +int +eventlist::select_event_handle +( + midipulse tick_s, midipulse tick_f, + midibyte astatus, midibyte cc, + midibyte data +) +{ + int result = 0; + bool have_selected_note_ons = false; + if (event::is_note_on_msg(astatus)) + { + if (count_selected_event(astatus, cc) > 0) + have_selected_note_ons = true; + } + for (auto & er : m_events) + { + if (event_in_range(er, astatus, tick_s, tick_f)) /* in time-range */ + { + bool isctrl = event::is_controller_msg(astatus); + if (isctrl && er.is_desired(astatus, cc, data)) /* in data-range */ + { + unselect_all(); /* or unmark() */ + er.select(); /* or mark()??? */ + ++result; + break; + } + if (! isctrl) /* chan. pressure? */ + { + bool twobytes = is_two_byte_msg(astatus); + if (twobytes) + { + if (er.is_data_in_handle_range(data)) /* checks d1() */ + { + if (have_selected_note_ons) /* Note Ons only */ + { + if (er.is_selected()) + { + unselect_all(); + er.select(); + if (result > 0) /* have a marked */ + { + for (auto & ev : m_events) + { + if (ev.is_marked()) + { + ev.unmark(); /* clear it */ + break; + } + } + --result; + have_selected_note_ons = false; + } + ++result; /* for selected one */ + break; + } + else + { + if (result == 0) /* mark only first */ + { + er.mark(); /* mark for hold */ + ++result; /* we got one */ + } + continue; /* till sel'ed/done */ + } + } + else /* not Note On */ + { + unselect_all(); + er.select(); + ++result; + break; + } + } + } + else + { + /* + * Not quite right. + */ + + if (er.is_data_in_handle_range(data)) /* checks d0() */ + { + unselect_all(); + er.select(); + ++result; + break; + } + } + } + } + } + + /* + * Is it a Note On that is unselected, but in range? Then use it.... + * The have_selected_note_ons flag will be false if we found a + * selected one in range (?) + */ + + if (result > 0 && have_selected_note_ons) + { + for (auto & er : m_events) + { + if (er.is_marked()) + { + unselect_all(); + er.unmark(); + er.select(); + } + } + } + return result; +} + +#endif // defined SEQ66_STAZED_SELECT_EVENT_HANDLE + /** * This function selects events in range of tick start, note high, tick end, * and note low. @@ -2146,7 +2264,7 @@ eventlist::select_note_events * \param e * Provides the event to be checked. * - * \param status + * \param astatus * Provides the event type that must be matched. However, Set Tempo * events will always be matched. * @@ -2165,11 +2283,11 @@ eventlist::select_note_events bool eventlist::event_in_range ( - const event & e, midibyte status, + const event & e, midibyte astatus, midipulse tick_s, midipulse tick_f ) const { - bool result = e.match_status(status) || e.is_tempo() || + bool result = e.match_status(astatus) || e.is_tempo() || e.is_time_signature(); if (result) diff --git a/libseq66/src/play/sequence.cpp b/libseq66/src/play/sequence.cpp index b479ce40..69f3b28d 100644 --- a/libseq66/src/play/sequence.cpp +++ b/libseq66/src/play/sequence.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 - * \updates 2023-09-01 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * The functionality of this class also includes handling some of the @@ -2258,6 +2258,26 @@ sequence::select_events (midibyte status, midibyte cc, bool inverse) return 0; } +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + +int +sequence::select_event_handle +( + midipulse tick_s, midipulse tick_f, + midibyte status, midibyte cc, eventlist::select action +) +{ + automutex locker(m_mutex); + int result = m_events.select_event_handle + ( + tick_s, tick_f, status, cc, action + ); + set_dirty(); + return result; +} + +#endif // define SEQ66_STAZED_SELECT_EVENT_HANDLE + /** * Selects all events, unconditionally. * diff --git a/seq_qt5/include/qseqdata.hpp b/seq_qt5/include/qseqdata.hpp index 21596ffe..2ae3618f 100644 --- a/seq_qt5/include/qseqdata.hpp +++ b/seq_qt5/include/qseqdata.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 - * \updates 2023-08-31 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and @@ -207,6 +207,17 @@ private slots: bool m_relative_adjust; +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE /* to define in event.hpp */ + + /** + * A feature shamelessly stolen from stazed's Seq32, in progress. + * Supporting drag handles in the near future. + */ + + bool m_drag_handle; + +#endif + /** * This value is true if the mouse is being dragged in the data pane, * which is done in order to change the height and value of each data diff --git a/seq_qt5/src/qseqdata.cpp b/seq_qt5/src/qseqdata.cpp index a4dc0c8d..5f2a89e2 100644 --- a/seq_qt5/src/qseqdata.cpp +++ b/seq_qt5/src/qseqdata.cpp @@ -26,7 +26,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 - * \updates 2023-07-02 + * \updates 2023-09-05 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and @@ -113,6 +113,9 @@ qseqdata::qseqdata m_cc (1), /* modulation */ m_line_adjust (false), m_relative_adjust (false), +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + m_drag_handle (false), +#endif m_dragging (false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); @@ -402,6 +405,15 @@ qseqdata::mousePressEvent (QMouseEvent * event) tick_start = pix_to_tix(mouse_x - 8); // 2; never get it to fire! tick_finish = pix_to_tix(mouse_x + 8); // 2; ditto +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + m_drag_handle = track().select_event_handle + ( + tick_start, tick_finish, m_status, m_cc, + m_dataarea_y - drop_y() + 3 + ) > 0; +#else +#endif + /* * Check if this tick range would select an event. */