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.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