abcjs 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/RELEASE.md +44 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +1028 -622
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/api/tune-metrics.js +18 -0
- package/src/data/abc_tune.js +13 -2
- package/src/edit/abc_editarea.js +4 -1
- package/src/parse/abc_parse.js +2 -0
- package/src/parse/abc_parse_directive.js +6 -0
- package/src/synth/abc_midi_flattener.js +40 -462
- package/src/synth/abc_midi_sequencer.js +25 -10
- package/src/synth/chord-track.js +562 -0
- package/src/synth/create-note-map.js +2 -1
- package/src/synth/create-synth.js +91 -42
- package/src/test/abc_parser_lint.js +1 -0
- package/src/write/creation/abstract-engraver.js +4 -1
- package/src/write/creation/decoration.js +3 -2
- package/src/write/creation/elements/tie-element.js +23 -0
- package/src/write/draw/draw.js +1 -1
- package/src/write/engraver-controller.js +9 -5
- package/src/write/interactive/create-analysis.js +50 -0
- package/src/write/interactive/find-selectable-element.js +24 -0
- package/src/write/interactive/selection.js +5 -45
- package/src/write/layout/layout-in-grid.js +83 -0
- package/src/write/layout/layout.js +29 -24
- package/src/write/layout/set-upper-and-lower-elements.js +2 -0
- package/src/write/layout/staff-group.js +2 -2
- package/src/write/layout/voice-elements.js +1 -1
- package/src/write/layout/voice.js +1 -1
- package/src/write/renderer.js +3 -0
- package/temp.txt +1 -48
- package/types/index.d.ts +96 -32
- package/version.js +1 -1
- package/abc2xml_239/abc2xml.html +0 -769
- package/abc2xml_239/abc2xml.py +0 -2248
- package/abc2xml_239/abc2xml_changelog.html +0 -124
- package/abc2xml_239/lazy-river.abc +0 -26
- package/abc2xml_239/lazy-river.xml +0 -3698
- package/abc2xml_239/mean-to-me.abc +0 -22
- package/abc2xml_239/mean-to-me.xml +0 -2954
- package/abc2xml_239/pyparsing.py +0 -3672
- package/abc2xml_239/pyparsing.pyc +0 -0
|
@@ -26,13 +26,15 @@ function CreateSynth() {
|
|
|
26
26
|
self.audioBuffers = []; // cache of the buffers so starting play can be fast.
|
|
27
27
|
self.duration = undefined; // the duration of the tune in seconds.
|
|
28
28
|
self.isRunning = false; // whether there is currently a sound buffer running.
|
|
29
|
-
|
|
29
|
+
self.options = undefined
|
|
30
|
+
self.pickupLength = 0
|
|
30
31
|
|
|
31
32
|
// Load and cache all needed sounds
|
|
32
33
|
self.init = function(options) {
|
|
33
34
|
if (!options)
|
|
34
35
|
options = {};
|
|
35
|
-
|
|
36
|
+
if (options.options)
|
|
37
|
+
self.options = options.options
|
|
36
38
|
registerAudioContext(options.audioContext); // This works no matter what - if there is already an ac it is a nop; if the context is not passed in, then it creates one.
|
|
37
39
|
var startTime = activeAudioContext().currentTime;
|
|
38
40
|
self.debugCallback = options.debugCallback;
|
|
@@ -115,6 +117,7 @@ function CreateSynth() {
|
|
|
115
117
|
var meter = options.visualObj.getMeterFraction();
|
|
116
118
|
if (meter.den)
|
|
117
119
|
self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den;
|
|
120
|
+
self.pickupLength = options.visualObj.getPickupLength()
|
|
118
121
|
} else if (options.sequence)
|
|
119
122
|
self.flattened = options.sequence;
|
|
120
123
|
else
|
|
@@ -124,6 +127,7 @@ function CreateSynth() {
|
|
|
124
127
|
self.sequenceCallback = params.sequenceCallback;
|
|
125
128
|
self.callbackContext = params.callbackContext;
|
|
126
129
|
self.onEnded = params.onEnded;
|
|
130
|
+
self.meterFraction = options.visualObj.getMeterFraction();
|
|
127
131
|
|
|
128
132
|
var allNotes = {};
|
|
129
133
|
var cached = [];
|
|
@@ -136,18 +140,19 @@ function CreateSynth() {
|
|
|
136
140
|
if (event.pitch !== undefined) {
|
|
137
141
|
var pitchNumber = event.pitch;
|
|
138
142
|
var noteName = pitchToNoteName[pitchNumber];
|
|
143
|
+
var inst = event.instrument !== undefined ? instrumentIndexToName[event.instrument] : currentInstrument
|
|
139
144
|
if (noteName) {
|
|
140
|
-
if (!allNotes[
|
|
141
|
-
allNotes[
|
|
142
|
-
if (!soundsCache[
|
|
143
|
-
allNotes[
|
|
145
|
+
if (!allNotes[inst])
|
|
146
|
+
allNotes[inst] = {};
|
|
147
|
+
if (!soundsCache[inst] || !soundsCache[inst][noteName])
|
|
148
|
+
allNotes[inst][noteName] = true;
|
|
144
149
|
else {
|
|
145
|
-
var label2 =
|
|
150
|
+
var label2 = inst+":"+noteName
|
|
146
151
|
if (cached.indexOf(label2) < 0)
|
|
147
152
|
cached.push(label2);
|
|
148
153
|
}
|
|
149
154
|
} else {
|
|
150
|
-
var label =
|
|
155
|
+
var label = inst+":"+noteName
|
|
151
156
|
console.log("Can't find note: ", pitchNumber, label);
|
|
152
157
|
if (errorNotes.indexOf(label) < 0)
|
|
153
158
|
errorNotes.push(label)
|
|
@@ -309,8 +314,10 @@ function CreateSynth() {
|
|
|
309
314
|
self.stop();
|
|
310
315
|
|
|
311
316
|
var noteMapTracks = createNoteMap(self.flattened);
|
|
312
|
-
|
|
313
|
-
|
|
317
|
+
|
|
318
|
+
if (self.options.swing)
|
|
319
|
+
addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength)
|
|
320
|
+
|
|
314
321
|
if (self.sequenceCallback)
|
|
315
322
|
self.sequenceCallback(noteMapTracks, self.callbackContext);
|
|
316
323
|
|
|
@@ -545,38 +552,80 @@ function CreateSynth() {
|
|
|
545
552
|
}
|
|
546
553
|
};
|
|
547
554
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
555
|
+
function addSwing(noteMapTracks, swing, meterFraction, pickupLength) {
|
|
556
|
+
|
|
557
|
+
// we can only swing in X/4 and X/8 meters.
|
|
558
|
+
if (meterFraction.den != 4 && meterFraction.den != 8)
|
|
559
|
+
return;
|
|
560
|
+
|
|
561
|
+
swing = parseFloat(swing);
|
|
562
|
+
|
|
563
|
+
// 50 (or less) is no swing,
|
|
564
|
+
if (isNaN(swing) || swing <= 50)
|
|
565
|
+
return;
|
|
566
|
+
|
|
567
|
+
// 66 is triplet swing 2:1, and
|
|
568
|
+
// 60 is swing with a ratio of 3:2.
|
|
569
|
+
// 75 is the maximum swing where the first eight is played as a dotted eight and the second as a sixteenth.
|
|
570
|
+
if (swing > 75)
|
|
571
|
+
swing = 75;
|
|
572
|
+
|
|
573
|
+
// convert the swing percentage to a percentage of increase for the first half of the beat
|
|
574
|
+
swing = swing/50 - 1;
|
|
575
|
+
|
|
576
|
+
// The volume of the swung notes is increased by this factor
|
|
577
|
+
// could be also in the settings. Try out values such 0.1, 0.2
|
|
578
|
+
var volumeIncrease = 0.0;
|
|
579
|
+
|
|
580
|
+
// the beatLength in X/8 meters
|
|
581
|
+
var beatLength = 0.25;
|
|
582
|
+
|
|
583
|
+
// in X/8 meters the 16s swing so the beatLength is halved
|
|
584
|
+
if (meterFraction.den === 8)
|
|
585
|
+
beatLength = beatLength/2;
|
|
586
|
+
|
|
587
|
+
// duration of a half beat
|
|
588
|
+
var halfbeatLength = beatLength/2;
|
|
589
|
+
|
|
590
|
+
// the extra duration of the first swung notes and the delay of the second notes
|
|
591
|
+
var swingDuration = halfbeatLength * swing;
|
|
592
|
+
|
|
593
|
+
for (var t = 0; t < noteMapTracks.length; t++) {
|
|
594
|
+
var track = noteMapTracks[t];
|
|
595
|
+
for (var i = 0; i < track.length; i++) {
|
|
596
|
+
var event = track[i];
|
|
597
|
+
if (
|
|
598
|
+
// is halfbeat
|
|
599
|
+
(event.start-pickupLength) % halfbeatLength == 0 && (event.start-pickupLength) % beatLength != 0
|
|
600
|
+
&& (
|
|
601
|
+
// the previous note is on the beat or before OR there is no previous note
|
|
602
|
+
i == 0
|
|
603
|
+
|| track[i-1].start <= track[i].start - halfbeatLength
|
|
604
|
+
)
|
|
605
|
+
&& (
|
|
606
|
+
// the next note is on the beat or after OR there is no next note
|
|
607
|
+
i == track.length - 1
|
|
608
|
+
|| track[i+1].start >= track[i].start + halfbeatLength
|
|
609
|
+
)
|
|
610
|
+
) {
|
|
611
|
+
var oldEventStart = event.start;
|
|
612
|
+
|
|
613
|
+
event.start += swingDuration;
|
|
614
|
+
|
|
615
|
+
// Increase volume of swung notes
|
|
616
|
+
event.volume *= 1 + volumeIncrease;
|
|
617
|
+
|
|
618
|
+
// if there is a previous note ending at the start of this note, extend its end
|
|
619
|
+
// and decrease its volume
|
|
620
|
+
if (i > 0 && track[i-1].end == oldEventStart) {
|
|
621
|
+
track[i-1].end = event.start;
|
|
622
|
+
track[i-1].volume *= 1 - volumeIncrease;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
580
629
|
}
|
|
581
630
|
|
|
582
631
|
module.exports = CreateSynth;
|
|
@@ -556,6 +556,7 @@ var ParserLint = function() {
|
|
|
556
556
|
subtitlespace: { type: "number", optional: true },
|
|
557
557
|
sysstaffsep: { type: "number", optional: true },
|
|
558
558
|
systemsep: { type: "number", optional: true },
|
|
559
|
+
stafftopmargin: { type: "number", optional: true },
|
|
559
560
|
tabgracefont: fontType,
|
|
560
561
|
tablabelfont: fontType,
|
|
561
562
|
tabnumberfont: fontType,
|
|
@@ -831,7 +831,10 @@ AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaf
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
if (elem.decoration) {
|
|
834
|
-
this.
|
|
834
|
+
// TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined.
|
|
835
|
+
// This should probably be combined with moveDecorations()
|
|
836
|
+
var bottom = nostem ? Math.min(-3, abselem.bottom - 6) : abselem.bottom
|
|
837
|
+
this.decoration.createDecoration(voice, elem.decoration, abselem.top, (notehead) ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove);
|
|
835
838
|
}
|
|
836
839
|
|
|
837
840
|
if (elem.barNumber) {
|
|
@@ -160,7 +160,7 @@ var stackedDecoration = function (decoration, width, abselem, yPos, positioning,
|
|
|
160
160
|
var height = glyphs.symbolHeightInPitches(symbol) + 1; // adding a little padding so nothing touches.
|
|
161
161
|
var y = getPlacement(placement);
|
|
162
162
|
y = (placement === 'above') ? y + height / 2 : y - height / 2;// Center the element vertically.
|
|
163
|
-
abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, { klass: 'ornament', thickness: glyphs.symbolHeightInPitches(symbol) }));
|
|
163
|
+
abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, { klass: 'ornament', thickness: glyphs.symbolHeightInPitches(symbol), position: placement }));
|
|
164
164
|
|
|
165
165
|
incrementPlacement(placement, height);
|
|
166
166
|
}
|
|
@@ -355,7 +355,8 @@ Decoration.prototype.createDecoration = function (voice, decoration, pitch, widt
|
|
|
355
355
|
// yPos is an object containing 'above' and 'below'. That is the placement of the next symbol on either side.
|
|
356
356
|
|
|
357
357
|
yPos.above = Math.max(yPos.above, this.minTop);
|
|
358
|
-
|
|
358
|
+
yPos.below = Math.min(yPos.below, minPitch);
|
|
359
|
+
var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, minPitch, accentAbove);
|
|
359
360
|
//if (hasOne) {
|
|
360
361
|
// abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor.
|
|
361
362
|
//}
|
|
@@ -177,6 +177,29 @@ TieElem.prototype.calcSlurY = function () {
|
|
|
177
177
|
} else
|
|
178
178
|
this.endY = this.above && beamInterferes ? this.anchor2.highestVert : this.anchor2.pitch;
|
|
179
179
|
|
|
180
|
+
if (this.anchor1.scalex === 1) { // Need a way to tell if this is a grace note - if so then keep the slur as close as possible. TODO-PER-HACK: this should be more declaratively determined.
|
|
181
|
+
var hasBeam1 = !!this.anchor1.parent.beam
|
|
182
|
+
var hasBeam2 = !!this.anchor2.parent.beam
|
|
183
|
+
if (hasBeam1) {
|
|
184
|
+
var isLastInBeam = this.anchor1.parent === this.anchor1.parent.beam.elems[this.anchor1.parent.beam.elems.length-1]
|
|
185
|
+
if (!isLastInBeam) {
|
|
186
|
+
if (this.above)
|
|
187
|
+
this.startY = this.anchor1.parent.fixed.t
|
|
188
|
+
else
|
|
189
|
+
this.startY = this.anchor1.parent.fixed.b
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (hasBeam2) {
|
|
194
|
+
var isFirstInBeam = this.anchor2.parent === this.anchor2.parent.beam.elems[0]
|
|
195
|
+
if (!isFirstInBeam) {
|
|
196
|
+
if (this.above)
|
|
197
|
+
this.endY = this.anchor2.parent.fixed.t
|
|
198
|
+
else
|
|
199
|
+
this.endY = this.anchor2.parent.fixed.b
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
180
203
|
} else if (this.anchor1) {
|
|
181
204
|
this.startY = this.endY = this.anchor1.pitch;
|
|
182
205
|
} else if (this.anchor2) {
|
package/src/write/draw/draw.js
CHANGED
|
@@ -20,7 +20,7 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
20
20
|
var abcLine = abcTune.lines[line];
|
|
21
21
|
if (abcLine.staff) {
|
|
22
22
|
if (classes.shouldAddClasses)
|
|
23
|
-
groupClasses.klass = "abcjs-staff l" + classes.lineNumber
|
|
23
|
+
groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber
|
|
24
24
|
renderer.paper.openGroup(groupClasses)
|
|
25
25
|
if (abcLine.vskip) {
|
|
26
26
|
renderer.moveY(abcLine.vskip);
|
|
@@ -17,6 +17,7 @@ var GetFontAndAttr = require('./helpers/get-font-and-attr');
|
|
|
17
17
|
var GetTextSize = require('./helpers/get-text-size');
|
|
18
18
|
var draw = require('./draw/draw');
|
|
19
19
|
var tablatures = require('../api/abc_tablatures');
|
|
20
|
+
var findSelectableElement = require('./interactive/find-selectable-element');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @class
|
|
@@ -32,6 +33,7 @@ var tablatures = require('../api/abc_tablatures');
|
|
|
32
33
|
*/
|
|
33
34
|
var EngraverController = function (paper, params) {
|
|
34
35
|
params = params || {};
|
|
36
|
+
this.findSelectableElement = findSelectableElement;
|
|
35
37
|
this.oneSvgPerLine = params.oneSvgPerLine;
|
|
36
38
|
this.selectionColor = params.selectionColor;
|
|
37
39
|
this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
|
|
@@ -40,6 +42,7 @@ var EngraverController = function (paper, params) {
|
|
|
40
42
|
this.responsive = params.responsive;
|
|
41
43
|
this.space = 3 * spacing.SPACE;
|
|
42
44
|
this.initialClef = params.initialClef;
|
|
45
|
+
this.timeBasedLayout = params.timeBasedLayout;
|
|
43
46
|
this.expandToWidest = !!params.expandToWidest;
|
|
44
47
|
this.scale = params.scale ? parseFloat(params.scale) : 0;
|
|
45
48
|
this.classes = new Classes({ shouldAddClasses: params.add_classes });
|
|
@@ -131,7 +134,7 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
|
|
|
131
134
|
this.constructTuneElements(abcTune);
|
|
132
135
|
// layout() sets the x-coordinate of the abcTune element here:
|
|
133
136
|
// abcTune.lines[0].staffGroup.voices[0].children[0].x
|
|
134
|
-
layout(this.renderer, abcTune, 0, this.space);
|
|
137
|
+
layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
|
|
135
138
|
|
|
136
139
|
var ret = [];
|
|
137
140
|
var section;
|
|
@@ -193,6 +196,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
|
|
|
193
196
|
percmap: abcTune.formatting.percmap,
|
|
194
197
|
initialClef: this.initialClef,
|
|
195
198
|
jazzchords: this.jazzchords,
|
|
199
|
+
timeBasedLayout: this.timeBasedLayout,
|
|
196
200
|
accentAbove: this.accentAbove,
|
|
197
201
|
germanAlphabet: this.germanAlphabet
|
|
198
202
|
});
|
|
@@ -254,7 +258,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
|
|
|
254
258
|
//Set the top text now that we know the width
|
|
255
259
|
|
|
256
260
|
// Do all the positioning, both horizontally and vertically
|
|
257
|
-
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
|
|
261
|
+
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
|
|
258
262
|
|
|
259
263
|
//Set the top text now that we know the width
|
|
260
264
|
if (this.expandToWidest && maxWidth > this.width + 1) {
|
|
@@ -301,7 +305,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
|
|
|
301
305
|
this.selectables = ret.selectables;
|
|
302
306
|
if (this.oneSvgPerLine) {
|
|
303
307
|
var div = this.renderer.paper.svg.parentNode;
|
|
304
|
-
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive);
|
|
308
|
+
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
|
|
305
309
|
} else {
|
|
306
310
|
this.svgs = [this.renderer.paper.svg];
|
|
307
311
|
}
|
|
@@ -310,7 +314,7 @@ EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOf
|
|
|
310
314
|
this.jazzchords = origJazzChords
|
|
311
315
|
};
|
|
312
316
|
|
|
313
|
-
function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
317
|
+
function splitSvgIntoLines(renderer, output, title, responsive, scale) {
|
|
314
318
|
// Each line is a top level <g> in the svg. To split it into separate
|
|
315
319
|
// svgs iterate through each of those and put them in a new svg. Since
|
|
316
320
|
// they are placed absolutely, the viewBox needs to be manipulated to
|
|
@@ -335,7 +339,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
|
335
339
|
var wrapper = document.createElement("div");
|
|
336
340
|
var divStyles = "overflow: hidden;"
|
|
337
341
|
if (responsive !== 'resize')
|
|
338
|
-
divStyles += "height:" + height + "px;"
|
|
342
|
+
divStyles += "height:" + (height * scale) + "px;"
|
|
339
343
|
wrapper.setAttribute("style", divStyles)
|
|
340
344
|
var svg = duplicateSvg(source)
|
|
341
345
|
var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function findNumber(klass, match, target, name) {
|
|
2
|
+
if (klass.indexOf(match) === 0) {
|
|
3
|
+
var value = klass.replace(match, '');
|
|
4
|
+
var num = parseInt(value, 10);
|
|
5
|
+
if ('' + num === value)
|
|
6
|
+
target[name] = num;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createAnalysis(target, ev) {
|
|
11
|
+
var classes = [];
|
|
12
|
+
if (target.absEl.elemset) {
|
|
13
|
+
var classObj = {};
|
|
14
|
+
for (var j = 0; j < target.absEl.elemset.length; j++) {
|
|
15
|
+
var es = target.absEl.elemset[j];
|
|
16
|
+
if (es) {
|
|
17
|
+
var klass = es.getAttribute("class").split(' ');
|
|
18
|
+
for (var k = 0; k < klass.length; k++)
|
|
19
|
+
classObj[klass[k]] = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (var kk = 0; kk < Object.keys(classObj).length; kk++)
|
|
23
|
+
classes.push(Object.keys(classObj)[kk]);
|
|
24
|
+
}
|
|
25
|
+
var analysis = {};
|
|
26
|
+
for (var ii = 0; ii < classes.length; ii++) {
|
|
27
|
+
findNumber(classes[ii], "abcjs-v", analysis, "voice");
|
|
28
|
+
findNumber(classes[ii], "abcjs-l", analysis, "line");
|
|
29
|
+
findNumber(classes[ii], "abcjs-m", analysis, "measure");
|
|
30
|
+
}
|
|
31
|
+
if (target.staffPos)
|
|
32
|
+
analysis.staffPos = target.staffPos;
|
|
33
|
+
var closest = ev.target;
|
|
34
|
+
while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
|
|
35
|
+
closest = closest.parentNode;
|
|
36
|
+
var parent = ev.target;
|
|
37
|
+
while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
|
|
38
|
+
parent = parent.parentNode;
|
|
39
|
+
if (parent && parent.dataset) {
|
|
40
|
+
analysis.name = parent.dataset.name;
|
|
41
|
+
analysis.clickedName = closest.dataset.name;
|
|
42
|
+
analysis.parentClasses = parent.classList;
|
|
43
|
+
}
|
|
44
|
+
if (closest && closest.classList)
|
|
45
|
+
analysis.clickedClasses = closest.classList;
|
|
46
|
+
analysis.selectableElement = target.svgEl;
|
|
47
|
+
return {classes: classes, analysis: analysis}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = createAnalysis;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var createAnalysis = require('./create-analysis');
|
|
2
|
+
|
|
3
|
+
function findSelectableElement(event) {
|
|
4
|
+
var selectable = event
|
|
5
|
+
while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
|
|
6
|
+
selectable = selectable.parentNode
|
|
7
|
+
}
|
|
8
|
+
if (selectable && selectable.attributes && selectable.attributes.selectable) {
|
|
9
|
+
var index = selectable.attributes['data-index'].nodeValue
|
|
10
|
+
if (index) {
|
|
11
|
+
index = parseInt(index, 10)
|
|
12
|
+
if (index >= 0 && index < this.selectables.length) {
|
|
13
|
+
var element = this.selectables[index]
|
|
14
|
+
var ret = createAnalysis(element, event)
|
|
15
|
+
ret.index = index
|
|
16
|
+
ret.element = element
|
|
17
|
+
return ret
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = findSelectableElement;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var spacing = require('../helpers/spacing');
|
|
2
|
+
var createAnalysis = require('./create-analysis');
|
|
2
3
|
|
|
3
4
|
function setupSelection(engraver, svgs) {
|
|
4
5
|
engraver.rangeHighlight = rangeHighlight;
|
|
@@ -339,58 +340,17 @@ function setSelection(dragIndex) {
|
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
|
|
342
344
|
function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
|
|
343
|
-
var
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for (var j = 0; j < target.absEl.elemset.length; j++) {
|
|
347
|
-
var es = target.absEl.elemset[j];
|
|
348
|
-
if (es) {
|
|
349
|
-
var klass = es.getAttribute("class").split(' ');
|
|
350
|
-
for (var k = 0; k < klass.length; k++)
|
|
351
|
-
classObj[klass[k]] = true;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
for (var kk = 0; kk < Object.keys(classObj).length; kk++)
|
|
355
|
-
classes.push(Object.keys(classObj)[kk]);
|
|
356
|
-
}
|
|
357
|
-
var analysis = {};
|
|
358
|
-
for (var ii = 0; ii < classes.length; ii++) {
|
|
359
|
-
findNumber(classes[ii], "abcjs-v", analysis, "voice");
|
|
360
|
-
findNumber(classes[ii], "abcjs-l", analysis, "line");
|
|
361
|
-
findNumber(classes[ii], "abcjs-m", analysis, "measure");
|
|
362
|
-
}
|
|
363
|
-
if (target.staffPos)
|
|
364
|
-
analysis.staffPos = target.staffPos;
|
|
365
|
-
var closest = ev.target;
|
|
366
|
-
while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
|
|
367
|
-
closest = closest.parentNode;
|
|
368
|
-
var parent = ev.target;
|
|
369
|
-
while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
|
|
370
|
-
parent = parent.parentNode;
|
|
371
|
-
if (parent && parent.dataset) {
|
|
372
|
-
analysis.name = parent.dataset.name;
|
|
373
|
-
analysis.clickedName = closest.dataset.name;
|
|
374
|
-
analysis.parentClasses = parent.classList;
|
|
375
|
-
}
|
|
376
|
-
if (closest && closest.classList)
|
|
377
|
-
analysis.clickedClasses = closest.classList;
|
|
378
|
-
analysis.selectableElement = target.svgEl;
|
|
345
|
+
var ret = createAnalysis(target, ev)
|
|
346
|
+
var classes = ret.classes
|
|
347
|
+
var analysis = ret.analysis
|
|
379
348
|
|
|
380
349
|
for (var i = 0; i < this.listeners.length; i++) {
|
|
381
350
|
this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, { step: dragStep, max: dragMax, index: dragIndex, setSelection: setSelection.bind(this) }, ev);
|
|
382
351
|
}
|
|
383
352
|
}
|
|
384
353
|
|
|
385
|
-
function findNumber(klass, match, target, name) {
|
|
386
|
-
if (klass.indexOf(match) === 0) {
|
|
387
|
-
var value = klass.replace(match, '');
|
|
388
|
-
var num = parseInt(value, 10);
|
|
389
|
-
if ('' + num === value)
|
|
390
|
-
target[name] = num;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
354
|
function clearSelection() {
|
|
395
355
|
for (var i = 0; i < this.selected.length; i++) {
|
|
396
356
|
this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
|
|
2
|
+
|
|
3
|
+
function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
|
|
4
|
+
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
5
|
+
var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding)
|
|
6
|
+
var totalDuration = ret.totalDuration
|
|
7
|
+
var minSpacing = ret.minSpacing
|
|
8
|
+
var totalWidth = minSpacing * totalDuration
|
|
9
|
+
if (timeBasedLayout.minWidth)
|
|
10
|
+
totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth)
|
|
11
|
+
var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding/2 : 2 // If the padding isn't specified still give it some
|
|
12
|
+
|
|
13
|
+
staffGroup.startx = leftEdge
|
|
14
|
+
staffGroup.w = totalWidth + leftEdge
|
|
15
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
16
|
+
var voice = staffGroup.voices[i]
|
|
17
|
+
voice.startx = leftEdge
|
|
18
|
+
voice.w = totalWidth + leftEdge
|
|
19
|
+
|
|
20
|
+
var x = leftEdge
|
|
21
|
+
var afterFixedLeft = false
|
|
22
|
+
var durationUnit = 0
|
|
23
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
24
|
+
var child = voice.children[j]
|
|
25
|
+
if (!afterFixedLeft) {
|
|
26
|
+
if (child.duration !== 0) {
|
|
27
|
+
// We got to the first music element on the line
|
|
28
|
+
afterFixedLeft = true
|
|
29
|
+
durationUnit = (totalWidth + leftEdge - x) / totalDuration
|
|
30
|
+
staffGroup.gridStart = x
|
|
31
|
+
} else {
|
|
32
|
+
// We are still doing the preliminary stuff - clef, time sig, etc.
|
|
33
|
+
child.x = x
|
|
34
|
+
x += child.w + child.minspacing
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (afterFixedLeft) {
|
|
38
|
+
if (timeBasedLayout.align === 'center')
|
|
39
|
+
child.x = x + (child.duration * durationUnit) / 2 - child.w / 2
|
|
40
|
+
else {
|
|
41
|
+
// left align with padding - but no padding for barlines, they should be right aligned.
|
|
42
|
+
// TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
|
|
43
|
+
if (child.duration === 0) {
|
|
44
|
+
child.x = x + 1 - child.w
|
|
45
|
+
} else {
|
|
46
|
+
// child.extraw has the width of the accidentals - push the note to the right to take that into consideration. It will be 0 if there is nothing to the left.
|
|
47
|
+
child.x = x + leftAlignPadding - child.extraw
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
x += child.duration * durationUnit
|
|
51
|
+
}
|
|
52
|
+
for (var k = 0; k < child.children.length; k++) {
|
|
53
|
+
var grandchild = child.children[k]
|
|
54
|
+
// some elements don't have a dx - Tempo, for instance
|
|
55
|
+
var dx = grandchild.dx ? grandchild.dx : 0
|
|
56
|
+
grandchild.x = child.x + dx
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
staffGroup.gridEnd = x
|
|
60
|
+
}
|
|
61
|
+
return totalWidth
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getTotalDuration(staffGroup, timeBasedLayout) {
|
|
65
|
+
var maxSpacing = 0
|
|
66
|
+
var maxCount = 0
|
|
67
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
68
|
+
var count = 0
|
|
69
|
+
var voice = staffGroup.voices[i]
|
|
70
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
71
|
+
var element = voice.children[j]
|
|
72
|
+
count += element.duration
|
|
73
|
+
if (element.duration) {
|
|
74
|
+
var width = (element.w+timeBasedLayout) / element.duration
|
|
75
|
+
maxSpacing = Math.max(maxSpacing, width)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
maxCount = Math.max(maxCount, count)
|
|
79
|
+
}
|
|
80
|
+
return { totalDuration: maxCount, minSpacing: maxSpacing}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = layoutInGrid;
|
|
@@ -2,8 +2,12 @@ var layoutVoice = require('./voice');
|
|
|
2
2
|
var setUpperAndLowerElements = require('./set-upper-and-lower-elements');
|
|
3
3
|
var layoutStaffGroup = require('./staff-group');
|
|
4
4
|
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
|
|
5
|
+
var layoutInGrid = require('./layout-in-grid');
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// This sets the "x" attribute on all the children in abctune.lines
|
|
8
|
+
// It also sets the "w" and "startx" attributes on "voices"
|
|
9
|
+
// It also sets the "w" and "startx" attributes on "voices.children"
|
|
10
|
+
var layout = function (renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
|
|
7
11
|
var i;
|
|
8
12
|
var abcLine;
|
|
9
13
|
// Adjust the x-coordinates to their absolute positions
|
|
@@ -12,7 +16,11 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
|
|
|
12
16
|
abcLine = abctune.lines[i];
|
|
13
17
|
if (abcLine.staff) {
|
|
14
18
|
// console.log("=== line", i)
|
|
15
|
-
var thisWidth
|
|
19
|
+
var thisWidth;
|
|
20
|
+
if (timeBasedLayout !== undefined)
|
|
21
|
+
thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);
|
|
22
|
+
else
|
|
23
|
+
thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
|
|
16
24
|
// console.log(thisWidth, maxWidth)
|
|
17
25
|
if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness
|
|
18
26
|
maxWidth = thisWidth
|
|
@@ -46,39 +54,36 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
|
|
|
46
54
|
var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) {
|
|
47
55
|
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
48
56
|
var newspace = space;
|
|
57
|
+
//dumpGroup("before", staffGroup)
|
|
49
58
|
for (var it = 0; it < 8; it++) { // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.)
|
|
50
59
|
// console.log("iteration", it)
|
|
51
|
-
|
|
52
|
-
var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
|
|
53
|
-
// dumpGroup("after",staffGroup)
|
|
60
|
+
var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
|
|
54
61
|
newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
|
|
55
62
|
if (debug)
|
|
56
63
|
console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
|
|
57
64
|
if (newspace === null) break;
|
|
58
65
|
}
|
|
66
|
+
//dumpGroup("after",staffGroup)
|
|
59
67
|
centerWholeRests(staffGroup.voices);
|
|
60
68
|
return staffGroup.w - leftEdge
|
|
61
69
|
};
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// }
|
|
80
|
-
// console.log(label,output)
|
|
81
|
-
// }
|
|
71
|
+
function replacer(key, value) {
|
|
72
|
+
// Filtering out properties
|
|
73
|
+
if (key === 'parent') {
|
|
74
|
+
return 'parent';
|
|
75
|
+
}
|
|
76
|
+
if (key === 'beam') {
|
|
77
|
+
return 'beam';
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function dumpGroup(label, staffGroup) {
|
|
83
|
+
console.log("=================== " + label + " =========================")
|
|
84
|
+
console.log(staffGroup)
|
|
85
|
+
console.log(JSON.stringify(staffGroup, replacer, "\t"))
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
|
|
84
89
|
if (isLastLine) {
|