View Issue Details

IDProjectCategoryView StatusLast Update
0010169ardourbugspublic2026-03-13 22:35
Reporterpyrotek45 Assigned To 
PrioritynormalSeveritymajorReproducibilityalways
Status newResolutionopen 
PlatformGNUOSLinuxOS Version(any)
Product Version9.0 
Summary0010169: Piano roll doesnt have the same shortcuts as regular midi editing.
Descriptionmany mouse shortcuts that you would do in the midi editing in the playlist do not seem to work in the piano roll. things like middle click to delete. Doing a simple select and ctrl + drag works but its starts with a weird offset. there doesn't seem to be a stop button for the piano roll. pressing play over and over doesn't seem to stop the playback.

the piano roll is an amazing feature, and im so glad its here. finally. but im just wanting it to be perfect. it should behave with all the same shortcuts that im used to on the playlist. currently that is not the case.
Steps To Reproducetry using middle mouse click to delete notes in the piano roll.
try selecting and dragging notes in the piano roll to see the weird visual offset. mine gets offset to the left visually, everytime.
and just as a tweak i think the piano roll should also have a stop button. or make it so the play button works as a stop button if its currently playing.
TagsNo tags attached.

Activities

paul

2026-02-12 00:15

administrator   ~0029862

> i think the piano roll should also have a stop button. or make it so the play button works as a stop button if its currently playing.

for timeline regions or for clips? the play button does entirely different things in each case ... in general (and also, notable in Live and Bitwig) you can't stop a playing clip from its own controls, which I admit I find a bit odd (but we copied it anyway).

pyrotek45

2026-02-12 00:33

reporter   ~0029863

honestly the play button is just odd, but i could live with it. the important thing to me is the keyboard and mouse controls dont seem to work in the lower piano roll, or in a separate window. also, i was mainly talking about time line regions. if i make a midi section, double click to open the piano roll. and press play, it seems i *have* to press the spacebar for it to stop. this is kinda awkward in full-screen mode. but not a real issue if you mainly use the spacebar. but otherwise , once you press the play button , it just goes forever. another thing to note is, in the bottom section ( the piano roll section ), i cant seem to grab ( or find a place to grab ) the section in which to resize it. is that by design or is there a spot im just not finding?

paul

2026-02-12 21:59

administrator   ~0029870

the lack of resize is intentional. we went over this many times with many users during the development of 9.0 and for now have settled on this zone not being resizable. that is not cast in stone, but it's the way it is for now. You can change the editing style to use a separate window, and that is resizable.

paul

2026-02-13 01:28

administrator   ~0029871

ok, middle button function restored to pianoroll now too, as of ac26912bacaebf

pyrotek45

2026-02-26 20:30

reporter   ~0030004

also, there is a weird bug where, in the piano roll, the bottom section or the stand alone window. if you use multi select on some midi note and hold control, then drag, you will see the midi notes that you are dragging are not in the correct spot. i wish there was a way where i could just video call or smth because i feel like i find so many little paper cuts like this all the time. anyways, it would be nice if that bug was patched, currently i cant control and drag midi notes because they just dont work, they appear all the way to the right for some reason, and then when i let go of the button to paste, it paste not where the preview notes were shown.

paul

2026-02-26 20:36

administrator   ~0030005

how are you making the "preview notes" appear?

pyrotek45

2026-02-26 20:46

reporter   ~0030006

select some notes, hold control, then drag.

pyrotek45

2026-03-06 23:22

reporter   ~0030055

any update? i can provide more videos if needed. but really hoping the piano roll becomes stable soon.

paul

2026-03-07 16:36

administrator   ~0030061

there's a year or more's worth of work on the pianoroll, currently underway. no update on this specific bug.

GhostsonAcid

2026-03-07 20:33

reporter   ~0030062

@paul "there's a year or more's worth of work on the pianoroll" ...
0___0
: (

pyrotek45

2026-03-13 22:33

reporter   ~0030104

i used ai to ask if it could fix this bug and it did it in 3 minutes. ill look into how to do a pull request on github because its only a few lines of code. but it fixed the midi move copies to work correctly for me. here is what it did. this was done in the midi_view.cc file.

--
1. Drag-copy notes don't visually align (midi_view.cc)

MidiView::move_copies() doesn't match the coordinate transform logic in MidiView::move_selection(). Three issues:

Uses source_beats_to_absolute_time() unconditionally instead of checking _on_timeline (which is false in the standalone piano roll)
Missing canvas_to_timeline() call when subtracting the note's canvas position — time_to_pixel_unrounded() returns timeline-relative coords but item_to_canvas() returns canvas coords, so they're off by _timeline_origin (the keyboard header width)
Missing timeline_to_canvas() conversion for the note length endpoint
The fix is to make move_copies() use the same pattern as move_selection():

Same pattern applied to Hit handling and the sustained note length (timeline_to_canvas added before
--

now the notes work just fine. it even fixed some other issues like the boundaries not being checked properly in the piano roll. I wasnt able to click notes into the eddges of the piano roll.
--

2. Can't select notes near left edge — StartBoundaryRect::covers() (boundary.cc)

Operator precedence bug — the comparison is inside fabs():

3. Can't select notes near right edge — EndBoundaryRect::covers() (boundary.cc)

The condition (point.x >= self.x0) matches everything to the right of the boundary rect's left edge, which is essentially the entire note canvas:

Bugs 2 and 3 cause the clip start/end boundary handles to swallow mouse events across most of the canvas, preventing note selection near the edges.

--

hopefully these notes help.

pyrotek45

2026-03-13 22:35

reporter   ~0030105

heres the snippets i forgot to add earlier. for the midi copies fix


// === MidiView::move_copies() ===

// BEFORE (broken):

    for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
        NoteBase* n = *i;
        if (n->note()->time() == earliest) {
            to_play.push_back (n->note());
        }

        timepos_t const note_time_qn = _midi_region->source_beats_to_absolute_time (n->note()->time());
        double_t dx = 0;

        if (_midi_context.note_mode() == Sustained) {
            dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn) + dx_qn)
                - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
        } else {
            Hit* hit = dynamic_cast<Hit*>(n);
            if (hit) {
                dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn) + dx_qn)
                    - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
            }
        }

        (*i)->move_event(dx, dy);

        if (_midi_context.note_mode() == Sustained) {
            Note* sus = dynamic_cast<Note*> (*i);
            double const len_dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn) + dx_qn + timecnt_t (n->note()->length()));

            sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
        }
    }

// AFTER (fixed — matches move_selection() logic):

    for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
        NoteBase* n = *i;
        if (n->note()->time() == earliest) {
            to_play.push_back (n->note());
        }

        Temporal::Beats note_time_qn;
        double_t dx = 0;

        if (!_on_timeline) {
            note_time_qn = n->note()->time ();
        } else {
            note_time_qn = _midi_region->source_beats_to_absolute_beats (n->note()->time());
        }

        if (_midi_context.note_mode() == Sustained) {
            dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn + dx_qn.beats()));
            dx -= _editing_context.canvas_to_timeline (n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x);
        } else {
            Hit* hit = dynamic_cast<Hit*>(n);
            if (hit) {
                dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn + dx_qn.beats()));
                dx -= _editing_context.canvas_to_timeline (n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x);
            }
        }

        (*i)->move_event(dx, dy);

        if (_midi_context.note_mode() == Sustained) {
            Note* sus = dynamic_cast<Note*> (*i);
            double len_dx = _editing_context.time_to_pixel_unrounded (timepos_t (note_time_qn) + dx_qn + timecnt_t (n->note()->length()));

            len_dx = _editing_context.timeline_to_canvas (len_dx);
            sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
        }
    }

and how to fix the boundaries bug.

// === StartBoundaryRect::covers() ===

// BEFORE (broken — comparison inside fabs):

bool
StartBoundaryRect::covers (ArdourCanvas::Duple const & point) const
{
    ArdourCanvas::Rect self (item_to_window (_rect));
    const double scale = UIConfiguration::instance().get_ui_scale();

    if ((point.x < self.x0) || (fabs ((point.x - self.x1) < (20. * scale)))) {
        /* before the start, or within 20 (scaled) pixels of the boundary, on the right */
        return true;
    }

// AFTER (fixed):

bool
StartBoundaryRect::covers (ArdourCanvas::Duple const & point) const
{
    ArdourCanvas::Rect self (item_to_window (_rect));
    const double scale = UIConfiguration::instance().get_ui_scale();

    if ((point.x < self.x0) || (fabs (point.x - self.x1) < (20. * scale))) {
        /* before the start, or within 20 (scaled) pixels of the boundary, on either side */
        return true;
    }


// === EndBoundaryRect::covers() ===

// BEFORE (broken — first condition covers entire canvas):

bool
EndBoundaryRect::covers (ArdourCanvas::Duple const & point) const
{
    ArdourCanvas::Rect self (item_to_window (_rect));
    const double scale = UIConfiguration::instance().get_ui_scale();

    if ((point.x >= self.x0) || ((self.x0 - point.x) < (20. * scale))) {
        /* paste the edge, or within 20 (scaled) pixels of the edge */
        return true;
    }

// AFTER (fixed):

bool
EndBoundaryRect::covers (ArdourCanvas::Duple const & point) const
{
    ArdourCanvas::Rect self (item_to_window (_rect));
    const double scale = UIConfiguration::instance().get_ui_scale();

    if ((point.x > self.x1) || (fabs (point.x - self.x0) < (20. * scale))) {
        /* past the end, or within 20 (scaled) pixels of the boundary, on either side */
        return true;
    }

Issue History

Date Modified Username Field Change
2026-02-10 03:05 pyrotek45 New Issue
2026-02-12 00:15 paul Note Added: 0029862
2026-02-12 00:33 pyrotek45 Note Added: 0029863
2026-02-12 21:59 paul Note Added: 0029870
2026-02-13 01:28 paul Note Added: 0029871
2026-02-26 20:30 pyrotek45 Note Added: 0030004
2026-02-26 20:30 pyrotek45 File Added: Screencast_20260226_152940.webm
2026-02-26 20:36 paul Note Added: 0030005
2026-02-26 20:46 pyrotek45 Note Added: 0030006
2026-03-06 23:22 pyrotek45 Note Added: 0030055
2026-03-07 16:36 paul Note Added: 0030061
2026-03-07 20:33 GhostsonAcid Note Added: 0030062
2026-03-13 22:33 pyrotek45 Note Added: 0030104
2026-03-13 22:35 pyrotek45 Note Added: 0030105