diff --git a/js/widgets/rhythmruler.js b/js/widgets/rhythmruler.js
index a85cae79b1..5078dbfd13 100644
--- a/js/widgets/rhythmruler.js
+++ b/js/widgets/rhythmruler.js
@@ -9,13 +9,23 @@
// License along with this library; if not, write to the Free Software
// Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
-// This widget enable us to create a rhythms which can be imported
-// into the pitch-time matrix and hence used to create chunks of
-// notes.
-
-// rulerButtonsDiv is for the widget buttons
-// rulerTableDiv is for the drum buttons (fixed first col) and the ruler cells
-
+/*
+ global TONEBPM, Singer, logo, _, delayExecution, docById, calcNoteValueToDisplay, platformColor,
+ beginnerMode, last, EIGHTHNOTEWIDTH, nearestBeat, rationalToFraction, DRUMNAMES, VOICENAMES,
+ EFFECTSNAMES, wheelnav, slicePath
+ */
+
+/* exported RhythmRuler */
+
+/**
+ * @abstract
+ * This widget enable us to create a rhythms which can be imported into the pitch-time matrix and
+ * hence used to create chunks of notes.
+ *
+ * @description
+ * - `rulerButtonsDiv` is for the widget buttons
+ * - `rulerTableDiv` is for the drum buttons (fixed first col) and the ruler cells
+ */
class RhythmRuler {
static ROWHEIGHT = 130;
static RULERHEIGHT = 70;
@@ -23,761 +33,1115 @@ class RhythmRuler {
static ICONSIZE = 32;
static DEL = 46;
- _noteWidth(noteValue) {
- return Math.floor(EIGHTHNOTEWIDTH * (8 / Math.abs(noteValue)) * 4);
- }
+ constructor() {
+ // console.debug("init RhythmRuler");
- _calculateZebraStripes(rulerno) {
- let ruler = this._rulers[rulerno];
- let evenColor;
- if (this._rulerSelected % 2 === 0) {
- evenColor = platformColor.selectorBackground;
- } else {
- evenColor = platformColor.selectorSelected;
- }
+ this._bpmFactor = (1000 * TONEBPM) / Singer.masterBPM;
- for (let i = 0; i < ruler.cells.length; i++) {
- let newCell = ruler.cells[i];
- newCell.style.border = "2px solid lightgrey";
- newCell.style.borderRadius = "10px";
- if (evenColor === platformColor.selectorBackground) {
- if (i % 2 === 0) {
- newCell.style.backgroundColor =
- platformColor.selectorBackground;
- } else {
- newCell.style.backgroundColor =
- platformColor.selectorSelected;
- }
- }
+ this._playing = false;
+ this._playingOne = false;
+ this._playingAll = false;
+ this._rulerPlaying = -1;
+ this._startingTime = null;
+ this._expanded = false;
- if (evenColor === platformColor.selectorSelected) {
- if (i % 2 === 0) {
- newCell.style.backgroundColor =
- platformColor.selectorSelected;
- } else {
- newCell.style.backgroundColor =
- platformColor.selectorBackground;
- }
- }
- }
- }
+ // There is one ruler per drum.
+ this.Drums = [];
+ // Rulers, one per drum, contain the subdivisions defined by rhythm blocks.
+ this.Rulers = [];
+ // Save the history of divisions so as to be able to restore them.
+ this._dissectHistory = [];
+ this._undoList = [];
- _dissectRuler(event, ruler) {
- let cell = event.target;
- if (cell === null) {
- return;
- }
+ this._playing = false;
+ this._playingOne = false;
+ this._playingAll = false;
+ this._cellCounter = 0;
- if (this._tapMode && this._tapTimes.length > 0) {
- const d = new Date();
- this._tapTimes.push(d.getTime());
- return;
- }
+ // Keep a elapsed time for each ruler to maintain sync.
+ this._elapsedTimes = [];
+ // Starting time from which we measure for sync.
+ this._startingTime = null;
- const cellParent = cell.parentNode;
- if (cellParent === null) {
- return;
- }
+ this._offsets = [];
+ this._rulerSelected = 0;
+ this._rulerPlaying = -1;
- this._rulerSelected = ruler;
- if (this._rulerSelected == undefined) {
- return;
- }
+ this._tapMode = false;
+ this._tapTimes = [];
+ this._tapCell = null;
+ this._tapEndTime = null;
- if (this._playing) {
- console.warn("You cannot dissect while widget is playing.");
- return;
- } else if (this._tapMode) {
- // Tap a rhythm by clicking in a cell.
- if (this._tapCell === null) {
- let noteValues = this.Rulers[this._rulerSelected][0];
- this._tapCell = event.target;
- if (noteValues[this._tapCell.cellIndex] < 0) {
- // Don't allow tapping in rests.
- this._tapCell = null;
- this._tapMode = false;
- this._tapTimes = [];
- this._tapEndTime = null;
- this._tapButton.innerHTML =
- '';
- return;
- }
+ this._longPressStartTime = null;
+ this._inLongPress = false;
- this._tapTimes = [];
+ this._mouseDownCell = null;
+ this._mouseUpCell = null;
- // Play a count off before starting tapping.
- const interval =
- this._bpmFactor /
- Math.abs(noteValues[this._tapCell.cellIndex]);
+ this._wheel = null;
- let drum;
- if (this.Drums[this._rulerSelected] === null) {
- drum = "snare drum";
- } else {
- let drumBlockNo = logo.blocks.blockList[
- this.Drums[this._rulerSelected]
- ].connections[1];
- drum = logo.blocks.blockList[drumBlockNo].value;
- }
+ // Element references
+ this._dissectNumber = null;
+ this._progressBar = null;
+ this._rulers = [];
- // FIXME: Should be based on meter
- for (let i = 0; i < 4; i++) {
- setTimeout(() => {
- logo.synth.trigger(
- 0, "C4", Singer.defaultBPMFactor / 16, drum, null, null
- );
- }, (interval * i) / 4);
- }
+ // If there are no drums, add one.
+ if (this.Drums.length === 0) {
+ this.Drums.push(null);
+ this.Rulers.push([[1], []]);
+ }
- setTimeout(() => {
- this.__startTapping(noteValues, interval, event);
- }, interval);
- }
- } else {
- let inputNum = this._dissectNumber.value;
- if (inputNum === "" || isNaN(inputNum)) {
- inputNum = 2;
- } else {
- inputNum = Math.abs(Math.floor(inputNum));
- }
+ this._elapsedTimes = [];
+ this._offsets = [];
+ for (let i = 0; i < this.Rulers.length; i++) {
+ this._elapsedTimes.push(0);
+ this._offsets.push(0);
+ }
- this._dissectNumber.value = inputNum;
+ this._cellScale = 1.0;
- this._rulerSelected = cell.parentNode.getAttribute("data-row");
- this.__dissectByNumber(cell, inputNum, true);
- }
+ const widgetWindow = window.widgetWindows.windowFor(this, "rhythm maker");
+ this.widgetWindow = widgetWindow;
+ widgetWindow.clear();
+ widgetWindow.show();
- // this._piemenuRuler(this._rulerSelected);
+ // For the button callbacks
- //Save dissect history everytime user dissects ruler
- this.saveDissectHistory();
- }
+ widgetWindow.onclose = () => {
+ // If the piemenu was open, close it.
+ // docById('wheelDiv').style.display = 'none';
+ // docById('contextWheelDiv').style.display = 'none';
- __startTapping(noteValues, interval, event) {
- const d = new Date();
- this._tapTimes = [d.getTime()];
- this._tapEndTime = this._tapTimes[0] + interval;
+ // Save the new dissect history.
+ const dissectHistory = [];
+ const drums = [];
+ for (let i = 0; i < this.Rulers.length; i++) {
+ if (this.Drums[i] === null) {
+ continue;
+ }
- // Set a timeout to end tapping
- setTimeout(() => {
- this.__endTapping(event);
- }, interval);
+ const history = [];
+ for (let j = 0; j < this.Rulers[i][1].length; j++) {
+ history.push(this.Rulers[i][1][j]);
+ }
- // Display a progress bar.
- const __move = (tick, stepSize) => {
- let width = 1;
-
- const frame = () => {
- if (width >= 100) {
- clearInterval(id);
- } else {
- width += stepSize;
- this._progressBar.style.width = width + "%";
+ this._dissectNumber.classList.add("hasKeyboard");
+ dissectHistory.push([history, this.Drums[i]]);
+ drums.push(this.Drums[i]);
+ }
+
+ // Look for any old entries that we may have missed.
+ for (let i = 0; i < this._dissectHistory.length; i++) {
+ const drum = this._dissectHistory[i][1];
+ if (drums.indexOf(drum) === -1) {
+ const history = JSON.parse(JSON.stringify(this._dissectHistory[i][0]));
+ dissectHistory.push([history, drum]);
}
}
-
- const id = setInterval(frame, tick);
- }
- this._tapCell.innerHTML = "
";
- this._progressBar = this._tapCell.querySelector(".progressBar");
- // Progress once per 8th note.
- __move(interval / 8, 100 / 8);
- }
+ this._dissectHistory = JSON.parse(JSON.stringify(dissectHistory));
- __endTapping(event) {
- let cell = event.target;
- if (cell.parentNode === null) {
- console.debug("Null parent node in endTapping");
- return;
- }
+ this._playing = false;
+ this._playingOne = false;
+ this._playingAll = false;
+ logo.hideMsgs();
- this._rulerSelected = cell.parentNode.getAttribute("data-row");
- if (this._progressBar) this._progressBar.remove();
- this._tapCell.innerHTML = "";
+ this.widgetWindow.destroy();
+ };
- const d = new Date();
- this._tapTimes.push(d.getTime());
+ this._playAllCell = widgetWindow.addButton(
+ "play-button.svg",
+ RhythmRuler.ICONSIZE,
+ _("Play all")
+ );
+ this._playAllCell.onclick = () => {
+ if (this._playing) {
+ this.__pause();
+ } else if (!this._playingAll) {
+ this.__resume();
+ }
+ };
- this._tapMode = false;
- if (
- typeof this._rulerSelected === "string" ||
- typeof this._rulerSelected === "number"
- ) {
- let noteValues = this.Rulers[this._rulerSelected][0];
+ this._save_lock = false;
+ widgetWindow.addButton(
+ "export-chunk.svg",
+ RhythmRuler.ICONSIZE,
+ _("Save rhythms")
+ ).onclick = async () => {
+ // that._save(0);
+ // Debounce button
+ if (!this._get_save_lock()) {
+ this._save_lock = true;
- if (last(this._tapTimes) > this._tapEndTime) {
- this._tapTimes[this._tapTimes.length - 1] = this._tapEndTime;
- }
+ // Save a merged version of the rulers.
+ this._saveTupletsMerged(this._mergeRulers());
- // convert times into cells here.
- let inputNum = this._dissectNumber.value;
- if (inputNum === "" || isNaN(inputNum)) {
- inputNum = 2;
- } else {
- inputNum = Math.abs(Math.floor(inputNum));
+ // Rather than each ruler individually.
+ // that._saveTuplets(0);
+ await delayExecution(1000);
+ this._save_lock = false;
}
+ };
- // Minimum beat is tied to the input number
- let minimumBeat;
- switch (inputNum) {
- case 2:
- minimumBeat = 16;
- break;
- case 3:
- minimumBeat = 27;
- break;
- case 4:
- minimumBeat = 32;
- break;
- case 5:
- minimumBeat = 25;
- break;
- case 6:
- minimumBeat = 36;
- break;
- case 7:
- minimumBeat = 14;
- break;
- case 8:
- minimumBeat = 64;
- break;
- default:
- minimumBeat = 16;
- break;
- }
-
- let newNoteValues = [];
- let sum = 0;
- let obj;
- for (let i = 1; i < this._tapTimes.length; i++) {
- let dtime = this._tapTimes[i] - this._tapTimes[i - 1];
- if (i < this._tapTimes.length - 1) {
- obj = nearestBeat(
- (100 * dtime) / this._bpmFactor,
- minimumBeat
- );
- if (obj[0] === 0) {
- obj[0] = 1;
- obj[1] = obj[1] / 2;
- }
-
- if (sum + obj[0] / obj[1] < 1) {
- sum += obj[0] / obj[1];
- newNoteValues.push(obj[1] / obj[0]);
- }
- } else {
- // Since the fractional value is noisy,
- // ensure that the final beat make the
- // total add up to the proper note value.
- obj = rationalToFraction(
- 1 / noteValues[this._tapCell.cellIndex] - sum
- );
- newNoteValues.push(obj[1] / obj[0]);
- }
+ widgetWindow.addButton(
+ "export-drums.svg",
+ RhythmRuler.ICONSIZE,
+ _("Save drum machine")
+ ).onclick = async () => {
+ // Debounce button
+ if (!this._get_save_lock()) {
+ this._save_lock = true;
+ this._saveMachine(0);
+ await delayExecution(1000);
+ this._save_lock = false;
}
+ };
- this.__divideFromList(this._tapCell, newNoteValues, true);
- }
-
- this._tapTimes = [];
- this._tapCell = null;
- this._tapEndTime = null;
- this._tapButton.innerHTML =
- '';
- }
-
- __addCellEventHandlers(cell, cellWidth, noteValue) {
+ // An input for setting the dissect number
+ this._dissectNumber = widgetWindow.addInputButton("2");
- const __mouseOverHandler = (event) => {
- let cell = event.target;
- if (cell === null || cell.parentNode === null) {
- return;
- }
+ this._dissectNumber.onfocus = () => {
+ // that._piemenuNumber(['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'], numberInput.value);
+ };
- this._rulerSelected = cell.parentNode.getAttribute("data-row");
- let noteValues = this.Rulers[this._rulerSelected][0];
- let noteValue = noteValues[cell.cellIndex];
- let obj;
- if (noteValue < 0) {
- obj = rationalToFraction(
- Math.abs(Math.abs(-1 / noteValue))
- );
- cell.innerHTML =
- calcNoteValueToDisplay(obj[1], obj[0], this._cellScale) +
- " " +
- _("silence");
- } else {
- obj = rationalToFraction(Math.abs(Math.abs(1 / noteValue)));
- cell.innerHTML = calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
+ this._dissectNumber.onkeydown = (event) => {
+ // 46 number is for the delete keypress
+ if (event.keyCode === 46) {
+ this._dissectNumber.value = this._dissectNumber.value.substring(
+ 0,
+ this._dissectNumber.value.length - 1
);
}
};
- const __mouseOutHandler = (event) => {
- let cell = event.target;
- cell.innerHTML = "";
+ this._dissectNumber.oninput = () => {
+ // Put a limit on the size (2 <--> 128).
+ this._dissectNumber.onmouseout = () => {
+ this._dissectNumber.value = Math.max(this._dissectNumber.value, 2);
+ };
+
+ this._dissectNumber.value = Math.max(Math.min(this._dissectNumber.value, 128), 2);
};
- const __mouseDownHandler = (event) => {
- let cell = event.target;
- this._mouseDownCell = cell;
+ widgetWindow.addButton(
+ "restore-button.svg",
+ RhythmRuler.ICONSIZE,
+ _("Undo")
+ ).onclick = () => {
+ this._undo();
+ };
- const d = new Date();
- this._longPressStartTime = d.getTime();
- this._inLongPress = false;
+ //.TRANS: user can tap out a rhythm by clicking on a ruler.
+ this._tapButton = widgetWindow.addButton(
+ "tap-button.svg",
+ RhythmRuler.ICONSIZE,
+ _("Tap a rhythm")
+ );
+ this._tapButton.onclick = () => {
+ this._tap();
+ };
- this._longPressBeep = setTimeout(() => {
- // Removing audio feedback on long press since it
- // occasionally confuses tone.js during rapid clicking
- // in the widget.
+ //.TRANS: clear all subdivisions from the ruler.
+ widgetWindow.addButton(
+ "erase-button.svg",
+ RhythmRuler.ICONSIZE,
+ _("Clear")
+ ).onclick = () => {
+ this._clear();
+ };
- // that._logo.synth.trigger(0, 'C4', 1 / 32, 'chime', null, null);
+ // We use an outer div to scroll vertically and an inner div to
+ // scroll horizontally.
+ const rhythmRulerTable = document.createElement("table");
+ widgetWindow.getWidgetBody().append(rhythmRulerTable);
- let cell = this._mouseDownCell;
- if (cell !== null && cell.parentNode !== null) {
- this._rulerSelected = cell.parentNode.getAttribute(
- "data-row"
- );
- let noteValues = this.Rulers[this._rulerSelected][0];
- let noteValue = noteValues[cell.cellIndex];
- cell.style.backgroundColor =
- platformColor.selectorBackground;
+ let wMax = 0;
+ // Each row in the ruler table contains a play button in the
+ // first column and a ruler table in the second column.
+ for (let i = 0; i < this.Rulers.length; i++) {
+ const rhythmRulerTableRow = rhythmRulerTable.insertRow();
+
+ if (beginnerMode) {
+ let w = 0;
+ for (let r = 0; r < this.Rulers[i][0].length; r++) {
+ w += 580 / this.Rulers[i][0][r];
}
- }, 1500);
- };
- const __mouseUpHandler = (event) => {
- clearTimeout(this._longPressBeep);
- let cell = event.target;
- this._mouseUpCell = cell;
- if (this._mouseDownCell !== this._mouseUpCell) {
- this._tieRuler(event, cell.parentNode.getAttribute("data-row"));
- } else if (this._longPressStartTime !== null && !this._tapMode) {
- const d = new Date();
- let elapseTime = d.getTime() - this._longPressStartTime;
- if (elapseTime > 1500) {
- this._inLongPress = true;
- this.__toggleRestState(this, true);
+ if (w > wMax) {
+ rhythmRulerTable.style.width = w + "px";
+ wMax = w;
}
+ } else {
+ const drumcell = rhythmRulerTableRow.insertCell();
+ drumcell.innerHTML =
+ '';
+ drumcell.className = "headcol"; // Position fixed when scrolling horizontally
+
+ drumcell.onclick = ((id) => {
+ return () => {
+ if (this._playing) {
+ if (this._rulerPlaying === id) {
+ this.innerHTML =
+ '';
+ this._playing = false;
+ this._playingOne = false;
+ this._playingAll = false;
+ this._rulerPlaying = -1;
+ this._startingTime = null;
+ this._elapsedTimes[id] = 0;
+ this._offsets[id] = 0;
+ setTimeout(this._calculateZebraStripes(id), 1000);
+ }
+ } else {
+ if (this._playingOne === false) {
+ this._rulerSelected = id;
+ logo.turtleDelay = 0;
+ this._playing = true;
+ this._playingOne = true;
+ this._playingAll = false;
+ this._cellCounter = 0;
+ this._startingTime = null;
+ this._rulerPlaying = id;
+ this.innerHTML =
+ '';
+ this._elapsedTimes[id] = 0;
+ this._offsets[id] = 0;
+ this._playOne();
+ }
+ }
+ };
+ })(i);
}
- this._mouseDownCell = null;
- this._mouseUpCell = null;
- this._longPressStartTime = null;
- };
+ const rulerCell = rhythmRulerTableRow.insertCell();
+ // Create individual rulers as tables.
+ rulerCell.innerHTML = '';
- const __clickHandler = (event) => {
- if (event == undefined) return;
- if (!this.__getLongPressStatus()) {
- let cell = event.target;
- if (cell !== null && cell.parentNode !== null) {
- this._dissectRuler(
- event,
- cell.parentNode.getAttribute("data-row")
- );
+ const rulerCellTable = docById("rulerCellTable" + i);
+ rulerCellTable.style.textAlign = "center";
+ rulerCellTable.style.border = "0px";
+ rulerCellTable.style.borderCollapse = "collapse";
+ rulerCellTable.cellSpacing = "0px";
+ rulerCellTable.cellPadding = "0px";
+ const rulerRow = rulerCellTable.insertRow();
+ this._rulers[i] = rulerRow;
+ rulerRow.setAttribute("data-row", i);
+
+ for (let j = 0; j < this.Rulers[i][0].length; j++) {
+ const noteValue = this.Rulers[i][0][j];
+ const rulerSubCell = rulerRow.insertCell(-1);
+ rulerSubCell.innerHTML = calcNoteValueToDisplay(noteValue, 1, this._cellScale);
+ rulerSubCell.style.height = RhythmRuler.RULERHEIGHT + "px";
+ rulerSubCell.style.minHeight = rulerSubCell.style.height;
+ rulerSubCell.style.maxHeight = rulerSubCell.style.height;
+ rulerSubCell.style.width = this._noteWidth(noteValue) + "px";
+ rulerSubCell.style.minWidth = rulerSubCell.style.width;
+ rulerSubCell.style.border = "0px";
+ rulerSubCell.border = "0px";
+ rulerSubCell.padding = "0px";
+ rulerSubCell.style.padding = "0px";
+ rulerSubCell.style.lineHeight = 60 + " % ";
+ if (i % 2 === 0) {
+ if (j % 2 === 0) {
+ rulerSubCell.style.backgroundColor = platformColor.selectorBackground;
+ } else {
+ rulerSubCell.style.backgroundColor = platformColor.selectorSelected;
+ }
} else {
- console.error("Rhythm Ruler: null cell found on click");
+ if (j % 2 === 0) {
+ rulerSubCell.style.backgroundColor = platformColor.selectorSelected;
+ } else {
+ rulerSubCell.style.backgroundColor = platformColor.selectorBackground;
+ }
}
- }
- this._inLongPress = false;
- };
+ this.__addCellEventHandlers(rulerSubCell, this._noteWidth(noteValue), noteValue);
+ }
- let obj;
- if (cellWidth > 12 && noteValue > 0) {
- obj = rationalToFraction(Math.abs(1 / noteValue));
- cell.innerHTML = calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
- );
- } else {
- cell.innerHTML = "";
-
- cell.removeEventListener("mouseover", __mouseOverHandler);
- cell.addEventListener("mouseover", __mouseOverHandler);
-
- cell.removeEventListener("mouseout", __mouseOutHandler);
- cell.addEventListener("mouseout", __mouseOutHandler);
+ // Match the play button height to the ruler height.
+ rhythmRulerTableRow.cells[0].style.width = RhythmRuler.BUTTONSIZE + "px";
+ rhythmRulerTableRow.cells[0].style.minWidth = RhythmRuler.BUTTONSIZE + "px";
+ rhythmRulerTableRow.cells[0].style.maxWidth = RhythmRuler.BUTTONSIZE + "px";
+ rhythmRulerTableRow.cells[0].style.height = rulerRow.offsetHeight + "px";
+ rhythmRulerTableRow.cells[0].style.minHeight = rulerRow.offsetHeight + "px";
+ rhythmRulerTableRow.cells[0].style.maxHeight = rulerRow.offsetHeight + "px";
+ rhythmRulerTableRow.cells[0].style.verticalAlign = "middle";
}
- cell.removeEventListener("mousedown", __mouseDownHandler);
- cell.addEventListener("mousedown", __mouseDownHandler);
-
- cell.removeEventListener("mouseup", __mouseUpHandler);
- cell.addEventListener("mouseup", __mouseUpHandler);
-
- cell.removeEventListener("click", __clickHandler);
- cell.addEventListener("click", __clickHandler);
- }
-
- __getLongPressStatus() {
- return this._inLongPress;
- }
+ // Restore dissect history.
+ let cell;
+ for (let drum = 0; drum < this.Drums.length; drum++) {
+ if (drum === null) {
+ continue;
+ }
- __toggleRestState(cell, addToUndoList) {
+ for (let i = 0; i < this._dissectHistory.length; i++) {
+ if (this._dissectHistory[i][1] !== this.Drums[drum]) {
+ continue;
+ }
- if (cell !== null && cell.parentNode !== null) {
- this._rulerSelected = cell.parentNode.getAttribute("data-row");
- let noteValues = this.Rulers[this._rulerSelected][0];
- let noteValue = noteValues[cell.cellIndex];
+ const rhythmRulerTableRow = this._rulers[drum];
+ for (let j = 0; j < this._dissectHistory[i][0].length; j++) {
+ if (this._dissectHistory[i][0][j] == undefined) {
+ continue;
+ }
- const __mouseOverHandler = (event) => {
- let cell = event.target;
- if (cell === null) {
- return;
- }
+ this._rulerSelected = drum;
- let obj;
+ if (typeof this._dissectHistory[i][0][j] === "number") {
+ cell = rhythmRulerTableRow.cells[this._dissectHistory[i][0][j]];
+ this.__toggleRestState(cell, false);
+ } else if (typeof this._dissectHistory[i][0][j][0] === "number") {
+ if (typeof this._dissectHistory[i][0][j][1] === "number") {
+ // dissect is [cell, num]
+ cell = rhythmRulerTableRow.cells[this._dissectHistory[i][0][j][0]];
+ if (cell != undefined) {
+ this.__dissectByNumber(
+ cell,
+ this._dissectHistory[i][0][j][1],
+ false
+ );
+ }
+ // else {
+ // console.warn(
+ // "Could not find cell to divide. Did the order of the rhythm blocks change?"
+ // );
+ // }
+ } else {
+ // divide is [cell, [values]]
+ cell = rhythmRulerTableRow.cells[this._dissectHistory[i][0][j][0]];
+ if (cell != undefined) {
+ this.__divideFromList(
+ cell,
+ this._dissectHistory[i][0][j][1],
+ false
+ );
+ }
+ }
+ } else {
+ // tie is [[cell, value], [cell, value]...]
+ const history = this._dissectHistory[i][0][j];
+ this._mouseDownCell = rhythmRulerTableRow.cells[history[0][0]];
+ this._mouseUpCell = rhythmRulerTableRow.cells[last(history)[0]];
+ if (this._mouseUpCell != undefined) {
+ this.__tie(false);
+ }
- this._rulerSelected = cell.parentNode.getAttribute("data-row");
- let noteValues = this.Rulers[this._rulerSelected][0];
- let noteValue = noteValues[cell.cellIndex];
- if (noteValue < 0) {
- obj = rationalToFraction(
- Math.abs(Math.abs(-1 / noteValue))
- );
- cell.innerHTML =
- calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
- ) +
- " " +
- _("silence");
- } else {
- obj = rationalToFraction(
- Math.abs(Math.abs(1 / noteValue))
- );
- cell.innerHTML = calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
- );
+ this._mouseDownCell = null;
+ this._mouseUpCell = null;
+ }
}
- };
+ }
+ }
- const __mouseOutHandler = (event) => {
- let cell = event.target;
- cell.innerHTML = "";
- };
+ logo.textMsg(_("Click on the ruler to divide it."));
+ // this._piemenuRuler(this._rulerSelected);
+ }
- let obj;
- if (noteValue < 0) {
- obj = rationalToFraction(Math.abs(1 / noteValue));
- cell.innerHTML = calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
- );
- cell.removeEventListener("mouseover", __mouseOverHandler);
- cell.removeEventListener("mouseout", __mouseOutHandler);
- } else {
- cell.innerHTML = "";
+ _noteWidth(noteValue) {
+ return Math.floor(EIGHTHNOTEWIDTH * (8 / Math.abs(noteValue)) * 4);
+ }
- cell.removeEventListener("mouseover", __mouseOverHandler);
- cell.addEventListener("mouseover", __mouseOverHandler);
+ _calculateZebraStripes(rulerno) {
+ const ruler = this._rulers[rulerno];
+ let evenColor;
+ if (this._rulerSelected % 2 === 0) {
+ evenColor = platformColor.selectorBackground;
+ } else {
+ evenColor = platformColor.selectorSelected;
+ }
- cell.removeEventListener("mouseout", __mouseOutHandler);
- cell.addEventListener("mouseout", __mouseOutHandler);
+ for (let i = 0; i < ruler.cells.length; i++) {
+ const newCell = ruler.cells[i];
+ newCell.style.border = "2px solid lightgrey";
+ newCell.style.borderRadius = "10px";
+ if (evenColor === platformColor.selectorBackground) {
+ if (i % 2 === 0) {
+ newCell.style.backgroundColor = platformColor.selectorBackground;
+ } else {
+ newCell.style.backgroundColor = platformColor.selectorSelected;
+ }
}
- noteValues[cell.cellIndex] = -noteValue;
-
- this._calculateZebraStripes(this._rulerSelected);
-
- divisionHistory = this.Rulers[this._rulerSelected][1];
- if (addToUndoList) {
- this._undoList.push(["rest", this._rulerSelected]);
+ if (evenColor === platformColor.selectorSelected) {
+ if (i % 2 === 0) {
+ newCell.style.backgroundColor = platformColor.selectorSelected;
+ } else {
+ newCell.style.backgroundColor = platformColor.selectorBackground;
+ }
}
-
- divisionHistory.push(cell.cellIndex);
}
-
- // this._piemenuRuler(this._rulerSelected);
}
- __divideFromList(cell, newNoteValues, addToUndoList) {
- if (typeof cell !== "object") {
+ _dissectRuler(event, ruler) {
+ const cell = event.target;
+ if (cell === null) {
return;
}
- if (typeof newNoteValues !== "object") {
+ if (this._tapMode && this._tapTimes.length > 0) {
+ const d = new Date();
+ this._tapTimes.push(d.getTime());
return;
}
- let ruler = this._rulers[this._rulerSelected];
- let newCellIndex = cell.cellIndex;
-
- if (
- typeof this._rulerSelected === "string" ||
- typeof this._rulerSelected === "number"
- ) {
- let noteValues = this.Rulers[this._rulerSelected][0];
+ const cellParent = cell.parentNode;
+ if (cellParent === null) {
+ return;
+ }
- let divisionHistory = this.Rulers[this._rulerSelected][1];
- if (addToUndoList) {
- this._undoList.push(["tap", this._rulerSelected]);
- }
+ this._rulerSelected = ruler;
+ if (this._rulerSelected == undefined) {
+ return;
+ }
- divisionHistory.push([newCellIndex, newNoteValues]);
+ if (this._playing) {
+ // console.warn("You cannot dissect while widget is playing.");
+ return;
+ } else if (this._tapMode) {
+ // Tap a rhythm by clicking in a cell.
+ if (this._tapCell === null) {
+ const noteValues = this.Rulers[this._rulerSelected][0];
+ this._tapCell = event.target;
+ if (noteValues[this._tapCell.cellIndex] < 0) {
+ // Don't allow tapping in rests.
+ this._tapCell = null;
+ this._tapMode = false;
+ this._tapTimes = [];
+ this._tapEndTime = null;
+ this._tapButton.innerHTML =
+ '';
+ return;
+ }
- ruler.deleteCell(newCellIndex);
+ this._tapTimes = [];
- let noteValue = noteValues[newCellIndex];
- // let tempwidth = this._noteWidth(newNoteValue);
- noteValues.splice(newCellIndex, 1);
+ // Play a count off before starting tapping.
+ const interval = this._bpmFactor / Math.abs(noteValues[this._tapCell.cellIndex]);
- for (let i = 0; i < newNoteValues.length; i++) {
- let newCell = ruler.insertCell(newCellIndex + i);
- let newNoteValue = newNoteValues[i];
- let newCellWidth = parseFloat(this._noteWidth(newNoteValue));
- noteValues.splice(newCellIndex + i, 0, newNoteValue);
+ let drum;
+ if (this.Drums[this._rulerSelected] === null) {
+ drum = "snare drum";
+ } else {
+ const drumBlockNo =
+ logo.blocks.blockList[this.Drums[this._rulerSelected]].connections[1];
+ drum = logo.blocks.blockList[drumBlockNo].value;
+ }
- newCell.style.width = newCellWidth + "px";
- newCell.style.minWidth = newCell.style.width;
- newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
- newCell.style.minHeight = newCell.style.height;
- newCell.style.maxHeight = newCell.style.height;
+ // FIXME: Should be based on meter
+ for (let i = 0; i < 4; i++) {
+ setTimeout(() => {
+ logo.synth.trigger(0, "C4", Singer.defaultBPMFactor / 16, drum, null, null);
+ }, (interval * i) / 4);
+ }
- this.__addCellEventHandlers(
- newCell,
- newCellWidth,
- newNoteValue
- );
+ setTimeout(() => {
+ this.__startTapping(noteValues, interval, event);
+ }, interval);
+ }
+ } else {
+ let inputNum = this._dissectNumber.value;
+ if (inputNum === "" || isNaN(inputNum)) {
+ inputNum = 2;
+ } else {
+ inputNum = Math.abs(Math.floor(inputNum));
}
- this._calculateZebraStripes(this._rulerSelected);
+ this._dissectNumber.value = inputNum;
+
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ this.__dissectByNumber(cell, inputNum, true);
}
// this._piemenuRuler(this._rulerSelected);
- }
- __dissectByNumber(cell, inputNum, addToUndoList) {
- if (typeof cell !== "object") {
- return;
- }
+ //Save dissect history everytime user dissects ruler
+ this.saveDissectHistory();
+ }
- if (typeof inputNum !== "number") {
- return;
- }
+ __startTapping(noteValues, interval, event) {
+ const d = new Date();
+ this._tapTimes = [d.getTime()];
+ this._tapEndTime = this._tapTimes[0] + interval;
- let ruler = this._rulers[this._rulerSelected];
- let newCellIndex = cell.cellIndex;
+ // Set a timeout to end tapping
+ setTimeout(() => {
+ this.__endTapping(event);
+ }, interval);
- if (
- typeof this._rulerSelected === "string" ||
- typeof this._rulerSelected === "number"
- ) {
- let noteValues = this.Rulers[this._rulerSelected][0];
+ // Display a progress bar.
+ const __move = (tick, stepSize) => {
+ let width = 1;
- let noteValue = noteValues[newCellIndex];
- if (inputNum * noteValue > 256) {
- logo.errorMsg(
- _("Maximum value of 256 has been exceeded.")
- );
- return;
- } else {
- logo.hideMsgs();
- }
+ const id = setInterval(() => {
+ if (width >= 100) {
+ clearInterval(id);
+ } else {
+ width += stepSize;
+ this._progressBar.style.width = width + "%";
+ }
+ }, tick);
+ };
- let divisionHistory = this.Rulers[this._rulerSelected][1];
- if (addToUndoList) {
- this._undoList.push(["dissect", this._rulerSelected]);
- }
+ this._tapCell.innerHTML = "";
+ this._progressBar = this._tapCell.querySelector(".progressBar");
+ // Progress once per 8th note.
+ __move(interval / 8, 100 / 8);
+ }
- divisionHistory.push([newCellIndex, inputNum]);
+ __endTapping(event) {
+ const cell = event.target;
+ if (cell.parentNode === null) {
+ // console.debug("Null parent node in endTapping");
+ return;
+ }
- ruler.deleteCell(newCellIndex);
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ if (this._progressBar) this._progressBar.remove();
+ this._tapCell.innerHTML = "";
- let newNoteValue = 0;
+ const d = new Date();
+ this._tapTimes.push(d.getTime());
- newNoteValue = inputNum * noteValue;
+ this._tapMode = false;
+ if (typeof this._rulerSelected === "string" || typeof this._rulerSelected === "number") {
+ const noteValues = this.Rulers[this._rulerSelected][0];
- let tempwidth = this._noteWidth(newNoteValue);
- let tempwidthPixels = parseFloat(inputNum) * parseFloat(tempwidth) + "px";
- let difference = parseFloat(this._noteWidth(noteValue)) - parseFloat(inputNum) * parseFloat(tempwidth);
- let newCellWidth = parseFloat(this._noteWidth(newNoteValue)) + parseFloat(difference) / inputNum;
- noteValues.splice(newCellIndex, 1);
+ if (last(this._tapTimes) > this._tapEndTime) {
+ this._tapTimes[this._tapTimes.length - 1] = this._tapEndTime;
+ }
- for (let i = 0; i < inputNum; i++) {
- let newCell = ruler.insertCell(newCellIndex + i);
- noteValues.splice(newCellIndex + i, 0, newNoteValue);
+ // convert times into cells here.
+ let inputNum = this._dissectNumber.value;
+ if (inputNum === "" || isNaN(inputNum)) {
+ inputNum = 2;
+ } else {
+ inputNum = Math.abs(Math.floor(inputNum));
+ }
- newCell.style.width = newCellWidth + "px";
- newCell.style.minWidth = newCell.style.width;
- newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
- newCell.style.minHeight = newCell.style.height;
- newCell.style.maxHeight = newCell.style.height;
+ // Minimum beat is tied to the input number
+ let minimumBeat;
+ switch (inputNum) {
+ case 2:
+ minimumBeat = 16;
+ break;
+ case 3:
+ minimumBeat = 27;
+ break;
+ case 4:
+ minimumBeat = 32;
+ break;
+ case 5:
+ minimumBeat = 25;
+ break;
+ case 6:
+ minimumBeat = 36;
+ break;
+ case 7:
+ minimumBeat = 14;
+ break;
+ case 8:
+ minimumBeat = 64;
+ break;
+ default:
+ minimumBeat = 16;
+ break;
+ }
- this.__addCellEventHandlers(
- newCell,
- newCellWidth,
- newNoteValue
- );
+ const newNoteValues = [];
+ let sum = 0;
+ let obj;
+ for (let i = 1; i < this._tapTimes.length; i++) {
+ const dtime = this._tapTimes[i] - this._tapTimes[i - 1];
+ if (i < this._tapTimes.length - 1) {
+ obj = nearestBeat((100 * dtime) / this._bpmFactor, minimumBeat);
+ if (obj[0] === 0) {
+ obj[0] = 1;
+ obj[1] = obj[1] / 2;
+ }
+
+ if (sum + obj[0] / obj[1] < 1) {
+ sum += obj[0] / obj[1];
+ newNoteValues.push(obj[1] / obj[0]);
+ }
+ } else {
+ // Since the fractional value is noisy,
+ // ensure that the final beat make the
+ // total add up to the proper note value.
+ obj = rationalToFraction(1 / noteValues[this._tapCell.cellIndex] - sum);
+ newNoteValues.push(obj[1] / obj[0]);
+ }
}
- this._calculateZebraStripes(this._rulerSelected);
+ this.__divideFromList(this._tapCell, newNoteValues, true);
}
- // this._piemenuRuler(this._rulerSelected);
- };
+ this._tapTimes = [];
+ this._tapCell = null;
+ this._tapEndTime = null;
+ this._tapButton.innerHTML =
+ '';
+ }
- _tieRuler(event, ruler) {
- if (this._playing) {
- console.warn("You cannot tie while widget is playing.");
- return;
- } else if (this._tapMode) {
- // If we are tapping, then treat a tie as a tap.
- this._dissectRuler(event, ruler);
- return;
- }
+ __addCellEventHandlers(cell, cellWidth, noteValue) {
+ const __mouseOverHandler = (event) => {
+ const cell = event.target;
+ if (cell === null || cell.parentNode === null) {
+ return;
+ }
- // Does this work if there are more than 10 rulers?
- let cell = event.target;
- if (cell !== null && cell.parentNode !== null) {
this._rulerSelected = cell.parentNode.getAttribute("data-row");
- this.__tie(true);
- }
-
- // this._piemenuRuler(this._rulerSelected);
- }
+ const noteValues = this.Rulers[this._rulerSelected][0];
+ const noteValue = noteValues[cell.cellIndex];
+ let obj;
+ if (noteValue < 0) {
+ obj = rationalToFraction(Math.abs(Math.abs(-1 / noteValue)));
+ cell.innerHTML =
+ calcNoteValueToDisplay(obj[1], obj[0], this._cellScale) + " " + _("silence");
+ } else {
+ obj = rationalToFraction(Math.abs(Math.abs(1 / noteValue)));
+ cell.innerHTML = calcNoteValueToDisplay(obj[1], obj[0], this._cellScale);
+ }
+ };
- __tie(addToUndoList) {
- let ruler = this._rulers[this._rulerSelected];
+ const __mouseOutHandler = (event) => {
+ const cell = event.target;
+ cell.innerHTML = "";
+ };
- if (this._mouseDownCell === null || this._mouseUpCell === null) {
- return;
- }
+ const __mouseDownHandler = (event) => {
+ const cell = event.target;
+ this._mouseDownCell = cell;
- if (this._mouseDownCell === this._mouseUpCell) {
- return;
- }
+ const d = new Date();
+ this._longPressStartTime = d.getTime();
+ this._inLongPress = false;
- if (
- typeof this._rulerSelected === "string" ||
- typeof this._rulerSelected === "number"
- ) {
- let noteValues = this.Rulers[this._rulerSelected][0];
+ this._longPressBeep = setTimeout(() => {
+ // Removing audio feedback on long press since it
+ // occasionally confuses tone.js during rapid clicking
+ // in the widget.
- let downCellIndex = this._mouseDownCell.cellIndex;
- let upCellIndex = this._mouseUpCell.cellIndex;
+ // that._logo.synth.trigger(0, 'C4', 1 / 32, 'chime', null, null);
- if (downCellIndex === -1 || upCellIndex === -1) {
- return;
- }
+ const cell = this._mouseDownCell;
+ if (cell !== null && cell.parentNode !== null) {
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ cell.style.backgroundColor = platformColor.selectorBackground;
+ }
+ }, 1500);
+ };
- if (downCellIndex > upCellIndex) {
- let tmp = downCellIndex;
- downCellIndex = upCellIndex;
- upCellIndex = tmp;
- tmp = this._mouseDdownCell;
- this._mouseDownCell = this._mouseUpCell;
- this._mouseUpCell = tmp;
+ const __mouseUpHandler = (event) => {
+ clearTimeout(this._longPressBeep);
+ const cell = event.target;
+ this._mouseUpCell = cell;
+ if (this._mouseDownCell !== this._mouseUpCell) {
+ this._tieRuler(event, cell.parentNode.getAttribute("data-row"));
+ } else if (this._longPressStartTime !== null && !this._tapMode) {
+ const d = new Date();
+ const elapseTime = d.getTime() - this._longPressStartTime;
+ if (elapseTime > 1500) {
+ this._inLongPress = true;
+ this.__toggleRestState(this, true);
+ }
}
- noteValues = this.Rulers[this._rulerSelected][0];
+ this._mouseDownCell = null;
+ this._mouseUpCell = null;
+ this._longPressStartTime = null;
+ };
- let divisionHistory = this.Rulers[this._rulerSelected][1];
- if (addToUndoList) {
- this._undoList.push(["tie", this._rulerSelected]);
+ const __clickHandler = (event) => {
+ if (event == undefined) return;
+ if (!this.__getLongPressStatus()) {
+ const cell = event.target;
+ if (cell !== null && cell.parentNode !== null) {
+ this._dissectRuler(event, cell.parentNode.getAttribute("data-row"));
+ }
+ // else {
+ // console.error("Rhythm Ruler: null cell found on click");
+ // }
}
- let history = [];
- for (let i = downCellIndex; i < upCellIndex + 1; i++) {
- history.push([i, noteValues[i]]);
- }
+ this._inLongPress = false;
+ };
- divisionHistory.push(history);
+ let obj;
+ if (cellWidth > 12 && noteValue > 0) {
+ obj = rationalToFraction(Math.abs(1 / noteValue));
+ cell.innerHTML = calcNoteValueToDisplay(obj[1], obj[0], this._cellScale);
+ } else {
+ cell.innerHTML = "";
- let oldNoteValue = noteValues[downCellIndex];
- let noteValue = Math.abs(1 / oldNoteValue);
+ cell.removeEventListener("mouseover", __mouseOverHandler);
+ cell.addEventListener("mouseover", __mouseOverHandler);
- // Delete all the cells between down and up except the down
- // cell, which we will expand.
- for (let i = upCellIndex; i > downCellIndex; i--) {
- noteValue += Math.abs(1 / noteValues[i]);
- ruler.deleteCell(i);
- this.Rulers[this._rulerSelected][0].splice(i, 1);
- }
+ cell.removeEventListener("mouseout", __mouseOutHandler);
+ cell.addEventListener("mouseout", __mouseOutHandler);
+ }
- let newCellWidth = this._noteWidth(1 / noteValue);
- // Use noteValue of downCell for REST status.
- if (oldNoteValue < 0) {
- noteValues[downCellIndex] = -1 / noteValue;
- } else {
- noteValues[downCellIndex] = 1 / noteValue;
- }
+ cell.removeEventListener("mousedown", __mouseDownHandler);
+ cell.addEventListener("mousedown", __mouseDownHandler);
- this._mouseDownCell.style.width = newCellWidth + "px";
- this._mouseDownCell.style.minWidth = this._mouseDownCell.style.width;
- this._mouseDownCell.style.height = RhythmRuler.RULERHEIGHT + "px";
- this._mouseDownCell.style.minHeight = this._mouseDownCell.style.height;
- this._mouseDownCell.style.maxHeight = this._mouseDownCell.style.height;
+ cell.removeEventListener("mouseup", __mouseUpHandler);
+ cell.addEventListener("mouseup", __mouseUpHandler);
- this.__addCellEventHandlers(
- this._mouseDownCell,
- newCellWidth,
- noteValues[downCellIndex]
- );
+ cell.removeEventListener("click", __clickHandler);
+ cell.addEventListener("click", __clickHandler);
+ }
- this._calculateZebraStripes(this._rulerSelected);
- }
+ __getLongPressStatus() {
+ return this._inLongPress;
}
- _undo() {
- // FIXME: Add undo for REST
- logo.synth.stop();
- this._startingTime = null;
- this._playing = false;
- this._playingAll = false;
- this._playingOne = false;
- this._rulerPlaying = -1;
- this._startingTime = null;
+ __toggleRestState(cell, addToUndoList) {
+ if (cell !== null && cell.parentNode !== null) {
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ const noteValues = this.Rulers[this._rulerSelected][0];
+ const noteValue = noteValues[cell.cellIndex];
- if (this._undoList.length === 0) {
- return;
- }
+ const __mouseOverHandler = (event) => {
+ const cell = event.target;
+ if (cell === null) {
+ return;
+ }
- let obj = this._undoList.pop();
- let lastRuler = obj[1];
- let divisionHistory = this.Rulers[lastRuler][1];
- if (divisionHistory.length === 0) {
- return;
+ let obj;
+
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ const noteValues = this.Rulers[this._rulerSelected][0];
+ const noteValue = noteValues[cell.cellIndex];
+ if (noteValue < 0) {
+ obj = rationalToFraction(Math.abs(Math.abs(-1 / noteValue)));
+ cell.innerHTML =
+ calcNoteValueToDisplay(obj[1], obj[0], this._cellScale) +
+ " " +
+ _("silence");
+ } else {
+ obj = rationalToFraction(Math.abs(Math.abs(1 / noteValue)));
+ cell.innerHTML = calcNoteValueToDisplay(obj[1], obj[0], this._cellScale);
+ }
+ };
+
+ const __mouseOutHandler = (event) => {
+ const cell = event.target;
+ cell.innerHTML = "";
+ };
+
+ let obj;
+ if (noteValue < 0) {
+ obj = rationalToFraction(Math.abs(1 / noteValue));
+ cell.innerHTML = calcNoteValueToDisplay(obj[1], obj[0], this._cellScale);
+ cell.removeEventListener("mouseover", __mouseOverHandler);
+ cell.removeEventListener("mouseout", __mouseOutHandler);
+ } else {
+ cell.innerHTML = "";
+
+ cell.removeEventListener("mouseover", __mouseOverHandler);
+ cell.addEventListener("mouseover", __mouseOverHandler);
+
+ cell.removeEventListener("mouseout", __mouseOutHandler);
+ cell.addEventListener("mouseout", __mouseOutHandler);
+ }
+
+ noteValues[cell.cellIndex] = -noteValue;
+
+ this._calculateZebraStripes(this._rulerSelected);
+
+ const divisionHistory = this.Rulers[this._rulerSelected][1];
+ if (addToUndoList) {
+ this._undoList.push(["rest", this._rulerSelected]);
+ }
+
+ divisionHistory.push(cell.cellIndex);
+ }
+
+ // this._piemenuRuler(this._rulerSelected);
+ }
+
+ __divideFromList(cell, newNoteValues, addToUndoList) {
+ if (typeof cell !== "object") {
+ return;
+ }
+
+ if (typeof newNoteValues !== "object") {
+ return;
+ }
+
+ const ruler = this._rulers[this._rulerSelected];
+ const newCellIndex = cell.cellIndex;
+
+ if (typeof this._rulerSelected === "string" || typeof this._rulerSelected === "number") {
+ const noteValues = this.Rulers[this._rulerSelected][0];
+
+ const divisionHistory = this.Rulers[this._rulerSelected][1];
+ if (addToUndoList) {
+ this._undoList.push(["tap", this._rulerSelected]);
+ }
+
+ divisionHistory.push([newCellIndex, newNoteValues]);
+
+ ruler.deleteCell(newCellIndex);
+ // let tempwidth = this._noteWidth(newNoteValue);
+ noteValues.splice(newCellIndex, 1);
+
+ for (let i = 0; i < newNoteValues.length; i++) {
+ const newCell = ruler.insertCell(newCellIndex + i);
+ const newNoteValue = newNoteValues[i];
+ const newCellWidth = parseFloat(this._noteWidth(newNoteValue));
+ noteValues.splice(newCellIndex + i, 0, newNoteValue);
+
+ newCell.style.width = newCellWidth + "px";
+ newCell.style.minWidth = newCell.style.width;
+ newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
+ newCell.style.minHeight = newCell.style.height;
+ newCell.style.maxHeight = newCell.style.height;
+
+ this.__addCellEventHandlers(newCell, newCellWidth, newNoteValue);
+ }
+
+ this._calculateZebraStripes(this._rulerSelected);
+ }
+
+ // this._piemenuRuler(this._rulerSelected);
+ }
+
+ __dissectByNumber(cell, inputNum, addToUndoList) {
+ if (typeof cell !== "object") {
+ return;
+ }
+
+ if (typeof inputNum !== "number") {
+ return;
+ }
+
+ const ruler = this._rulers[this._rulerSelected];
+ const newCellIndex = cell.cellIndex;
+
+ if (typeof this._rulerSelected === "string" || typeof this._rulerSelected === "number") {
+ const noteValues = this.Rulers[this._rulerSelected][0];
+
+ const noteValue = noteValues[newCellIndex];
+ if (inputNum * noteValue > 256) {
+ logo.errorMsg(_("Maximum value of 256 has been exceeded."));
+ return;
+ } else {
+ logo.hideMsgs();
+ }
+
+ const divisionHistory = this.Rulers[this._rulerSelected][1];
+ if (addToUndoList) {
+ this._undoList.push(["dissect", this._rulerSelected]);
+ }
+
+ divisionHistory.push([newCellIndex, inputNum]);
+
+ ruler.deleteCell(newCellIndex);
+
+ let newNoteValue = 0;
+
+ newNoteValue = inputNum * noteValue;
+
+ const tempwidth = this._noteWidth(newNoteValue);
+ const difference =
+ parseFloat(this._noteWidth(noteValue)) -
+ parseFloat(inputNum) * parseFloat(tempwidth);
+ const newCellWidth =
+ parseFloat(this._noteWidth(newNoteValue)) + parseFloat(difference) / inputNum;
+ noteValues.splice(newCellIndex, 1);
+
+ for (let i = 0; i < inputNum; i++) {
+ const newCell = ruler.insertCell(newCellIndex + i);
+ noteValues.splice(newCellIndex + i, 0, newNoteValue);
+
+ newCell.style.width = newCellWidth + "px";
+ newCell.style.minWidth = newCell.style.width;
+ newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
+ newCell.style.minHeight = newCell.style.height;
+ newCell.style.maxHeight = newCell.style.height;
+
+ this.__addCellEventHandlers(newCell, newCellWidth, newNoteValue);
+ }
+
+ this._calculateZebraStripes(this._rulerSelected);
+ }
+
+ // this._piemenuRuler(this._rulerSelected);
+ }
+
+ _tieRuler(event, ruler) {
+ if (this._playing) {
+ // console.warn("You cannot tie while widget is playing.");
+ return;
+ } else if (this._tapMode) {
+ // If we are tapping, then treat a tie as a tap.
+ this._dissectRuler(event, ruler);
+ return;
+ }
+
+ // Does this work if there are more than 10 rulers?
+ const cell = event.target;
+ if (cell !== null && cell.parentNode !== null) {
+ this._rulerSelected = cell.parentNode.getAttribute("data-row");
+ this.__tie(true);
+ }
+
+ // this._piemenuRuler(this._rulerSelected);
+ }
+
+ __tie(addToUndoList) {
+ const ruler = this._rulers[this._rulerSelected];
+
+ if (this._mouseDownCell === null || this._mouseUpCell === null) {
+ return;
+ }
+
+ if (this._mouseDownCell === this._mouseUpCell) {
+ return;
+ }
+
+ if (typeof this._rulerSelected === "string" || typeof this._rulerSelected === "number") {
+ let noteValues = this.Rulers[this._rulerSelected][0];
+
+ let downCellIndex = this._mouseDownCell.cellIndex;
+ let upCellIndex = this._mouseUpCell.cellIndex;
+
+ if (downCellIndex === -1 || upCellIndex === -1) {
+ return;
+ }
+
+ if (downCellIndex > upCellIndex) {
+ let tmp = downCellIndex;
+ downCellIndex = upCellIndex;
+ upCellIndex = tmp;
+ tmp = this._mouseDdownCell;
+ this._mouseDownCell = this._mouseUpCell;
+ this._mouseUpCell = tmp;
+ }
+
+ noteValues = this.Rulers[this._rulerSelected][0];
+
+ const divisionHistory = this.Rulers[this._rulerSelected][1];
+ if (addToUndoList) {
+ this._undoList.push(["tie", this._rulerSelected]);
+ }
+
+ const history = [];
+ for (let i = downCellIndex; i < upCellIndex + 1; i++) {
+ history.push([i, noteValues[i]]);
+ }
+
+ divisionHistory.push(history);
+
+ const oldNoteValue = noteValues[downCellIndex];
+ let noteValue = Math.abs(1 / oldNoteValue);
+
+ // Delete all the cells between down and up except the down
+ // cell, which we will expand.
+ for (let i = upCellIndex; i > downCellIndex; i--) {
+ noteValue += Math.abs(1 / noteValues[i]);
+ ruler.deleteCell(i);
+ this.Rulers[this._rulerSelected][0].splice(i, 1);
+ }
+
+ const newCellWidth = this._noteWidth(1 / noteValue);
+ // Use noteValue of downCell for REST status.
+ if (oldNoteValue < 0) {
+ noteValues[downCellIndex] = -1 / noteValue;
+ } else {
+ noteValues[downCellIndex] = 1 / noteValue;
+ }
+
+ this._mouseDownCell.style.width = newCellWidth + "px";
+ this._mouseDownCell.style.minWidth = this._mouseDownCell.style.width;
+ this._mouseDownCell.style.height = RhythmRuler.RULERHEIGHT + "px";
+ this._mouseDownCell.style.minHeight = this._mouseDownCell.style.height;
+ this._mouseDownCell.style.maxHeight = this._mouseDownCell.style.height;
+
+ this.__addCellEventHandlers(
+ this._mouseDownCell,
+ newCellWidth,
+ noteValues[downCellIndex]
+ );
+
+ this._calculateZebraStripes(this._rulerSelected);
+ }
+ }
+
+ _undo() {
+ // FIXME: Add undo for REST
+ logo.synth.stop();
+ this._startingTime = null;
+ this._playing = false;
+ this._playingAll = false;
+ this._playingOne = false;
+ this._rulerPlaying = -1;
+ this._startingTime = null;
+
+ if (this._undoList.length === 0) {
+ return;
+ }
+
+ const obj = this._undoList.pop();
+ const lastRuler = obj[1];
+ const divisionHistory = this.Rulers[lastRuler][1];
+ if (divisionHistory.length === 0) {
+ return;
}
- let ruler = this._rulers[lastRuler];
- let noteValues = this.Rulers[lastRuler][0];
+ const ruler = this._rulers[lastRuler];
+ const noteValues = this.Rulers[lastRuler][0];
if (obj[0] === "dissect") {
- let inputNum = divisionHistory[divisionHistory.length - 1][1];
- let newCellIndex = divisionHistory[divisionHistory.length - 1][0];
- let cellWidth = ruler.cells[newCellIndex].style.width;
- let newCellWidth = parseFloat(cellWidth) * inputNum;
- let oldCellNoteValue = noteValues[newCellIndex];
- let newNoteValue = oldCellNoteValue / inputNum;
-
- let newCell = ruler.insertCell(newCellIndex);
+ const inputNum = divisionHistory[divisionHistory.length - 1][1];
+ const newCellIndex = divisionHistory[divisionHistory.length - 1][0];
+ const cellWidth = ruler.cells[newCellIndex].style.width;
+ const newCellWidth = parseFloat(cellWidth) * inputNum;
+ const oldCellNoteValue = noteValues[newCellIndex];
+ const newNoteValue = oldCellNoteValue / inputNum;
+
+ const newCell = ruler.insertCell(newCellIndex);
newCell.style.width = this._noteWidth(newNoteValue) + "px";
newCell.style.minWidth = newCell.style.width;
newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
@@ -800,21 +1164,19 @@ class RhythmRuler {
ruler.deleteCell(newCellIndex + 1);
}
} else if (obj[0] === "tap") {
- let newCellIndex = last(divisionHistory)[0];
- let oldNoteValues = last(divisionHistory)[1];
+ const newCellIndex = last(divisionHistory)[0];
+ const oldNoteValues = last(divisionHistory)[1];
- // Calculate the new note value based on the sum of the
- // oldnoteValues.
- let oldCellNoteValue = noteValues[newCellIndex];
+ // Calculate the new note value based on the sum of the oldnoteValues.
let sum = 0;
for (let i = 0; i < oldNoteValues.length; i++) {
sum += 1 / oldNoteValues[i];
}
- let newNoteValue = 1 / sum;
- let newCellWidth = this._noteWidth(newNoteValue);
+ const newNoteValue = 1 / sum;
+ const newCellWidth = this._noteWidth(newNoteValue);
- let newCell = ruler.insertCell(newCellIndex);
+ const newCell = ruler.insertCell(newCellIndex);
newCell.style.width = newCellWidth + "px";
newCell.style.minWidth = newCell.style.width;
newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
@@ -823,12 +1185,8 @@ class RhythmRuler {
newCell.style.backgroundColor = platformColor.selectorBackground;
- let obj = rationalToFraction(newNoteValue);
- newCell.innerHTML = calcNoteValueToDisplay(
- obj[1],
- obj[0],
- this._cellScale
- );
+ const obj = rationalToFraction(newNoteValue);
+ newCell.innerHTML = calcNoteValueToDisplay(obj[1], obj[0], this._cellScale);
noteValues[newCellIndex] = newNoteValue;
noteValues.splice(newCellIndex + 1, oldNoteValues.length - 1);
@@ -839,13 +1197,13 @@ class RhythmRuler {
ruler.deleteCell(newCellIndex + 1);
}
} else if (obj[0] === "tie") {
- let history = last(divisionHistory);
+ const history = last(divisionHistory);
// The old cell is the same as the first entry in the
// history. Dissect the old cell into history.length
// parts and restore their size and note values.
if (history.length > 0) {
- let oldCell = ruler.cells[history[0][0]];
- let oldCellWidth = this._noteWidth(history[0][1]);
+ const oldCell = ruler.cells[history[0][0]];
+ const oldCellWidth = this._noteWidth(history[0][1]);
oldCell.style.width = oldCellWidth + "px";
oldCell.style.minWidth = oldCell.style.width;
oldCell.style.height = RhythmRuler.RULERHEIGHT + "px";
@@ -853,15 +1211,11 @@ class RhythmRuler {
oldCell.style.maxHeight = oldCell.style.height;
noteValues[history[0][0]] = history[0][1];
- this.__addCellEventHandlers(
- oldCell,
- oldCellWidth,
- history[0][1]
- );
+ this.__addCellEventHandlers(oldCell, oldCellWidth, history[0][1]);
for (let i = 1; i < history.length; i++) {
- let newCell = ruler.insertCell(history[0][0] + i);
- let newCellWidth = this._noteWidth(history[i][1]);
+ const newCell = ruler.insertCell(history[0][0] + i);
+ const newCellWidth = this._noteWidth(history[i][1]);
newCell.style.width = newCellWidth + "px";
newCell.style.minWidth = newCell.style.width;
newCell.style.height = RhythmRuler.RULERHEIGHT + "px";
@@ -869,26 +1223,19 @@ class RhythmRuler {
newCell.style.maxHeight = newCell.style.height;
noteValues.splice(history[0][0] + i, 0, history[i][1]);
- newCell.innerHTML = calcNoteValueToDisplay(
- history[i][1],
- 1,
- this._cellScale
- );
+ newCell.innerHTML = calcNoteValueToDisplay(history[i][1], 1, this._cellScale);
- this.__addCellEventHandlers(
- newCell,
- newCellWidth,
- history[i][1]
- );
+ this.__addCellEventHandlers(newCell, newCellWidth, history[i][1]);
}
this.Rulers[lastRuler][0] = noteValues;
- } else {
- console.warn("empty history encountered... skipping undo");
}
+ // else {
+ // console.warn("empty history encountered... skipping undo");
+ // }
} else if (obj[0] === "rest") {
- let newCellIndex = last(divisionHistory);
- let cell = ruler.cells[newCellIndex];
+ const newCellIndex = last(divisionHistory);
+ const cell = ruler.cells[newCellIndex];
this.__toggleRestState(cell, false);
divisionHistory.pop();
}
@@ -1007,7 +1354,7 @@ class RhythmRuler {
logo.synth.stop();
logo.resetSynth(0);
if (this._startingTime === null) {
- let d = new Date();
+ const d = new Date();
this._startingTime = d.getTime();
for (let i = 0; i < this.Rulers.length; i++) {
this._offsets[i] = 0;
@@ -1024,20 +1371,20 @@ class RhythmRuler {
logo.synth.stop();
logo.resetSynth(0);
if (this._startingTime === null) {
- let d = new Date();
+ const d = new Date();
this._startingTime = d.getTime();
this._elapsedTimes[this._rulerSelected] = 0;
this._offsets[this._rulerSelected] = 0;
}
- console.debug("this._rulerSelected " + this._rulerSelected);
+ // console.debug("this._rulerSelected " + this._rulerSelected);
this.__loop(0, this._rulerSelected, 0);
}
__loop(noteTime, rulerNo, colIndex) {
- let ruler = this._rulers[rulerNo];
+ const ruler = this._rulers[rulerNo];
if (ruler === null) {
- console.warn("Cannot find ruler " + rulerNo + ". Widget closed?");
+ // console.warn("Cannot find ruler " + rulerNo + ". Widget closed?");
return;
}
@@ -1046,17 +1393,16 @@ class RhythmRuler {
this._calculateZebraStripes(rulerNo);
}
- let cell = ruler.cells[colIndex];
- let noteValues = this.Rulers[rulerNo][0];
- let noteValue = noteValues[colIndex];
+ const cell = ruler.cells[colIndex];
+ const noteValues = this.Rulers[rulerNo][0];
+ const noteValue = noteValues[colIndex];
noteTime = Math.abs(1 / noteValue);
let drum;
if (this.Drums[rulerNo] === null) {
drum = "snare drum";
} else {
- let drumblockno = logo.blocks.blockList[this.Drums[rulerNo]]
- .connections[1];
+ const drumblockno = logo.blocks.blockList[this.Drums[rulerNo]].connections[1];
drum = logo.blocks.blockList[drumblockno].value;
}
@@ -1087,17 +1433,27 @@ class RhythmRuler {
}
}
-
if (this._playing) {
// Play the current note.
if (noteValue > 0) {
if (foundVoice) {
logo.synth.trigger(
- 0, "C4", Singer.defaultBPMFactor / noteValue, drum, null, null, false
+ 0,
+ "C4",
+ Singer.defaultBPMFactor / noteValue,
+ drum,
+ null,
+ null,
+ false
);
} else if (foundDrum) {
logo.synth.trigger(
- 0, ["C4"], Singer.defaultBPMFactor / noteValue, drum, null, null
+ 0,
+ ["C4"],
+ Singer.defaultBPMFactor / noteValue,
+ drum,
+ null,
+ null
);
}
}
@@ -1107,8 +1463,7 @@ class RhythmRuler {
// Calculate any offset in playback.
const d = new Date();
- this._offsets[rulerNo] =
- d.getTime() - this._startingTime - this._elapsedTimes[rulerNo];
+ this._offsets[rulerNo] = d.getTime() - this._startingTime - this._elapsedTimes[rulerNo];
}
setTimeout(() => {
@@ -1127,15 +1482,15 @@ class RhythmRuler {
_save(selectedRuler) {
// Deprecated -- replaced by save tuplets code
- for (let name in logo.blocks.palettes.dict) {
+ for (const name in logo.blocks.palettes.dict) {
logo.blocks.palettes.dict[name].hideMenu(true);
}
logo.refreshCanvas();
setTimeout(() => {
- let ruler = this._rulers[selectedRuler];
- let noteValues = this.Rulers[selectedRuler][0];
+ const ruler = this._rulers[selectedRuler];
+ const noteValues = this.Rulers[selectedRuler][0];
// Get the first word of drum's name (ignore the word 'drum' itself)
// and add 'rhythm'.
let stack_value;
@@ -1144,37 +1499,49 @@ class RhythmRuler {
} else {
stack_value =
logo.blocks.blockList[
- logo.blocks.blockList[this.Drums[selectedRuler]]
- .connections[1]
+ logo.blocks.blockList[this.Drums[selectedRuler]].connections[1]
].value.split(" ")[0] +
" " +
_("rhythm");
}
- let delta = selectedRuler * 42;
- let newStack = [
+ const delta = selectedRuler * 42;
+ const newStack = [
[
0,
- ["action", { collapsed: true }],
+ [
+ "action",
+ {
+ collapsed: true
+ }
+ ],
100 + delta,
100 + delta,
[null, 1, 2, null]
],
- [1, ["text", { value: stack_value }], 0, 0, [0]]
+ [
+ 1,
+ [
+ "text",
+ {
+ value: stack_value
+ }
+ ],
+ 0,
+ 0,
+ [0]
+ ]
];
let previousBlock = 0;
let sameNoteValue = 1;
for (let i = 0; i < ruler.cells.length; i++) {
- if (
- noteValues[i] === noteValues[i + 1] &&
- i < ruler.cells.length - 1
- ) {
+ if (noteValues[i] === noteValues[i + 1] && i < ruler.cells.length - 1) {
sameNoteValue += 1;
continue;
} else {
- let idx = newStack.length;
- let noteValue = noteValues[i];
+ const idx = newStack.length;
+ const noteValue = noteValues[i];
- let obj = rationalToFraction(1 / Math.abs(noteValue));
+ const obj = rationalToFraction(1 / Math.abs(noteValue));
newStack.push([
idx,
@@ -1185,49 +1552,46 @@ class RhythmRuler {
]);
newStack.push([
idx + 1,
- ["number", { value: sameNoteValue }],
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
0,
0,
[idx]
]);
- newStack.push([
- idx + 2,
- "divide",
- 0,
- 0,
- [idx, idx + 3, idx + 4]
- ]);
+ newStack.push([idx + 2, "divide", 0, 0, [idx, idx + 3, idx + 4]]);
newStack.push([
idx + 3,
- ["number", { value: obj[0] }],
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
0,
0,
[idx + 2]
]);
newStack.push([
idx + 4,
- ["number", { value: obj[1] }],
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
0,
0,
[idx + 2]
]);
newStack.push([idx + 5, "vspace", 0, 0, [idx, idx + 6]]);
if (i == ruler.cells.length - 1) {
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, null]
- ]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, null]]);
} else {
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, idx + 7]
- ]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, idx + 7]]);
}
previousBlock = idx + 6;
@@ -1245,52 +1609,64 @@ class RhythmRuler {
}
_saveTuplets(selectedRuler) {
- for (let name in logo.blocks.palettes.dict) {
+ for (const name in logo.blocks.palettes.dict) {
logo.blocks.palettes.dict[name].hideMenu(true);
}
logo.refreshCanvas();
setTimeout(() => {
- let ruler = this._rulers[selectedRuler];
- let noteValues = this.Rulers[selectedRuler][0];
+ const ruler = this._rulers[selectedRuler];
+ const noteValues = this.Rulers[selectedRuler][0];
let stack_value;
if (this.Drums[selectedRuler] === null) {
stack_value = _("rhythm");
} else {
stack_value =
logo.blocks.blockList[
- logo.blocks.blockList[this.Drums[selectedRuler]]
- .connections[1]
+ logo.blocks.blockList[this.Drums[selectedRuler]].connections[1]
].value.split(" ")[0] +
" " +
_("rhythm");
}
- let delta = selectedRuler * 42;
- let newStack = [
+ const delta = selectedRuler * 42;
+ const newStack = [
[
0,
- ["action", { collapsed: true }],
+ [
+ "action",
+ {
+ collapsed: true
+ }
+ ],
100 + delta,
100 + delta,
[null, 1, 2, null]
],
- [1, ["text", { value: stack_value }], 0, 0, [0]]
+ [
+ 1,
+ [
+ "text",
+ {
+ value: stack_value
+ }
+ ],
+ 0,
+ 0,
+ [0]
+ ]
];
let previousBlock = 0;
let sameNoteValue = 1;
for (let i = 0; i < ruler.cells.length; i++) {
- if (
- noteValues[i] === noteValues[i + 1] &&
- i < ruler.cells.length - 1
- ) {
+ if (noteValues[i] === noteValues[i + 1] && i < ruler.cells.length - 1) {
sameNoteValue += 1;
continue;
} else {
- let idx = newStack.length;
- let noteValue = noteValues[i];
- let obj = rationalToFraction(1 / Math.abs(noteValue));
- let n = obj[1] / sameNoteValue;
+ const idx = newStack.length;
+ const noteValue = noteValues[i];
+ const obj = rationalToFraction(1 / Math.abs(noteValue));
+ const n = obj[1] / sameNoteValue;
if (Number.isInteger(n)) {
newStack.push([
idx,
@@ -1301,39 +1677,42 @@ class RhythmRuler {
]);
newStack.push([
idx + 1,
- ["number", { value: sameNoteValue }],
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
0,
0,
[idx]
]);
- newStack.push([
- idx + 2,
- "divide",
- 0,
- 0,
- [idx, idx + 3, idx + 4]
- ]);
+ newStack.push([idx + 2, "divide", 0, 0, [idx, idx + 3, idx + 4]]);
newStack.push([
idx + 3,
- ["number", { value: obj[0] }],
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
0,
0,
[idx + 2]
]);
newStack.push([
idx + 4,
- ["number", { value: n }],
+ [
+ "number",
+ {
+ value: n
+ }
+ ],
0,
0,
[idx + 2]
]);
- newStack.push([
- idx + 5,
- "vspace",
- 0,
- 0,
- [idx, idx + 6]
- ]);
+ newStack.push([idx + 5, "vspace", 0, 0, [idx, idx + 6]]);
} else {
newStack.push([
idx,
@@ -1344,57 +1723,48 @@ class RhythmRuler {
]);
newStack.push([
idx + 1,
- ["number", { value: sameNoteValue }],
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
0,
0,
[idx]
]);
- newStack.push([
- idx + 2,
- "divide",
- 0,
- 0,
- [idx, idx + 3, idx + 4]
- ]);
+ newStack.push([idx + 2, "divide", 0, 0, [idx, idx + 3, idx + 4]]);
newStack.push([
idx + 3,
- ["number", { value: obj[0] }],
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
0,
0,
[idx + 2]
]);
newStack.push([
idx + 4,
- ["number", { value: obj[1] }],
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
0,
0,
[idx + 2]
]);
- newStack.push([
- idx + 5,
- "vspace",
- 0,
- 0,
- [idx, idx + 6]
- ]);
+ newStack.push([idx + 5, "vspace", 0, 0, [idx, idx + 6]]);
}
if (i == ruler.cells.length - 1) {
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, null]
- ]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, null]]);
} else {
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, idx + 7]
- ]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, idx + 7]]);
}
previousBlock = idx + 6;
@@ -1412,68 +1782,84 @@ class RhythmRuler {
}
_saveTupletsMerged(noteValues) {
- for (let name in logo.blocks.palettes.dict) {
+ for (const name in logo.blocks.palettes.dict) {
logo.blocks.palettes.dict[name].hideMenu(true);
}
logo.refreshCanvas();
- let stack_value = _("rhythm");
- let delta = 42;
- let newStack = [
+ const stack_value = _("rhythm");
+ const delta = 42;
+ const newStack = [
[
0,
- ["action", { collapsed: true }],
+ [
+ "action",
+ {
+ collapsed: true
+ }
+ ],
100 + delta,
100 + delta,
[null, 1, 2, null]
],
- [1, ["text", { value: stack_value }], 0, 0, [0]]
+ [
+ 1,
+ [
+ "text",
+ {
+ value: stack_value
+ }
+ ],
+ 0,
+ 0,
+ [0]
+ ]
];
let previousBlock = 0;
let sameNoteValue = 1;
for (let i = 0; i < noteValues.length; i++) {
- if (
- noteValues[i] === noteValues[i + 1] &&
- i < noteValues.length - 1
- ) {
+ if (noteValues[i] === noteValues[i + 1] && i < noteValues.length - 1) {
sameNoteValue += 1;
continue;
} else {
- let idx = newStack.length;
- let noteValue = noteValues[i];
- let obj = rationalToFraction(1 / Math.abs(noteValue));
- newStack.push([
- idx,
- "rhythm2",
- 0,
- 0,
- [previousBlock, idx + 1, idx + 2, idx + 5]
- ]);
+ const idx = newStack.length;
+ const noteValue = noteValues[i];
+ const obj = rationalToFraction(1 / Math.abs(noteValue));
+ newStack.push([idx, "rhythm2", 0, 0, [previousBlock, idx + 1, idx + 2, idx + 5]]);
newStack.push([
idx + 1,
- ["number", { value: sameNoteValue }],
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
0,
0,
[idx]
]);
- newStack.push([
- idx + 2,
- "divide",
- 0,
- 0,
- [idx, idx + 3, idx + 4]
- ]);
+ newStack.push([idx + 2, "divide", 0, 0, [idx, idx + 3, idx + 4]]);
newStack.push([
idx + 3,
- ["number", { value: obj[0] }],
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
0,
0,
[idx + 2]
]);
newStack.push([
idx + 4,
- ["number", { value: obj[1] }],
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
0,
0,
[idx + 2]
@@ -1483,13 +1869,7 @@ class RhythmRuler {
if (i == noteValues.length - 1) {
newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, null]]);
} else {
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, idx + 7]
- ]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, idx + 7]]);
}
previousBlock = idx + 6;
@@ -1507,9 +1887,7 @@ class RhythmRuler {
if (this.Drums[selectedRuler] === null) {
drum = "snare drum";
} else {
- let drumBlockNo = logo.blocks.blockList[
- this.Drums[selectedRuler]
- ].connections[1];
+ const drumBlockNo = logo.blocks.blockList[this.Drums[selectedRuler]].connections[1];
drum = logo.blocks.blockList[drumBlockNo].value;
}
@@ -1534,16 +1912,16 @@ class RhythmRuler {
}
_saveDrumMachine(selectedRuler, drum, effect) {
- for (let name in logo.blocks.palettes.dict) {
+ for (const name in logo.blocks.palettes.dict) {
logo.blocks.palettes.dict[name].hideMenu(true);
}
logo.refreshCanvas();
setTimeout(() => {
- let ruler = this._rulers[selectedRuler];
- let noteValues = this.Rulers[selectedRuler][0];
- let delta = selectedRuler * 42;
+ const ruler = this._rulers[selectedRuler];
+ const noteValues = this.Rulers[selectedRuler][0];
+ const delta = selectedRuler * 42;
// Just save the action, not the drum machine itself.
// let newStack = [[0, ['start', {'collapsed': false}], 100 + delta, 100 + delta, [null, 1, null]]];
@@ -1554,37 +1932,49 @@ class RhythmRuler {
} else {
action_name =
logo.blocks.blockList[
- logo.blocks.blockList[this.Drums[selectedRuler]]
- .connections[1]
+ logo.blocks.blockList[this.Drums[selectedRuler]].connections[1]
].value.split(" ")[0] +
" " +
_("action");
}
- let newStack = [
+ const newStack = [
[
0,
- ["action", { collapsed: true }],
+ [
+ "action",
+ {
+ collapsed: true
+ }
+ ],
100 + delta,
100 + delta,
[null, 1, 2, null]
],
- [1, ["text", { value: action_name }], 0, 0, [0]]
+ [
+ 1,
+ [
+ "text",
+ {
+ value: action_name
+ }
+ ],
+ 0,
+ 0,
+ [0]
+ ]
];
let previousBlock = 0; // 1
let sameNoteValue = 1;
for (let i = 0; i < ruler.cells.length; i++) {
- if (
- noteValues[i] === noteValues[i + 1] &&
- i < ruler.cells.length - 1
- ) {
+ if (noteValues[i] === noteValues[i + 1] && i < ruler.cells.length - 1) {
sameNoteValue += 1;
continue;
} else {
- let idx = newStack.length;
- let noteValue = noteValues[i];
+ const idx = newStack.length;
+ const noteValue = noteValues[i];
- let obj = rationalToFraction(1 / Math.abs(noteValue));
+ const obj = rationalToFraction(1 / Math.abs(noteValue));
if (sameNoteValue === 1) {
// Add a note block.
@@ -1595,16 +1985,15 @@ class RhythmRuler {
0,
[previousBlock, idx + 1, idx + 4, idx + 7]
]);
- newStack.push([
- idx + 1,
- "divide",
- 0,
- 0,
- [idx, idx + 2, idx + 3]
- ]);
+ newStack.push([idx + 1, "divide", 0, 0, [idx, idx + 2, idx + 3]]);
newStack.push([
idx + 2,
- ["number", { value: obj[0] }],
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
0,
0,
[idx + 1]
@@ -1612,58 +2001,43 @@ class RhythmRuler {
if (noteValue < 0) {
newStack.push([
idx + 3,
- ["number", { value: obj[1] }],
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
0,
0,
[idx + 1]
]);
- newStack.push([
- idx + 4,
- "vspace",
- 0,
- 0,
- [idx, idx + 5]
- ]);
- newStack.push([
- idx + 5,
- "rest2",
- 0,
- 0,
- [idx + 4, idx + 6]
- ]);
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, null]
- ]);
+ newStack.push([idx + 4, "vspace", 0, 0, [idx, idx + 5]]);
+ newStack.push([idx + 5, "rest2", 0, 0, [idx + 4, idx + 6]]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, null]]);
} else {
newStack.push([
idx + 3,
- ["number", { value: obj[1] }],
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
0,
0,
[idx + 1]
]);
- newStack.push([
- idx + 4,
- "vspace",
- 0,
- 0,
- [idx, idx + 5]
- ]);
- newStack.push([
- idx + 5,
- "playdrum",
- 0,
- 0,
- [idx + 4, idx + 6, null]
- ]);
+ newStack.push([idx + 4, "vspace", 0, 0, [idx, idx + 5]]);
+ newStack.push([idx + 5, "playdrum", 0, 0, [idx + 4, idx + 6, null]]);
if (effect) {
newStack.push([
idx + 6,
- ["effectsname", { value: drum }],
+ [
+ "effectsname",
+ {
+ value: drum
+ }
+ ],
0,
0,
[idx + 5]
@@ -1671,7 +2045,12 @@ class RhythmRuler {
} else {
newStack.push([
idx + 6,
- ["drumname", { value: drum }],
+ [
+ "drumname",
+ {
+ value: drum
+ }
+ ],
0,
0,
[idx + 5]
@@ -1679,21 +2058,9 @@ class RhythmRuler {
}
}
if (i == ruler.cells.length - 1) {
- newStack.push([
- idx + 7,
- "hidden",
- 0,
- 0,
- [idx, null]
- ]);
+ newStack.push([idx + 7, "hidden", 0, 0, [idx, null]]);
} else {
- newStack.push([
- idx + 7,
- "hidden",
- 0,
- 0,
- [idx, idx + 8]
- ]);
+ newStack.push([idx + 7, "hidden", 0, 0, [idx, idx + 8]]);
previousBlock = idx + 7;
}
} else {
@@ -1718,28 +2085,26 @@ class RhythmRuler {
}
newStack.push([
idx + 1,
- ["number", { value: sameNoteValue }],
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
0,
0,
[idx]
]);
- newStack.push([
- idx + 2,
- "newnote",
- 0,
- 0,
- [idx, idx + 3, idx + 6, idx + 9]
- ]);
- newStack.push([
- idx + 3,
- "divide",
- 0,
- 0,
- [idx + 2, idx + 4, idx + 5]
- ]);
+ newStack.push([idx + 2, "newnote", 0, 0, [idx, idx + 3, idx + 6, idx + 9]]);
+ newStack.push([idx + 3, "divide", 0, 0, [idx + 2, idx + 4, idx + 5]]);
newStack.push([
idx + 4,
- ["number", { value: 1 }],
+ [
+ "number",
+ {
+ value: 1
+ }
+ ],
0,
0,
[idx + 3]
@@ -1747,58 +2112,43 @@ class RhythmRuler {
if (noteValue < 0) {
newStack.push([
idx + 5,
- ["number", { value: -noteValue }],
+ [
+ "number",
+ {
+ value: -noteValue
+ }
+ ],
0,
0,
[idx + 3]
]);
- newStack.push([
- idx + 6,
- "vspace",
- 0,
- 0,
- [idx + 2, idx + 7]
- ]);
- newStack.push([
- idx + 7,
- "rest2",
- 0,
- 0,
- [idx + 6, idx + 8]
- ]);
- newStack.push([
- idx + 8,
- "hidden",
- 0,
- 0,
- [idx + 7, null]
- ]);
+ newStack.push([idx + 6, "vspace", 0, 0, [idx + 2, idx + 7]]);
+ newStack.push([idx + 7, "rest2", 0, 0, [idx + 6, idx + 8]]);
+ newStack.push([idx + 8, "hidden", 0, 0, [idx + 7, null]]);
} else {
newStack.push([
idx + 5,
- ["number", { value: noteValue }],
+ [
+ "number",
+ {
+ value: noteValue
+ }
+ ],
0,
0,
[idx + 3]
]);
- newStack.push([
- idx + 6,
- "vspace",
- 0,
- 0,
- [idx + 2, idx + 7]
- ]);
- newStack.push([
- idx + 7,
- "playdrum",
- 0,
- 0,
- [idx + 6, idx + 8, null]
- ]);
+ newStack.push([idx + 6, "vspace", 0, 0, [idx + 2, idx + 7]]);
+ newStack.push([idx + 7, "playdrum", 0, 0, [idx + 6, idx + 8, null]]);
if (effect) {
newStack.push([
idx + 8,
- ["effectsname", { value: drum }],
+ [
+ "effectsname",
+ {
+ value: drum
+ }
+ ],
0,
0,
[idx + 7]
@@ -1806,20 +2156,19 @@ class RhythmRuler {
} else {
newStack.push([
idx + 8,
- ["drumname", { value: drum }],
+ [
+ "drumname",
+ {
+ value: drum
+ }
+ ],
0,
0,
[idx + 7]
]);
}
}
- newStack.push([
- idx + 9,
- "hidden",
- 0,
- 0,
- [idx + 2, null]
- ]);
+ newStack.push([idx + 9, "hidden", 0, 0, [idx + 2, null]]);
}
sameNoteValue = 1;
@@ -1837,16 +2186,16 @@ class RhythmRuler {
}
_saveVoiceMachine(selectedRuler, voice) {
- for (let name in logo.blocks.palettes.dict) {
+ for (const name in logo.blocks.palettes.dict) {
logo.blocks.palettes.dict[name].hideMenu(true);
}
logo.refreshCanvas();
setTimeout(() => {
- let ruler = this._rulers[selectedRuler];
- let noteValues = this.Rulers[selectedRuler][0];
- let delta = selectedRuler * 42;
+ const ruler = this._rulers[selectedRuler];
+ const noteValues = this.Rulers[selectedRuler][0];
+ const delta = selectedRuler * 42;
// Just save the action, not the drum machine itself.
// let newStack = [[0, ['start', {'collapsed': false}], 100 + delta, 100 + delta, [null, 1, null]]];
@@ -1858,867 +2207,376 @@ class RhythmRuler {
// This should never happen.
let action_name;
- if (this.Drums[selectedRuler] === null) {
- action_name = _("guitar") + " " + _("action");
- } else {
- let action_name =
- logo.blocks.blockList[
- logo.blocks.blockList[this.Drums[selectedRuler]]
- .connections[1]
- ].value.split(" ")[0] +
- "_" +
- _("action");
- }
-
- let newStack = [
- [
- 0,
- ["action", { collapsed: true }],
- 100 + delta,
- 100 + delta,
- [null, 1, 2, null]
- ],
- [1, ["text", { value: action_name }], 0, 0, [0]]
- ];
- newStack.push([2, "settimbre", 0, 0, [0, 3, 5, 4]]);
- newStack.push([3, ["voicename", { value: voice }], 0, 0, [2]]);
- newStack.push([4, "hidden", 0, 0, [2, null]]);
- let previousBlock = 2;
- let sameNoteValue = 1;
- for (let i = 0; i < ruler.cells.length; i++) {
- if (
- noteValues[i] === noteValues[i + 1] &&
- i < ruler.cells.length - 1
- ) {
- sameNoteValue += 1;
- continue;
- } else {
- let idx = newStack.length;
- let noteValue = noteValues[i];
-
- let obj = rationalToFraction(1 / Math.abs(noteValue));
-
- if (sameNoteValue === 1) {
- // Add a note block.
- if (noteValue < 0) {
- newStack.push([
- idx,
- "newnote",
- 0,
- 0,
- [previousBlock, idx + 1, idx + 4, idx + 7]
- ]);
- newStack.push([
- idx + 1,
- "divide",
- 0,
- 0,
- [idx, idx + 2, idx + 3]
- ]);
- newStack.push([
- idx + 2,
- ["number", { value: obj[0] }],
- 0,
- 0,
- [idx + 1]
- ]);
- newStack.push([
- idx + 3,
- ["number", { value: obj[1] }],
- 0,
- 0,
- [idx + 1]
- ]);
- newStack.push([
- idx + 4,
- "vspace",
- 0,
- 0,
- [idx, idx + 5]
- ]);
- newStack.push([
- idx + 5,
- "rest2",
- 0,
- 0,
- [idx + 4, idx + 6]
- ]);
- newStack.push([
- idx + 6,
- "hidden",
- 0,
- 0,
- [idx + 5, null]
- ]);
- if (i == ruler.cells.length - 1) {
- newStack.push([
- idx + 7,
- "hidden",
- 0,
- 0,
- [idx, null]
- ]);
- } else {
- newStack.push([
- idx + 7,
- "hidden",
- 0,
- 0,
- [idx, idx + 8]
- ]);
- previousBlock = idx + 7;
- }
- } else {
- newStack.push([
- idx,
- "newnote",
- 0,
- 0,
- [previousBlock, idx + 1, idx + 4, idx + 8]
- ]);
- newStack.push([
- idx + 1,
- "divide",
- 0,
- 0,
- [idx, idx + 2, idx + 3]
- ]);
- newStack.push([
- idx + 2,
- ["number", { value: obj[0] }],
- 0,
- 0,
- [idx + 1]
- ]);
- newStack.push([
- idx + 3,
- ["number", { value: obj[1] }],
- 0,
- 0,
- [idx + 1]
- ]);
- newStack.push([
- idx + 4,
- "vspace",
- 0,
- 0,
- [idx, idx + 5]
- ]);
- newStack.push([
- idx + 5,
- "pitch",
- 0,
- 0,
- [idx + 4, idx + 6, idx + 7, null]
- ]);
- newStack.push([
- idx + 6,
- ["notename", { value: "C" }],
- 0,
- 0,
- [idx + 5]
- ]);
- newStack.push([
- idx + 7,
- ["number", { value: 4 }],
- 0,
- 0,
- [idx + 5]
- ]);
- if (i == ruler.cells.length - 1) {
- newStack.push([
- idx + 8,
- "hidden",
- 0,
- 0,
- [idx, null]
- ]);
- } else {
- newStack.push([
- idx + 8,
- "hidden",
- 0,
- 0,
- [idx, idx + 9]
- ]);
- previousBlock = idx + 8;
- }
- }
- } else {
- // Add a note block inside a repeat block.
- if (i == ruler.cells.length - 1) {
- newStack.push([
- idx,
- "repeat",
- 0,
- 0,
- [previousBlock, idx + 1, idx + 2, null]
- ]);
- } else {
- newStack.push([
- idx,
- "repeat",
- 0,
- 0,
- [previousBlock, idx + 1, idx + 2, idx + 11]
- ]);
- previousBlock = idx;
- }
- newStack.push([
- idx + 1,
- ["number", { value: sameNoteValue }],
- 0,
- 0,
- [idx]
- ]);
- if (noteValue < 0) {
- newStack.push([
- idx + 2,
- "newnote",
- 0,
- 0,
- [idx, idx + 3, idx + 6, idx + 9]
- ]);
- newStack.push([
- idx + 3,
- "divide",
- 0,
- 0,
- [idx + 2, idx + 4, idx + 5]
- ]);
- newStack.push([
- idx + 4,
- ["number", { value: 1 }],
- 0,
- 0,
- [idx + 3]
- ]);
- newStack.push([
- idx + 5,
- ["number", { value: -noteValue }],
- 0,
- 0,
- [idx + 3]
- ]);
- newStack.push([
- idx + 6,
- "vspace",
- 0,
- 0,
- [idx + 2, idx + 7]
- ]);
- newStack.push([
- idx + 7,
- "rest2",
- 0,
- 0,
- [idx + 6, idx + 8]
- ]);
- newStack.push([
- idx + 8,
- "hidden",
- 0,
- 0,
- [idx + 7, null]
- ]);
- newStack.push([
- idx + 9,
- "hidden",
- 0,
- 0,
- [idx + 2, null]
- ]);
- } else {
- newStack.push([
- idx + 2,
- "newnote",
- 0,
- 0,
- [idx, idx + 3, idx + 6, idx + 10]
- ]);
- newStack.push([
- idx + 3,
- "divide",
- 0,
- 0,
- [idx + 2, idx + 4, idx + 5]
- ]);
- newStack.push([
- idx + 4,
- ["number", { value: 1 }],
- 0,
- 0,
- [idx + 3]
- ]);
- newStack.push([
- idx + 5,
- ["number", { value: noteValue }],
- 0,
- 0,
- [idx + 3]
- ]);
- newStack.push([
- idx + 6,
- "vspace",
- 0,
- 0,
- [idx + 2, idx + 7]
- ]);
- newStack.push([
- idx + 7,
- "pitch",
- 0,
- 0,
- [idx + 6, idx + 8, idx + 9, null]
- ]);
- newStack.push([
- idx + 8,
- ["notename", { value: "C" }],
- 0,
- 0,
- [idx + 7]
- ]);
- newStack.push([
- idx + 9,
- ["number", { value: 4 }],
- 0,
- 0,
- [idx + 7]
- ]);
- newStack.push([
- idx + 10,
- "hidden",
- 0,
- 0,
- [idx + 2, null]
- ]);
- }
- }
-
- sameNoteValue = 1;
- }
- }
-
- logo.blocks.loadNewBlocks(newStack);
- if (selectedRuler > this.Rulers.length - 2) {
- return;
- } else {
- this._saveMachine(selectedRuler + 1);
- }
- }, 500);
- }
-
- _mergeRulers() {
- // Merge the rulers into one set of rhythms.
- let rList = [];
- let noteValues;
- for (let r = 0; r < this.Rulers.length; r++) {
- let t = 0;
- let selectedRuler = this.Rulers[r];
- noteValues = selectedRuler[0];
- for (let i = 0; i < noteValues.length; i++) {
- t += 1 / noteValues[i];
- if (rList.indexOf(t) === -1) {
- rList.push(t);
- }
- }
- }
-
- rList.sort(function(a, b) {
- return a - b;
- });
-
- noteValues = [];
- for (let i = 0; i < rList.length; i++) {
- if (i === 0) {
- noteValues.push(1 / rList[i]);
- } else {
- noteValues.push(1 / (rList[i] - rList[i - 1]));
- }
- }
-
- return noteValues;
- }
-
- _get_save_lock() {
- return this._save_lock;
- }
-
- constructor() {
- console.debug("init RhythmRuler");
-
- this._bpmFactor = (1000 * TONEBPM) / Singer.masterBPM;
-
- this._playing = false;
- this._playingOne = false;
- this._playingAll = false;
- this._rulerPlaying = -1;
- this._startingTime = null;
- this._expanded = false;
-
- // There is one ruler per drum.
- this.Drums = [];
- // Rulers, one per drum, contain the subdivisions defined by rhythm blocks.
- this.Rulers = [];
- // Save the history of divisions so as to be able to restore them.
- this._dissectHistory = [];
- this._undoList = [];
-
- this._playing = false;
- this._playingOne = false;
- this._playingAll = false;
- this._cellCounter = 0;
-
- // Keep a elapsed time for each ruler to maintain sync.
- this._elapsedTimes = [];
- // Starting time from which we measure for sync.
- this._startingTime = null;
-
- this._offsets = [];
- this._rulerSelected = 0;
- this._rulerPlaying = -1;
-
- this._tapMode = false;
- this._tapTimes = [];
- this._tapCell = null;
- this._tapEndTime = null;
-
- this._longPressStartTime = null;
- this._inLongPress = false;
-
- this._mouseDownCell = null;
- this._mouseUpCell = null;
-
- this._wheel = null;
-
- // Element references
- this._dissectNumber = null;
- this._progressBar = null;
- this._rulers = [];
-
- // If there are no drums, add one.
- if (this.Drums.length === 0) {
- this.Drums.push(null);
- this.Rulers.push([[1], []]);
- }
-
- this._elapsedTimes = [];
- this._offsets = [];
- for (let i = 0; i < this.Rulers.length; i++) {
- this._elapsedTimes.push(0);
- this._offsets.push(0);
- }
-
- let w = window.innerWidth;
- this._cellScale = 1.0;
-
- let widgetWindow = window.widgetWindows.windowFor(this, "rhythm maker");
- this.widgetWindow = widgetWindow;
- widgetWindow.clear();
- widgetWindow.show();
-
- // For the button callbacks
-
- widgetWindow.onclose = () => {
- // If the piemenu was open, close it.
- // docById('wheelDiv').style.display = 'none';
- // docById('contextWheelDiv').style.display = 'none';
-
- // Save the new dissect history.
- let dissectHistory = [];
- let drums = [];
- for (let i = 0; i < this.Rulers.length; i++) {
- if (this.Drums[i] === null) {
- continue;
- }
-
- let history = [];
- for (let j = 0; j < this.Rulers[i][1].length; j++) {
- history.push(this.Rulers[i][1][j]);
- }
-
- this._dissectNumber.classList.add("hasKeyboard");
- dissectHistory.push([history, this.Drums[i]]);
- drums.push(this.Drums[i]);
- }
-
- // Look for any old entries that we may have missed.
- for (let i = 0; i < this._dissectHistory.length; i++) {
- let drum = this._dissectHistory[i][1];
- if (drums.indexOf(drum) === -1) {
- let history = JSON.parse(
- JSON.stringify(this._dissectHistory[i][0])
- );
- dissectHistory.push([history, drum]);
- }
- }
-
- this._dissectHistory = JSON.parse(JSON.stringify(dissectHistory));
-
- this._playing = false;
- this._playingOne = false;
- this._playingAll = false;
- logo.hideMsgs();
-
- this.widgetWindow.destroy();
- };
-
- this._playAllCell = widgetWindow.addButton(
- "play-button.svg",
- RhythmRuler.ICONSIZE,
- _("Play all")
- );
- this._playAllCell.onclick = () => {
- if (this._playing) {
- this.__pause();
- } else if (!this._playingAll) {
- this.__resume();
- }
- };
-
- this._save_lock = false;
- widgetWindow.addButton(
- "export-chunk.svg",
- RhythmRuler.ICONSIZE,
- _("Save rhythms")
- ).onclick = async () => {
- // that._save(0);
- // Debounce button
- if (!this._get_save_lock()) {
- this._save_lock = true;
-
- // Save a merged version of the rulers.
- this._saveTupletsMerged(this._mergeRulers());
-
- // Rather than each ruler individually.
- // that._saveTuplets(0);
- await delayExecution(1000);
- this._save_lock = false;
- }
- };
-
- widgetWindow.addButton(
- "export-drums.svg",
- RhythmRuler.ICONSIZE,
- _("Save drum machine")
- ).onclick = async () =>{
- // Debounce button
- if (!this._get_save_lock()) {
- this._save_lock = true;
- this._saveMachine(0);
- await delayExecution(1000);
- this._save_lock = false;
- }
- };
-
- // An input for setting the dissect number
- this._dissectNumber = widgetWindow.addInputButton("2");
-
- this._dissectNumber.onfocus = (event) => {
- // that._piemenuNumber(['2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'], numberInput.value);
- };
-
- this._dissectNumber.onkeydown = (event) => {
- // 46 number is for the delete keypress
- if (event.keyCode === 46) {
- this._dissectNumber.value = this._dissectNumber.value.substring(
- 0,
- this._dissectNumber.value.length - 1
- );
- }
- };
-
- this._dissectNumber.oninput = (event) => {
- // Put a limit on the size (2 <--> 128).
- this._dissectNumber.onmouseout = () => {
- this._dissectNumber.value = Math.max(
- this._dissectNumber.value,
- 2
- );
- };
-
- this._dissectNumber.value = Math.max(
- Math.min(this._dissectNumber.value, 128),
- 2
- );
- };
-
- widgetWindow.addButton(
- "restore-button.svg",
- RhythmRuler.ICONSIZE,
- _("Undo")
- ).onclick = () => {
- this._undo();
- };
-
- //.TRANS: user can tap out a rhythm by clicking on a ruler.
- this._tapButton = widgetWindow.addButton(
- "tap-button.svg",
- RhythmRuler.ICONSIZE,
- _("Tap a rhythm")
- );
- this._tapButton.onclick = () => {
- this._tap();
- };
-
- //.TRANS: clear all subdivisions from the ruler.
- widgetWindow.addButton(
- "erase-button.svg",
- RhythmRuler.ICONSIZE,
- _("Clear")
- ).onclick = () => {
- this._clear();
- };
-
- // We use an outer div to scroll vertically and an inner div to
- // scroll horizontally.
- let rhythmRulerTable = document.createElement("table");
- widgetWindow.getWidgetBody().append(rhythmRulerTable);
-
- let wMax = 0;
- // Each row in the ruler table contains a play button in the
- // first column and a ruler table in the second column.
- for (let i = 0; i < this.Rulers.length; i++) {
- let rhythmRulerTableRow = rhythmRulerTable.insertRow();
-
- if (beginnerMode) {
- let w = 0;
- for (let r = 0; r < this.Rulers[i][0].length; r++) {
- w += 580 / this.Rulers[i][0][r];
- }
-
- if (w > wMax) {
- rhythmRulerTable.style.width = w + "px";
- wMax = w;
- }
- } else {
- let drumcell = rhythmRulerTableRow.insertCell();
- drumcell.innerHTML =
- '';
- drumcell.className = "headcol"; // Position fixed when scrolling horizontally
-
- drumcell.onclick = ((id) => {
- return () => {
- if (this._playing) {
- if (this._rulerPlaying === id) {
- this.innerHTML =
- '';
- this._playing = false;
- this._playingOne = false;
- this._playingAll = false;
- this._rulerPlaying = -1;
- this._startingTime = null;
- this._elapsedTimes[id] = 0;
- this._offsets[id] = 0;
- setTimeout(
- this._calculateZebraStripes(id),
- 1000
- );
- }
- } else {
- if (this._playingOne === false) {
- this._rulerSelected = id;
- logo.turtleDelay = 0;
- this._playing = true;
- this._playingOne = true;
- this._playingAll = false;
- this._cellCounter = 0;
- this._startingTime = null;
- this._rulerPlaying = id;
- this.innerHTML =
- '';
- this._elapsedTimes[id] = 0;
- this._offsets[id] = 0;
- this._playOne();
- }
- }
- };
- })(i);
- }
-
- let rulerCell = rhythmRulerTableRow.insertCell();
- // Create individual rulers as tables.
- rulerCell.innerHTML =
- '';
-
- let rulerCellTable = docById("rulerCellTable" + i);
- rulerCellTable.style.textAlign = "center";
- rulerCellTable.style.border = "0px";
- rulerCellTable.style.borderCollapse = "collapse";
- rulerCellTable.cellSpacing = "0px";
- rulerCellTable.cellPadding = "0px";
- let rulerRow = rulerCellTable.insertRow();
- this._rulers[i] = rulerRow;
- rulerRow.setAttribute("data-row", i);
-
- for (let j = 0; j < this.Rulers[i][0].length; j++) {
- let noteValue = this.Rulers[i][0][j];
- let rulerSubCell = rulerRow.insertCell(-1);
- rulerSubCell.innerHTML = calcNoteValueToDisplay(
- noteValue,
- 1,
- this._cellScale
- );
- rulerSubCell.style.height = RhythmRuler.RULERHEIGHT + "px";
- rulerSubCell.style.minHeight = rulerSubCell.style.height;
- rulerSubCell.style.maxHeight = rulerSubCell.style.height;
- rulerSubCell.style.width = this._noteWidth(noteValue) + "px";
- rulerSubCell.style.minWidth = rulerSubCell.style.width;
- rulerSubCell.style.border = "0px";
- rulerSubCell.border = "0px";
- rulerSubCell.padding = "0px";
- rulerSubCell.style.padding = "0px";
- rulerSubCell.style.lineHeight = 60 + " % ";
- if (i % 2 === 0) {
- if (j % 2 === 0) {
- rulerSubCell.style.backgroundColor =
- platformColor.selectorBackground;
- } else {
- rulerSubCell.style.backgroundColor =
- platformColor.selectorSelected;
- }
- } else {
- if (j % 2 === 0) {
- rulerSubCell.style.backgroundColor =
- platformColor.selectorSelected;
- } else {
- rulerSubCell.style.backgroundColor =
- platformColor.selectorBackground;
- }
- }
-
- this.__addCellEventHandlers(
- rulerSubCell,
- this._noteWidth(noteValue),
- noteValue
- );
- }
-
- // Match the play button height to the ruler height.
- rhythmRulerTableRow.cells[0].style.width = RhythmRuler.BUTTONSIZE + "px";
- rhythmRulerTableRow.cells[0].style.minWidth = RhythmRuler.BUTTONSIZE + "px";
- rhythmRulerTableRow.cells[0].style.maxWidth = RhythmRuler.BUTTONSIZE + "px";
- rhythmRulerTableRow.cells[0].style.height =
- rulerRow.offsetHeight + "px";
- rhythmRulerTableRow.cells[0].style.minHeight =
- rulerRow.offsetHeight + "px";
- rhythmRulerTableRow.cells[0].style.maxHeight =
- rulerRow.offsetHeight + "px";
- rhythmRulerTableRow.cells[0].style.verticalAlign = "middle";
- }
-
- // Restore dissect history.
- let cell;
- for (let drum = 0; drum < this.Drums.length; drum++) {
- if (this.Drums[i] === null) {
- continue;
+ if (this.Drums[selectedRuler] === null) {
+ action_name = _("guitar") + " " + _("action");
+ } else {
+ // const action_name =
+ // logo.blocks.blockList[
+ // logo.blocks.blockList[this.Drums[selectedRuler]].connections[1]
+ // ].value.split(" ")[0] +
+ // "_" +
+ // _("action");
}
- for (let i = 0; i < this._dissectHistory.length; i++) {
- if (this._dissectHistory[i][1] !== this.Drums[drum]) {
- continue;
- }
-
- let rhythmRulerTableRow = this._rulers[drum];
- for (let j = 0; j < this._dissectHistory[i][0].length; j++) {
- if (this._dissectHistory[i][0][j] == undefined) {
- continue;
+ const newStack = [
+ [
+ 0,
+ [
+ "action",
+ {
+ collapsed: true
+ }
+ ],
+ 100 + delta,
+ 100 + delta,
+ [null, 1, 2, null]
+ ],
+ [
+ 1,
+ [
+ "text",
+ {
+ value: action_name
+ }
+ ],
+ 0,
+ 0,
+ [0]
+ ]
+ ];
+ newStack.push([2, "settimbre", 0, 0, [0, 3, 5, 4]]);
+ newStack.push([
+ 3,
+ [
+ "voicename",
+ {
+ value: voice
}
+ ],
+ 0,
+ 0,
+ [2]
+ ]);
+ newStack.push([4, "hidden", 0, 0, [2, null]]);
+ let previousBlock = 2;
+ let sameNoteValue = 1;
+ for (let i = 0; i < ruler.cells.length; i++) {
+ if (noteValues[i] === noteValues[i + 1] && i < ruler.cells.length - 1) {
+ sameNoteValue += 1;
+ continue;
+ } else {
+ const idx = newStack.length;
+ const noteValue = noteValues[i];
- this._rulerSelected = drum;
+ const obj = rationalToFraction(1 / Math.abs(noteValue));
- if (typeof this._dissectHistory[i][0][j] === "number") {
- cell =
- rhythmRulerTableRow.cells[
- this._dissectHistory[i][0][j]
- ];
- this.__toggleRestState(cell, false);
- } else if (
- typeof this._dissectHistory[i][0][j][0] === "number"
- ) {
- if (
- typeof this._dissectHistory[i][0][j][1] === "number"
- ) {
- // dissect is [cell, num]
- cell =
- rhythmRulerTableRow.cells[
- this._dissectHistory[i][0][j][0]
- ];
- if (cell != undefined) {
- this.__dissectByNumber(
- cell,
- this._dissectHistory[i][0][j][1],
- false
- );
+ if (sameNoteValue === 1) {
+ // Add a note block.
+ if (noteValue < 0) {
+ newStack.push([
+ idx,
+ "newnote",
+ 0,
+ 0,
+ [previousBlock, idx + 1, idx + 4, idx + 7]
+ ]);
+ newStack.push([idx + 1, "divide", 0, 0, [idx, idx + 2, idx + 3]]);
+ newStack.push([
+ idx + 2,
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
+ 0,
+ 0,
+ [idx + 1]
+ ]);
+ newStack.push([
+ idx + 3,
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
+ 0,
+ 0,
+ [idx + 1]
+ ]);
+ newStack.push([idx + 4, "vspace", 0, 0, [idx, idx + 5]]);
+ newStack.push([idx + 5, "rest2", 0, 0, [idx + 4, idx + 6]]);
+ newStack.push([idx + 6, "hidden", 0, 0, [idx + 5, null]]);
+ if (i == ruler.cells.length - 1) {
+ newStack.push([idx + 7, "hidden", 0, 0, [idx, null]]);
} else {
- console.warn(
- "Could not find cell to divide. Did the order of the rhythm blocks change?"
- );
+ newStack.push([idx + 7, "hidden", 0, 0, [idx, idx + 8]]);
+ previousBlock = idx + 7;
}
} else {
- // divide is [cell, [values]]
- cell =
- rhythmRulerTableRow.cells[
- this._dissectHistory[i][0][j][0]
- ];
- if (cell != undefined) {
- this.__divideFromList(
- cell,
- this._dissectHistory[i][0][j][1],
- false
- );
+ newStack.push([
+ idx,
+ "newnote",
+ 0,
+ 0,
+ [previousBlock, idx + 1, idx + 4, idx + 8]
+ ]);
+ newStack.push([idx + 1, "divide", 0, 0, [idx, idx + 2, idx + 3]]);
+ newStack.push([
+ idx + 2,
+ [
+ "number",
+ {
+ value: obj[0]
+ }
+ ],
+ 0,
+ 0,
+ [idx + 1]
+ ]);
+ newStack.push([
+ idx + 3,
+ [
+ "number",
+ {
+ value: obj[1]
+ }
+ ],
+ 0,
+ 0,
+ [idx + 1]
+ ]);
+ newStack.push([idx + 4, "vspace", 0, 0, [idx, idx + 5]]);
+ newStack.push([
+ idx + 5,
+ "pitch",
+ 0,
+ 0,
+ [idx + 4, idx + 6, idx + 7, null]
+ ]);
+ newStack.push([
+ idx + 6,
+ [
+ "notename",
+ {
+ value: "C"
+ }
+ ],
+ 0,
+ 0,
+ [idx + 5]
+ ]);
+ newStack.push([
+ idx + 7,
+ [
+ "number",
+ {
+ value: 4
+ }
+ ],
+ 0,
+ 0,
+ [idx + 5]
+ ]);
+ if (i == ruler.cells.length - 1) {
+ newStack.push([idx + 8, "hidden", 0, 0, [idx, null]]);
+ } else {
+ newStack.push([idx + 8, "hidden", 0, 0, [idx, idx + 9]]);
+ previousBlock = idx + 8;
}
}
} else {
- // tie is [[cell, value], [cell, value]...]
- let history = this._dissectHistory[i][0][j];
- this._mouseDownCell =
- rhythmRulerTableRow.cells[history[0][0]];
- this._mouseUpCell =
- rhythmRulerTableRow.cells[last(history)[0]];
- if (this._mouseUpCell != undefined) {
- this.__tie(false);
+ // Add a note block inside a repeat block.
+ if (i == ruler.cells.length - 1) {
+ newStack.push([
+ idx,
+ "repeat",
+ 0,
+ 0,
+ [previousBlock, idx + 1, idx + 2, null]
+ ]);
+ } else {
+ newStack.push([
+ idx,
+ "repeat",
+ 0,
+ 0,
+ [previousBlock, idx + 1, idx + 2, idx + 11]
+ ]);
+ previousBlock = idx;
+ }
+ newStack.push([
+ idx + 1,
+ [
+ "number",
+ {
+ value: sameNoteValue
+ }
+ ],
+ 0,
+ 0,
+ [idx]
+ ]);
+ if (noteValue < 0) {
+ newStack.push([
+ idx + 2,
+ "newnote",
+ 0,
+ 0,
+ [idx, idx + 3, idx + 6, idx + 9]
+ ]);
+ newStack.push([idx + 3, "divide", 0, 0, [idx + 2, idx + 4, idx + 5]]);
+ newStack.push([
+ idx + 4,
+ [
+ "number",
+ {
+ value: 1
+ }
+ ],
+ 0,
+ 0,
+ [idx + 3]
+ ]);
+ newStack.push([
+ idx + 5,
+ [
+ "number",
+ {
+ value: -noteValue
+ }
+ ],
+ 0,
+ 0,
+ [idx + 3]
+ ]);
+ newStack.push([idx + 6, "vspace", 0, 0, [idx + 2, idx + 7]]);
+ newStack.push([idx + 7, "rest2", 0, 0, [idx + 6, idx + 8]]);
+ newStack.push([idx + 8, "hidden", 0, 0, [idx + 7, null]]);
+ newStack.push([idx + 9, "hidden", 0, 0, [idx + 2, null]]);
+ } else {
+ newStack.push([
+ idx + 2,
+ "newnote",
+ 0,
+ 0,
+ [idx, idx + 3, idx + 6, idx + 10]
+ ]);
+ newStack.push([idx + 3, "divide", 0, 0, [idx + 2, idx + 4, idx + 5]]);
+ newStack.push([
+ idx + 4,
+ [
+ "number",
+ {
+ value: 1
+ }
+ ],
+ 0,
+ 0,
+ [idx + 3]
+ ]);
+ newStack.push([
+ idx + 5,
+ [
+ "number",
+ {
+ value: noteValue
+ }
+ ],
+ 0,
+ 0,
+ [idx + 3]
+ ]);
+ newStack.push([idx + 6, "vspace", 0, 0, [idx + 2, idx + 7]]);
+ newStack.push([
+ idx + 7,
+ "pitch",
+ 0,
+ 0,
+ [idx + 6, idx + 8, idx + 9, null]
+ ]);
+ newStack.push([
+ idx + 8,
+ [
+ "notename",
+ {
+ value: "C"
+ }
+ ],
+ 0,
+ 0,
+ [idx + 7]
+ ]);
+ newStack.push([
+ idx + 9,
+ [
+ "number",
+ {
+ value: 4
+ }
+ ],
+ 0,
+ 0,
+ [idx + 7]
+ ]);
+ newStack.push([idx + 10, "hidden", 0, 0, [idx + 2, null]]);
}
-
- this._mouseDownCell = null;
- this._mouseUpCell = null;
}
+
+ sameNoteValue = 1;
+ }
+ }
+
+ logo.blocks.loadNewBlocks(newStack);
+ if (selectedRuler > this.Rulers.length - 2) {
+ return;
+ } else {
+ this._saveMachine(selectedRuler + 1);
+ }
+ }, 500);
+ }
+
+ _mergeRulers() {
+ // Merge the rulers into one set of rhythms.
+ const rList = [];
+ let noteValues;
+ for (let r = 0; r < this.Rulers.length; r++) {
+ let t = 0;
+ const selectedRuler = this.Rulers[r];
+ noteValues = selectedRuler[0];
+ for (let i = 0; i < noteValues.length; i++) {
+ t += 1 / noteValues[i];
+ if (rList.indexOf(t) === -1) {
+ rList.push(t);
}
}
}
- logo.textMsg(_("Click on the ruler to divide it."));
- // this._piemenuRuler(this._rulerSelected);
+ rList.sort(function (a, b) {
+ return a - b;
+ });
+
+ noteValues = [];
+ for (let i = 0; i < rList.length; i++) {
+ if (i === 0) {
+ noteValues.push(1 / rList[i]);
+ } else {
+ noteValues.push(1 / (rList[i] - rList[i - 1]));
+ }
+ }
+
+ return noteValues;
}
- saveDissectHistory () {
+ _get_save_lock() {
+ return this._save_lock;
+ }
+
+ saveDissectHistory() {
// Save the new dissect history.
- let dissectHistory = [];
- let drums = [];
+ const dissectHistory = [];
+ const drums = [];
let drum;
let history;
for (let i = 0; i < this.Rulers.length; i++) {
@@ -2740,17 +2598,18 @@ class RhythmRuler {
for (let i = 0; i < this._dissectHistory.length; i++) {
drum = this._dissectHistory[i][1];
if (drums.indexOf(drum) === -1) {
- history = JSON.parse(
- JSON.stringify(this._dissectHistory[i][0])
- );
+ history = JSON.parse(JSON.stringify(this._dissectHistory[i][0]));
dissectHistory.push([history, drum]);
}
}
this._dissectHistory = JSON.parse(JSON.stringify(dissectHistory));
- };
+ }
- _piemenuRuler(selectedRuler) {
+ /**
+ * @deprecated
+ */
+ _piemenuRuler(/* selectedRuler */) {
return; // In progress
/*
// piemenu version of ruler
@@ -2808,7 +2667,7 @@ class RhythmRuler {
// exit button
this._exitWheel = new wheelnav("_exitWheel", this._numberWheel.raphael);
- let wheelLabels = [];
+ const wheelLabels = [];
for (let i = 0; i < wheelValues.length; i++) {
wheelLabels.push(wheelValues[i].toString());
}
@@ -2846,14 +2705,12 @@ class RhythmRuler {
this._exitWheel.clickModeRotate = false;
this._exitWheel.createWheel(["x", " "]);
-
const __selectionChanged = () => {
- this._dissectNumber.value =
- wheelValues[this._numberWheel.selectedNavItemIndex];
+ this._dissectNumber.value = wheelValues[this._numberWheel.selectedNavItemIndex];
};
const __exitMenu = () => {
- let d = new Date();
+ const d = new Date();
this._piemenuExitTime = d.getTime();
docById("wheelDiv").style.display = "none";
this._numberWheel.removeWheel();
@@ -2872,14 +2729,14 @@ class RhythmRuler {
// Hide the widget when the selection is made.
for (let i = 0; i < wheelLabels.length; i++) {
- this._numberWheel.navItems[i].navigateFunction = function() {
+ this._numberWheel.navItems[i].navigateFunction = function () {
__selectionChanged();
__exitMenu();
};
}
// Or use the exit wheel...
- this._exitWheel.navItems[0].navigateFunction = function() {
+ this._exitWheel.navItems[0].navigateFunction = function () {
__exitMenu();
};
}
@@ -2894,9 +2751,9 @@ class RhythmRuler {
docById("wheelDiv").style.width = "300px";
// Position the widget over the note block.
- let x = this._left + 100;
- let y = this._top;
- let selectorWidth = 150;
+ const x = this._left + 100;
+ const y = this._top;
+ const selectorWidth = 150;
docById("wheelDiv").style.left =
Math.min(