diff --git a/ui/b/piano-ctrl.js b/ui/b/piano-ctrl.js index fd71fc68d..b8db73a45 100644 --- a/ui/b/piano-ctrl.js +++ b/ui/b/piano-ctrl.js @@ -41,8 +41,15 @@ let piano_clipboard = "[]"; function quantization (piano_roll) { - const stepping = piano_roll.stepping ? piano_roll.stepping[0] : Util.PPQN; - return Math.min (stepping, Util.PPQN); + if (piano_roll.grid_length == "auto") + { + const stepping = piano_roll.stepping ? piano_roll.stepping[0] : Util.PPQN; + return Math.min (stepping, Util.PPQN); + } + else + { + return piano_roll.grid_stepping; + } } function quantize (piano_roll, tick, nearest = true) @@ -63,8 +70,7 @@ export class PianoCtrl { } quantization () { - const roll = this.piano_roll, stepping = roll.stepping ? roll.stepping[0] : Util.PPQN; - return Math.min (stepping, Util.PPQN); + return quantization (this.piano_roll); } quantize (tick, nearest = true) { diff --git a/ui/b/pianoroll.js b/ui/b/pianoroll.js index 01f89d9c9..6f66d04c3 100644 --- a/ui/b/pianoroll.js +++ b/ui/b/pianoroll.js @@ -95,6 +95,27 @@ const HTML = (t, d) => html` t.notes_canvas = h)} @pointermove=${Util.debounce (t.notes_canvas_pointermove.bind (t))} @pointerdown=${t.notes_canvas_pointerdown} > +
t.pianogridmenu.popup (e)} @mousedown=${e => t.pianogridmenu.popup (e)}> + ${t.grid_label} + t.pianogridmenu = h)} @activate=${e => t.setgrid (e.detail.uri)}> + Tuplet + straight + triplet + quintuplet + septuplet + Grid Quantization + auto + 2/1 + 1/1 + 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128 + +
t.vscrollbar = h)} >
t.vscrollbar_extend = h)} > @@ -132,6 +153,7 @@ class BPianoRoll extends LitComponent { } static properties = { clip: PRIVATE_PROPERTY, ///< The clip with notes to be edited. + grid_label: {} }; constructor() { @@ -144,6 +166,10 @@ class BPianoRoll extends LitComponent { this.cgrid = null; this.menu_btn = null; this.menu_icon = null; + this.grid_label = "auto"; + this.grid_tuplet = 2; + this.grid_length = "auto"; // either "auto" or length (like "1/16") + this.grid_stepping = 0; this.pianotoolmenu = null; this.pianorollmenu = null; this.notes_canvas = null; @@ -341,6 +367,46 @@ class BPianoRoll extends LitComponent { this.notes_canvas_pointermove_zmovedel = App.zmoves_add (this.notes_canvas_pointermove.bind (this)); App.zmove(); // trigger move / hover } + setgrid (uri) + { + if (uri == 2 || uri == 3 || uri == 5 || uri == 7) + this.grid_tuplet = parseInt (uri); + else + this.grid_length = uri; + + this.grid_stepping = Math.round (Util.PPQN * 4 / this.grid_length_factor()); + + let grid_label = this.grid_length; + if (this.grid_tuplet == 3) grid_label += "T"; + if (this.grid_tuplet == 5) grid_label += "Q"; + if (this.grid_tuplet == 7) grid_label += "S"; + + this.setAttribute ("grid_label", grid_label); + } + grid_length_factor() + { + const tuplet_scale = this.grid_tuplet / 2; + switch (this.grid_length) + { + case "2/1": return tuplet_scale * 0.5; + case "1/1": return tuplet_scale; + case "1/2": return tuplet_scale * 2; + case "1/4": return tuplet_scale * 4; + case "1/8": return tuplet_scale * 8; + case "1/16": return tuplet_scale * 16; + case "1/32": return tuplet_scale * 32; + case "1/64": return tuplet_scale * 64; + case "1/128": return tuplet_scale * 128; + default: return 1; // should never happen + } + } + gchecked (g) + { + if (g == this.grid_length || g == this.grid_tuplet) + return '√'; + else + return ' '; + } piano_current_tick (current_clip, current_tick) { if (this.clip != current_clip) return; @@ -780,15 +846,32 @@ function paint_timegrid (canvas, with_labels) // determine stepping granularity let stepping; // [ ticks_per_step, steps_per_mainline, steps_per_midline ] - const mingap = th * 17; - if (denominator_pixels / 16 >= mingap) - stepping = [ TPD / 16, 16, 4 ]; - else if (denominator_pixels / 4 >= mingap) - stepping = [ TPD / 4, 4 * signature[0], 4 ]; - else if (denominator_pixels >= mingap) - stepping = [ TPD, signature[0], 0 ]; - else // just use bars - stepping = [ bar_ticks, 0, 0 ]; + let mingap = th * 17; + + let div; + if (this.grid_length == "auto") + { + div = this.grid_tuplet * 4096; + mingap *= 2; + } + else + div = this.grid_length_factor() / signature[0]; + + while (denominator_pixels / div < mingap) // ensure that grid lines are not too close to each other + div /= 2; + let steps_per_mainline, steps_per_midline; + if (this.grid_tuplet == 2) + { + steps_per_mainline = Math.max (Math.round (div), 1); + steps_per_midline = Math.max (Math.round (div * 4), 1); + } + else + { + steps_per_mainline = Math.max (Math.round (div), this.grid_tuplet); + steps_per_midline = Math.max (Math.round (div * 4), this.grid_tuplet); + } + stepping = [ Math.round (TPD / div), steps_per_midline, steps_per_mainline ]; + this.stepping = stepping; // first 2^x aligned bar tick before/at xposition