diff --git a/README.md b/README.md index 91fb7f45..af5d347b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# README for Seq66 0.99.9 2023-09-04 +# README for Seq66 0.99.9 2023-09-06 __Seq66__: MIDI sequencer/live-looper with a hardware-sampler grid interface; pattern banks, triggers, and playlists for song management; scale and chord @@ -84,6 +84,8 @@ Windows, and using a conventional source tarball. * Version 0.99.9: * Fixed bug: port-mapping Remap and Restart did not work due to timing. + * Related to issue #115: Added ability to select a line in the data + pane and grab a handle to change its value. * Adding more seqroll keystokes (and HTML help). Enabled Esc to exit paint mode if not playing. * Added live-note mapping (needs testing!), refactoring set-record diff --git a/TODO b/TODO index 6bffe860..e39bb2b8 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,13 @@ TO DO for Seq66 0.99.9 Chris Ahlstrom -2019-04-13 to 2023-09-05 +2019-04-13 to 2023-09-06 Misc: - Open t.midi, start playing, stop, then add more notes. They are - way longer than the snap value! + way longer than the snap value! Cannot duplicate on Debian + box! + - Make sure pitch bend is handled correctly in qseqdata. - When enabling a MIDI Control/Display port, also enable it automatically. See issue #116. - Fix and test new record/quantize handling, and verify that diff --git a/libseq66/include/midi/event.hpp b/libseq66/include/midi/event.hpp index 45c70227..be6a3de4 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-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * This module also declares/defines the various constants, status-byte @@ -52,7 +52,7 @@ #include "midi/midibytes.hpp" /* seq66::midibyte alias, etc. */ -#undef SEQ66_STAZED_SELECT_EVENT_HANDLE /* nowhere near ready! */ +#define SEQ66_STAZED_SELECT_EVENT_HANDLE /* EXPERIMENTAL */ /** * Defines the number of data bytes in MIDI status data. diff --git a/libseq66/include/play/sequence.hpp b/libseq66/include/play/sequence.hpp index dc4f63bc..ec1a94ac 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-09-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * The functions add_list_var() and add_long_list() have been replaced by @@ -1702,18 +1702,20 @@ class sequence int select_events ( midipulse tick_s, midipulse tick_f, - midibyte status, midibyte cc, eventlist::select action + midibyte astatus, midibyte cc, eventlist::select action ); int select_events ( - midibyte status, midibyte cc, bool inverse = false + midibyte astatus, 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 + midibyte astatus, midibyte cc, + midibyte data ); + void adjust_event_handle (midibyte astatus, midibyte data); #endif /** @@ -1791,9 +1793,6 @@ class sequence void link_new (); bool edge_fix (); bool remove_unlinked_notes (); -#if defined USE_ADJUST_DATA_HANDLE - void adjust_data_handle (midibyte status, int data); -#endif /** * Resets everything to zero. This function is used when the sequencer diff --git a/libseq66/src/cfg/scales.cpp b/libseq66/src/cfg/scales.cpp index cab8b191..aeda7114 100644 --- a/libseq66/src/cfg/scales.cpp +++ b/libseq66/src/cfg/scales.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2019-10-04 - * \updates 2023-05-04 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * Here is a list of many scale interval patterns if working with @@ -562,7 +562,7 @@ interval_name_ptr (int interval) "P1", "m2", "M2", "m3", "M3", "P4", "TT", "P5", "m6", "M6", "m7", "M7", "P8", "m9", "M9", "0" /* "0" if error */ }; - int index = abs(interval); + int index = std::abs(interval); if (index > c_interval_size) index = c_interval_size; @@ -586,7 +586,7 @@ interval_name_ptr (int interval) bool harmonic_number_valid (int number) { - return abs(number) < c_harmonic_size; + return std::abs(number) < c_harmonic_size; } /** @@ -600,7 +600,7 @@ harmonic_interval_name_ptr (int interval) { "I", "ii", "iii", "IV", "V", "vi", "vii", "I", "0" }; - int index = abs(interval); + int index = std::abs(interval); if (index > c_harmonic_size) index = c_harmonic_size; diff --git a/libseq66/src/midi/event.cpp b/libseq66/src/midi/event.cpp index fca348d9..fa59f638 100644 --- a/libseq66/src/midi/event.cpp +++ b/libseq66/src/midi/event.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 - * \updates 2023-08-25 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * A MIDI event (i.e. "track event") is encapsulated by the seq66::event @@ -475,7 +475,7 @@ event::is_desired (midibyte status, midibyte cc, midibyte data) const { result = m_data[0] == cc; if (result) - result = is_data_in_handle_range(); /* check d0/d1() */ + result = is_data_in_handle_range(data); /* check d0/d1() */ } } return result; diff --git a/libseq66/src/midi/eventlist.cpp b/libseq66/src/midi/eventlist.cpp index 078adb25..9b2e78ba 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-09-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * This container now can indicate if certain Meta events (time-signaure or @@ -1944,6 +1944,13 @@ eventlist::select_events #if defined SEQ66_STAZED_SELECT_EVENT_HANDLE +/** + * Selects the seqdata event handle if in range. + * + * One issue in adjusting data is Pitch events, which have two + * components [d0() and d1()] which must be combined. + */ + int eventlist::select_event_handle ( @@ -1956,7 +1963,7 @@ eventlist::select_event_handle bool have_selected_note_ons = false; if (event::is_note_on_msg(astatus)) { - if (count_selected_event(astatus, cc) > 0) + if (count_selected_events(astatus, cc) > 0) have_selected_note_ons = true; } for (auto & er : m_events) @@ -1973,7 +1980,7 @@ eventlist::select_event_handle } if (! isctrl) /* chan. pressure? */ { - bool twobytes = is_two_byte_msg(astatus); + bool twobytes = event::is_two_byte_msg(astatus); if (twobytes) { if (er.is_data_in_handle_range(data)) /* checks d1() */ diff --git a/libseq66/src/play/sequence.cpp b/libseq66/src/play/sequence.cpp index 69f3b28d..9aeaafab 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-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * The functionality of this class also includes handling some of the @@ -2264,13 +2264,13 @@ int sequence::select_event_handle ( midipulse tick_s, midipulse tick_f, - midibyte status, midibyte cc, eventlist::select action + midibyte astatus, midibyte cc, midibyte data ) { automutex locker(m_mutex); int result = m_events.select_event_handle ( - tick_s, tick_f, status, cc, action + tick_s, tick_f, astatus, cc, data ); set_dirty(); return result; @@ -2529,37 +2529,42 @@ sequence::jitter_notes (int jitr) return result; } -#if defined USE_ADJUST_DATA_HANDLE +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE -/* - * Unused. Has issue(s) to correct before enabling. Such as "what was this - * function meant to do?" +/** + * Used for moving the data value of an event in the seqdata pane up or + * down. + * + * One issue in adjusting data is Pitch events, which have two + * components [d0() and d1()] which must be combined. But d0() is the + * least-significant byte, and d1() is the most-significant byte. + * Since pitch is a 2-byte message, d1() will be adjusted. */ void -sequence::adjust_data_handle (midibyte status, int adata) +sequence::adjust_event_handle (midibyte astatus, midibyte adata) { midibyte data[2]; midibyte datitem; - int dataindex = event::is_two_byte_msg(status) ? 1 : 0 ; + int dataindex = event::is_two_byte_msg(astatus) ? 1 : 0 ; automutex locker(m_mutex); for (auto & e : m_events) { - if (e.is_selected_status(status)) + if (e.is_selected_status(astatus)) { - status = event::mask_status(status); - e.get_data(data[0], data[1]); /* \tricky code */ + astatus = event::mask_status(astatus); + e.get_data(data[0], data[1]); /* \tricky code */ datitem = adata; if (datitem > (c_midibyte_data_max - 1)) datitem = (c_midibyte_data_max - 1); - data[datidx] = datitem; + data[dataindex] = datitem; e.set_data(data[0], data[1]); } } } -#endif // defined USE_ADJUST_DATA_HANDLE +#endif /** * Increments events the match the given status and control values. diff --git a/seq_qt5/include/qseqdata.hpp b/seq_qt5/include/qseqdata.hpp index 2ae3618f..6c9740c4 100644 --- a/seq_qt5/include/qseqdata.hpp +++ b/seq_qt5/include/qseqdata.hpp @@ -28,12 +28,16 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 - * \updates 2023-09-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and * contains vertical lines whose height matches the value of each data event. * The height of the vertical lines is editable via the mouse. + * + * Another EXPERIMENT. Drawing a circular "grab handle" when an event is + * crossed by the mouse or is selected. This is progress on the way to + * improving issue #115. */ #include @@ -124,6 +128,14 @@ class qseqdata final : return m_cc; } +private: + + void flag_dirty (); /* tricky code */ + +#if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE + void set_adjustment (midipulse tick_start, midipulse tick_finish); +#endif + private: // performer::callback overrides virtual bool on_ui_change (seq::number seqno) override; @@ -218,6 +230,20 @@ private slots: #endif + /** + * Keeps track of the X-location of the mouse, in ticks. + */ + + midipulse m_mouse_tick; + + /** + * The precision of event-line detection in ticks. This depends + * upon the PPQN, obviously. This value starts at 2 pixels and is + * corrected to ticks by the pix_to_tix() function. + */ + + midipulse m_handle_delta; + /** * 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 5f2a89e2..85ef0121 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-09-05 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and @@ -41,25 +41,25 @@ #include "qt5_helpers.hpp" /* seq66::qt_timer() */ /* - * The fixes for issue #90 cause a lot of redrawing during mouse movement - * while using a line to edit (for example) velocity. So we do not call + * The fixes for issue #90 cause a lot of redrawing during mouse movement while + * using a line to edit (for example) velocity. But we still call * change_event_data_range() or change_event_data_relative() during mouse - * movement. The orange edit line still appears, but doesn't take effect - * until the mouse button is released. + * movement, if SEQ66_TRACK_DATA_EDITING_MOVEMENTS is defined. * - * This also disables the "relative adjust" feature we copped from Kepler34. - * However, we have never been able to get that feature to turn on. In - * Kepler34, all it seems to do is allow modifying a single event by moving - * the mouse up and down. Obviously, our implementation is buggy, but does it - * matter? We will still see if we can get it to work at some point. + * We also disabled the "relative adjust" feature we copped from Kepler34. + * However, we have never been able to get that feature to turn on. In Kepler34, + * all it seems to do is allow modifying a single event by moving the mouse up + * and down. Obviously, our implementation is buggy, but does it matter? We will + * still see if we can get it to work at some point. * - * To revert to the old behavior, define this macro. We leave the old behavior - * in since we figured out that constant title-dirtying in - * qsmainwnd::enable_save() was causing continuous flickering during editing - * mouse movements. + * To revert to the old behavior, define this macro. We leave the old behavior in + * since we figured out that constant title-dirtying in qsmainwnd :: + * enable_save() was causing continuous flickering during editing mouse + * movements. */ #define SEQ66_TRACK_DATA_EDITING_MOVEMENTS +#undef SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE /* * Do not document a namespace; it breaks Doxygen. @@ -82,9 +82,12 @@ static const int sc_dataarea_y = 128; * Tweaks. */ -static const int s_x_data_fix = -6; // 2; /* adjusts x-value for the events */ +static const int s_x_data_fix = -6; /* adjusts x-value for the events */ static const int s_key_padding = 8; /* adjusts x for keyboard padding */ static const int s_circle_d = 6; /* diameter of tempo/prog. dots */ +static const int s_handle_d = 8; /* diameter of grab handle */ +static const int s_handle_r = 4; /* radius of grab handle */ +static const int s_handle_delta = 2; /* delta of mouse-pixels */ /** * Principal constructor. @@ -110,21 +113,24 @@ qseqdata::qseqdata m_is_time_signature (false), m_is_program_change (false), m_status (EVENT_NOTE_ON), - m_cc (1), /* modulation */ + m_cc (1), /* modulation */ m_line_adjust (false), m_relative_adjust (false), -#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE /* in event.hpp */ m_drag_handle (false), #endif + m_mouse_tick (-1), + m_handle_delta (pix_to_tix(s_handle_delta)), m_dragging (false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - m_font.setPointSize(8); /* was 6 points */ + setMouseTracking(true); /* no click needed */ + m_font.setPointSize(8); m_font.setBold(true); #if defined SEQ66_PLATFORM_DEBUG_TMI - set_initialized(); /* helps debug */ + set_initialized(); #endif - cb_perf().enregister(this); /* notification */ + cb_perf().enregister(this); /* set for notification */ m_timer = qt_timer(this, "qseqdata", 2, SLOT(conditional_update())); } @@ -271,6 +277,22 @@ qseqdata::paintEvent (QPaintEvent * qpep) painter.drawLine(event_x, event_height, event_x, height()); snprintf(digits, sizeof digits, "%3d", d1); + bool its_close = false; + if (! selected && m_mouse_tick >= 0) + { + midipulse delta = std::labs(tick - m_mouse_tick); + if (delta <= m_handle_delta) + its_close = true; + } + if (selected || its_close) + { + painter.drawEllipse + ( + event_x - s_handle_r, event_height - s_handle_r, + s_handle_d, s_handle_d + ); + } + QString val = digits; pen.setColor(fore_color()); painter.setPen(pen); @@ -390,53 +412,78 @@ qseqdata::mousePressEvent (QMouseEvent * event) { int mouse_x = event->x() - c_keyboard_padding_x + scroll_offset_x(); int mouse_y = event->y(); + midipulse tick_start = pix_to_tix(mouse_x - 8); /* 2; never fires! */ + midipulse tick_finish = pix_to_tix(mouse_x + 8); /* 2; ditto */ + bool isctrl = bool(event->modifiers() & Qt::ControlModifier); + drop_x(mouse_x); /* set values for line */ + drop_y(mouse_y); #if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE - - /* - * If near an event (4px), do relative adjustment. Do we need to - * push-undo here? Not sure, won't change for now. Disable for now - * because it either doesn't work or causes velocity changes to not - * occur. Also, it's an issue with tracks densely packed with note - * events. - */ - - midipulse tick_start, tick_finish; - tick_start = pix_to_tix(mouse_x - 8); // 2; never get it to fire! - tick_finish = pix_to_tix(mouse_x + 8); // 2; ditto + bool would_select = set_adjustment(tick_start, tick_finish); + if (! would_select) + { + m_dragging = m_line_adjust = true; /* set ev values under line */ + } +#endif #if defined SEQ66_STAZED_SELECT_EVENT_HANDLE - m_drag_handle = track().select_event_handle + + int count = track().select_event_handle /* check for handle grab */ ( tick_start, tick_finish, m_status, m_cc, - m_dataarea_y - drop_y() + 3 - ) > 0; -#else + m_dataarea_y - drop_y() + 3 /* m_dataarea_y == 128 */ + ); + m_drag_handle = count > 0; + if (m_drag_handle) + { + track().push_undo(); + m_dragging = false; + } + else + { + +#endif + + int currcount = track().get_num_selected_events(m_status, m_cc); + if (currcount > 0 && ! isctrl) + track().unselect(); /* unselect all of selected */ + + int evcount = track().select_events /* select clicked event */ + ( + tick_start, tick_finish, m_status, m_cc, + eventlist::select::select_one /* adds to the selections */ + ); + bool selected = evcount > 0; + if (selected) + { + flag_dirty(); + } + else + { + track().unselect(); /* unselect all of selected */ + m_dragging = m_line_adjust = true; + flag_dirty(); + } + +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + } #endif /* * Check if this tick range would select an event. */ - bool would_select = track().select_events - ( - tick_start, tick_finish, m_status, m_cc, - eventlist::select::would_select - ); - if (would_select) - m_relative_adjust = true; /* printf("rel adjust\n"); */ - else - -#endif - m_line_adjust = true; /* set ev values under line */ track().push_undo(); - drop_x(mouse_x); /* set values for line */ - drop_y(mouse_y); +// drop_x(mouse_x); /* set values for line */ +// drop_y(mouse_y); old_rect().clear(); /* reset dirty redraw box */ - m_dragging = true; /* may be dragging now */ } +/** + * Convert x,y to ticks, then set events in range + */ + void qseqdata::mouseReleaseEvent (QMouseEvent * event) { @@ -450,10 +497,6 @@ qseqdata::mouseReleaseEvent (QMouseEvent * event) swap_y(); } - /* - * Convert x,y to ticks, then set events in range - */ - midipulse tick_s = pix_to_tix(drop_x()); midipulse tick_f = pix_to_tix(current_x()); int ds = byte_value(m_dataarea_y, m_dataarea_y - drop_y()); @@ -466,19 +509,40 @@ qseqdata::mouseReleaseEvent (QMouseEvent * event) if (ok) set_dirty(); } +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + else if (m_drag_handle) + { + track().unselect(); + track().set_dirty(); + } + m_drag_handle = m_relative_adjust = m_dragging = false; +#else m_relative_adjust = m_dragging = false; +#endif + + /* + * Should we call update()? + * Should we call track().push_undo(true)? + */ } void qseqdata::mouseMoveEvent (QMouseEvent * event) { - if (! m_dragging) - return; - - current_x(int(event->x()) - c_keyboard_padding_x); - current_y(int(event->y())); #if defined SEQ66_TRACK_DATA_EDITING_MOVEMENTS midipulse tick_s, tick_f; +#endif + current_x(int(event->x()) - c_keyboard_padding_x); + current_y(int(event->y())); // deduct from m_dataarea_y? + m_mouse_tick = -1; + +#if defined SEQ66_STAZED_SELECT_EVENT_HANDLE + if (m_drag_handle) + { + track().adjust_event_handle(m_status, m_dataarea_y - current_y()); + update(); + } + else #endif if (m_line_adjust) { @@ -510,14 +574,15 @@ qseqdata::mouseMoveEvent (QMouseEvent * event) if (ok) { (void) mark_modified(); - set_dirty(); /* just a flag setting */ + set_dirty(); /* just a flag setting */ } #else (void) mark_modified(); set_dirty(); #endif } - else if (m_relative_adjust) +#if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE + else if (m_relative_adjust) /* currently DISABLED */ { #if defined SEQ66_TRACK_DATA_EDITING_MOVEMENTS int adjy = byte_value(m_dataarea_y, drop_y() - current_y()); @@ -530,7 +595,7 @@ qseqdata::mouseMoveEvent (QMouseEvent * event) if (ok) { (void) mark_modified(); - set_dirty(); /* just a flag setting */ + set_dirty(); /* just a flag setting */ } #else (void) mark_modified(); @@ -543,8 +608,51 @@ qseqdata::mouseMoveEvent (QMouseEvent * event) drop_y(current_y()); } +#endif + else if (! m_dragging) + { + m_mouse_tick = pix_to_tix(current_x()); + update(); /* force a paintEvent() */ + } +} + +#if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE + +/** + * If near an event (8 px), do relative adjustment. Do we need to + * push-undo here? Not sure, won't change for now. Disable for now + * because it either doesn't work or causes velocity changes to not + * occur. Also, it's an issue with tracks densely packed with note + * events. + * + * The sequence::select_events() call checks if this tick range would + * select an event. + * + * \return + * Returns true if the mouse location would select events. + */ + +bool +qseqdata::set_adjustment (midipulse tick_start, midipulse tick_finish) +{ + int count = track().select_events + ( + tick_start, tick_finish, m_status, m_cc, + eventlist::select::would_select + ); + bool result = count > 0; + if (result) + { + m_dragging = m_relative_adjust = true; + +// drop_x(mouse_x); /* set values for line */ +// drop_y(mouse_y); +// old_rect().clear(); /* reset dirty redraw box */ + } } +#endif // defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE + void qseqdata::set_data_type (midibyte status, midibyte control) { @@ -583,6 +691,17 @@ qseqdata::set_data_type (midibyte status, midibyte control) update(); } +/** + * Similar to qstriggereditor::flag_dirty(). + */ + +void +qseqdata::flag_dirty () +{ + track().set_dirty(); + frame64()->set_dirty(); +} + } // namespace seq66 /* diff --git a/seq_qt5/src/qseqeditframe64.cpp b/seq_qt5/src/qseqeditframe64.cpp index 8b3fe7ae..f20d7877 100644 --- a/seq_qt5/src/qseqeditframe64.cpp +++ b/seq_qt5/src/qseqeditframe64.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-15 - * \updates 2023-09-04 + * \updates 2023-09-06 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and @@ -1601,7 +1601,7 @@ qseqeditframe64::log_timesig (bool islogbutton) bool found = track().detect_time_signature(tstamp, n, d, tick); if (found) { - found = labs(tick - tstamp) < (track().snap() / 2); + found = std::labs(tick - tstamp) < (track().snap() / 2); if (found) (void) track().delete_time_signature(tstamp); }