From 296bee847f77822dee73603ea43db7136dcdfa21 Mon Sep 17 00:00:00 2001 From: Henrique Lorenzi Date: Sun, 14 May 2017 21:41:10 -0300 Subject: [PATCH] add measure/line breaks --- index.html | 17 ++++++++ src/editor/editor.js | 86 ++++++++++++++++++++++++++++++++++++++-- src/main.js | 12 ++++++ src/song/measurebreak.js | 14 +++++++ src/song/song.js | 57 +++++++++++++++++++++++++- style.css | 19 +++++++++ 6 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 src/song/measurebreak.js diff --git a/index.html b/index.html index e1e5358..1e72b4c 100644 --- a/index.html +++ b/index.html @@ -139,6 +139,22 @@ + + + Measure Break: + + + + + + + + Line Break: + + + + + @@ -200,6 +216,7 @@ + diff --git a/src/editor/editor.js b/src/editor/editor.js index 8137385..1b5c1fa 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -314,6 +314,30 @@ Editor.prototype.insertMeterChange = function(numerator, denominator) } +Editor.prototype.insertMeasureBreak = function() +{ + this.selectNone(); + this.eraseForcedMeasuresAt(this.cursorTick1, this.cursorTick1); + this.song.forcedMeasures.insert( + new SongMeasureBreak(this.cursorTick1.clone(), false, { selected: true })); + this.cursorSetTickBoth(this.cursorTick1.clone().min(this.cursorTick2)); + this.refresh(); + this.setUnsavedChanges(true); +} + + +Editor.prototype.insertLineBreak = function() +{ + this.selectNone(); + this.eraseForcedMeasuresAt(this.cursorTick1, this.cursorTick1); + this.song.forcedMeasures.insert( + new SongMeasureBreak(this.cursorTick1.clone(), true, { selected: true })); + this.cursorSetTickBoth(this.cursorTick1.clone().min(this.cursorTick2)); + this.refresh(); + this.setUnsavedChanges(true); +} + + Editor.prototype.updateSvgCursor = function(node, visible, tick, track1, track2) { if (!visible) @@ -554,7 +578,10 @@ Editor.prototype.refreshRow = function(rowTickStart, rowYStart) var currentBlockStart = rowTickStart.clone(); var currentKeyChange = this.song.keyChanges.findPrevious(rowTickStart); var currentMeterChange = this.song.meterChanges.findPrevious(rowTickStart); + var currentForcedMeasure = this.song.forcedMeasures.findPrevious(rowTickStart); var currentMeasureStart = currentMeterChange.tick.clone(); + if (currentForcedMeasure != null) + currentMeasureStart.max(currentForcedMeasure.tick); // Also find the lowest and highest pitch rows for notes in this row. var pitchRowMin = Theory.getPitchRow({ scaleIndex: 0, tonicMidiPitch: 0, accidentalOffset: 0 }, 12 * 5, that.usePopularNotation); @@ -574,6 +601,7 @@ Editor.prototype.refreshRow = function(rowTickStart, rowYStart) var nextKeyChange = this.song.keyChanges.findNext(currentBlockStart); var nextMeterChange = this.song.meterChanges.findNext(currentBlockStart); + var nextForcedMeasure = this.song.forcedMeasures.findNext(currentBlockStart); if (nextKeyChange != null && nextKeyChange.tick.compare(nextBlockEnd) < 0) nextBlockEnd = nextKeyChange.tick.clone(); @@ -581,6 +609,9 @@ Editor.prototype.refreshRow = function(rowTickStart, rowYStart) if (nextMeterChange != null && nextMeterChange.tick.compare(nextBlockEnd) < 0) nextBlockEnd = nextMeterChange.tick.clone(); + if (nextForcedMeasure != null && nextForcedMeasure.tick.compare(nextBlockEnd) < 0) + nextBlockEnd = nextForcedMeasure.tick.clone(); + if (this.song.length.compare(nextBlockEnd) < 0) nextBlockEnd = this.song.length.clone(); @@ -596,12 +627,13 @@ Editor.prototype.refreshRow = function(rowTickStart, rowYStart) { tickStart: currentBlockStart.clone(), tickEnd: nextBlockEnd.clone(), - measureStartTick: currentMeterChange.tick.clone(), + measureStartTick: currentMeasureStart, x: x + scaleLabelsOffset, y: rowYStart, width: width, key: currentKeyChange, meter: currentMeterChange, + forcedMeasure: nextForcedMeasure, drawScaleLabels: drawScaleLabels }; @@ -628,6 +660,19 @@ Editor.prototype.refreshRow = function(rowTickStart, rowYStart) currentMeasureStart = currentMeterChange.tick.clone(); } + if (nextForcedMeasure != null && nextForcedMeasure.tick.compare(nextBlockEnd) == 0) + { + currentForcedMeasure = nextForcedMeasure; + currentMeasureStart = currentForcedMeasure.tick.clone(); + + if (nextForcedMeasure.isLineBreak) + break; + } + + currentMeasureStart = currentMeterChange.tick.clone(); + if (currentForcedMeasure != null) + currentMeasureStart.max(currentForcedMeasure.tick); + if (this.song.length.compare(nextBlockEnd) == 0) break; } @@ -687,7 +732,7 @@ Editor.prototype.refreshBlock = function( rowHasKeyChange, rowHasMeterChange) { var that = this; - + // Add block layout to master list. // This is used for mouse interaction. block.elements = []; @@ -698,7 +743,10 @@ Editor.prototype.refreshBlock = function( block.trackMeterChangeYStart = block.trackKeyChangeYEnd; block.trackMeterChangeYEnd = block.trackMeterChangeYStart + (rowHasMeterChange ? 20 : 0); - block.trackNoteYStart = block.trackMeterChangeYEnd; + block.trackForcedMeasureYStart = block.trackMeterChangeYEnd; + block.trackForcedMeasureYEnd = block.trackForcedMeasureYStart; + + block.trackNoteYStart = block.trackForcedMeasureYEnd; block.trackNoteYEnd = block.trackNoteYStart + (pitchRowMax + 1 - pitchRowMin) * this.noteHeight; block.trackChordYStart = block.trackNoteYEnd; @@ -1108,6 +1156,38 @@ Editor.prototype.refreshBlock = function( }); } + // Render forced measure. + if (block.forcedMeasure != null && + block.forcedMeasure.tick.compare(block.tickEnd) == 0) + { + this.addSvgNode("editorForcedMeasureLine", "line", + { + x1: block.x + block.width, + y1: block.y + (block.trackForcedMeasureYStart + block.trackForcedMeasureYEnd) / 2, + x2: block.x + block.width, + y2: block.y + block.trackChordYEnd + }); + + this.addSvgNode( + "editorForcedMeasureHandle" + (block.forcedMeasure.editorData.selected ? "Selected" : ""), + "rect", + { + x: block.x + block.width - this.handleSize / 2, + y: block.y + (block.trackForcedMeasureYStart + block.trackForcedMeasureYEnd) / 2 - 1 - this.handleSize, + width: this.handleSize, + height: this.handleSize + }); + + block.elements.push( + { + forcedMeasure: block.forcedMeasure, + x: block.x + block.width - this.handleSize, + y: block.y + (block.trackForcedMeasureYStart + block.trackForcedMeasureYEnd) / 2 - 1 - this.handleSize, + width: this.handleSize * 2, + height: this.handleSize + }); + } + return block; } diff --git a/src/main.js b/src/main.js index 14025ae..1a080d9 100644 --- a/src/main.js +++ b/src/main.js @@ -293,6 +293,18 @@ function handleButtonInsertMeterChange() } +function handleButtonInsertMeasureBreak() +{ + g_Editor.insertMeasureBreak(); +} + + +function handleButtonInsertLineBreak() +{ + g_Editor.insertLineBreak(); +} + + function handleSelectChordKindsChange() { var selectedIndex = document.getElementById("selectChordKinds").selectedIndex; diff --git a/src/song/measurebreak.js b/src/song/measurebreak.js new file mode 100644 index 0000000..43adf55 --- /dev/null +++ b/src/song/measurebreak.js @@ -0,0 +1,14 @@ +function SongMeasureBreak(tick, isLineBreak, editorData = null) +{ + this.tick = tick; + this.isLineBreak = isLineBreak; + this.editorData = editorData; +} + + +SongMeasureBreak.prototype.clone = function() +{ + return new SongMeasureBreak( + this.tick.clone(), + this.isLineBreak); +} \ No newline at end of file diff --git a/src/song/song.js b/src/song/song.js index 5d2619b..e8d92e9 100644 --- a/src/song/song.js +++ b/src/song/song.js @@ -1,7 +1,7 @@ function Song() { - this.CURRENT_TEXT_VERSION = 1; - this.CURRENT_BINARY_VERSION = 1; + this.CURRENT_TEXT_VERSION = 2; + this.CURRENT_BINARY_VERSION = 2; this.length = new Rational(4); @@ -112,10 +112,14 @@ Song.prototype.feedSynth = function(synth, startTick, useChordPatterns = true) var meter = that.meterChanges.findPrevious(chord.startTick); var meterBeatLength = meter.getBeatLength(); + var measureBreak = that.forcedMeasures.findPrevious(chord.startTick); var pattern = Theory.getChordStrummingPattern(meter); var tick = meter.tick.clone(); + if (measureBreak != null) + tick.max(measureBreak.tick); + var skipTick = new Rational(0); var patternIndex = 0; var mustPlayFirstBeat = false; @@ -298,6 +302,23 @@ Song.prototype.saveJSON = function() json += "\n"; } + json += " ],\n"; + json += " \"measureBreaks\": [\n"; + + for (var i = 0; i < this.forcedMeasures.items.length; i++) + { + var measureBreak = this.forcedMeasures.items[i]; + + json += " "; + json += "[ " + measureBreak.tick.toString() + ", "; + json += measureBreak.isLineBreak.toString() + " ]"; + + if (i < this.forcedMeasures.items.length - 1) + json += ","; + + json += "\n"; + } + json += " ]\n}"; return json; @@ -370,6 +391,16 @@ Song.prototype.loadJSON = function(jsonStr) song.meterChanges[i][2])); } + if (song.version >= 2) + { + for (var i = 0; i < song.measureBreaks.length; i++) + { + this.forcedMeasures.insert(new SongMeasureBreak( + Rational.fromArray(song.measureBreaks[i][0]), + song.measureBreaks[i][1])); + } + } + this.setLengthAuto(); this.sanitize(); } @@ -491,6 +522,16 @@ Song.prototype.saveBinary = function() writer.writeInteger(meterCh.denominator); } + // Write measure break data. + writer.writeInteger(this.forcedMeasures.items.length); + for (var i = 0; i < this.forcedMeasures.items.length; i++) + { + var measureBreak = this.forcedMeasures.items[i]; + + writer.writeRational(measureBreak.tick); + writer.writeInteger(measureBreak.isLineBreak ? 1 : 0); + } + // Compress data. var data = writer.data; data = pako.deflateRaw(data, { to: "string" }); @@ -651,6 +692,18 @@ Song.prototype.loadBinary = function(base64str) reader.readInteger())); } + // Read meter change data. + if (version >= 2) + { + var measureBreakNum = reader.readInteger(); + for (var i = 0; i < measureBreakNum; i++) + { + this.forcedMeasures.insert(new SongMeasureBreak( + reader.readRational(), + reader.readInteger() == 1 ? true : false)); + } + } + this.setLengthAuto(); this.sanitize(); } \ No newline at end of file diff --git a/style.css b/style.css index 49e7285..59abe2a 100644 --- a/style.css +++ b/style.css @@ -384,6 +384,25 @@ body alignment-baseline: middle; } +.editorForcedMeasureHandle +{ + fill: #000; + stroke-width: 0px; +} + +.editorForcedMeasureHandleSelected +{ + stroke: #000; + stroke-width: 2px; + fill: #ddd; +} + +.editorForcedMeasureLine +{ + stroke: #000; + stroke-width: 2px; +} + .editorCursor { stroke: blue;