abcjs 6.0.0-beta.7 → 6.0.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.
Files changed (204) hide show
  1. package/.github/workflows/tests.yml +29 -0
  2. package/CODE_OF_CONDUCT.md +76 -0
  3. package/CONTRIBUTING.md +1 -0
  4. package/LICENSE.md +1 -1
  5. package/README.md +92 -3
  6. package/RELEASE.md +957 -1
  7. package/abcjs-audio.css +14 -5
  8. package/dist/.gitignore +1 -2
  9. package/dist/abcjs-basic-min.js +3 -0
  10. package/dist/abcjs-basic-min.js.LICENSE +23 -0
  11. package/dist/abcjs-basic.js +28231 -0
  12. package/dist/abcjs-basic.js.map +1 -0
  13. package/dist/abcjs-plugin-min.js +3 -0
  14. package/dist/abcjs-plugin-min.js.LICENSE +23 -0
  15. package/dist/report-basic.html +37 -0
  16. package/dist/report-before-glyph-compress.html +37 -0
  17. package/dist/report-brown-ts-target-es5.html +37 -0
  18. package/dist/report-dev-orig-no-babel.html +37 -0
  19. package/dist/report-synth.html +37 -0
  20. package/docker-build.sh +1 -0
  21. package/glyphs.json +1 -0
  22. package/index.js +27 -2
  23. package/{static-wrappers/license.js → license.js} +1 -1
  24. package/package.json +26 -29
  25. package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
  26. package/src/api/abc_animation.js +1 -17
  27. package/src/api/abc_tablatures.js +144 -0
  28. package/src/api/abc_timing_callbacks.js +234 -116
  29. package/src/api/abc_tunebook.js +18 -67
  30. package/src/api/abc_tunebook_svg.js +38 -46
  31. package/src/data/abc_tune.js +232 -972
  32. package/src/data/deline-tune.js +199 -0
  33. package/src/edit/abc_editarea.js +112 -0
  34. package/src/edit/abc_editor.js +95 -221
  35. package/src/midi/abc_midi_create.js +48 -50
  36. package/src/parse/abc_common.js +0 -14
  37. package/src/parse/abc_parse.js +167 -1321
  38. package/src/parse/abc_parse_book.js +62 -0
  39. package/src/parse/abc_parse_directive.js +164 -41
  40. package/src/parse/abc_parse_header.js +116 -145
  41. package/src/parse/abc_parse_key_voice.js +26 -20
  42. package/src/parse/abc_parse_music.js +1337 -0
  43. package/src/parse/abc_tokenizer.js +21 -15
  44. package/src/parse/abc_transpose.js +3 -15
  45. package/src/parse/tune-builder.js +896 -0
  46. package/src/parse/wrap_lines.js +205 -453
  47. package/src/synth/abc_midi_flattener.js +1292 -0
  48. package/src/{midi → synth}/abc_midi_renderer.js +44 -17
  49. package/src/synth/abc_midi_sequencer.js +648 -0
  50. package/src/synth/active-audio-context.js +3 -14
  51. package/src/synth/cents-to-factor.js +10 -0
  52. package/src/synth/create-note-map.js +21 -32
  53. package/src/synth/create-synth-control.js +20 -103
  54. package/src/synth/create-synth.js +185 -77
  55. package/src/synth/download-buffer.js +7 -21
  56. package/src/synth/get-midi-file.js +13 -20
  57. package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
  58. package/src/synth/images/loop.svg.js +65 -0
  59. package/src/synth/images/pause.svg.js +10 -0
  60. package/src/synth/images/play.svg.js +9 -0
  61. package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
  62. package/src/synth/instrument-index-to-name.js +1 -16
  63. package/src/synth/load-note.js +37 -76
  64. package/src/synth/pitch-to-note-name.js +0 -15
  65. package/src/synth/pitches-to-perc.js +64 -0
  66. package/src/synth/place-note.js +78 -68
  67. package/src/synth/play-event.js +17 -18
  68. package/src/synth/register-audio-context.js +11 -23
  69. package/src/synth/sounds-cache.js +0 -15
  70. package/src/synth/supports-audio.js +9 -23
  71. package/src/synth/synth-controller.js +80 -49
  72. package/src/synth/synth-sequence.js +20 -34
  73. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  74. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  75. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  76. package/src/tablatures/instruments/string-patterns.js +277 -0
  77. package/src/tablatures/instruments/string-tablature.js +56 -0
  78. package/src/tablatures/instruments/tab-note.js +282 -0
  79. package/src/tablatures/instruments/tab-notes.js +41 -0
  80. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  81. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  82. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  83. package/src/tablatures/tab-absolute-elements.js +310 -0
  84. package/src/tablatures/tab-common.js +29 -0
  85. package/src/tablatures/tab-renderer.js +243 -0
  86. package/src/tablatures/transposer.js +110 -0
  87. package/src/test/abc_midi_lint.js +5 -22
  88. package/src/test/abc_midi_sequencer_lint.js +11 -14
  89. package/src/test/abc_parser_lint.js +136 -32
  90. package/src/test/abc_vertical_lint.js +94 -32
  91. package/src/test/rendering-lint.js +38 -5
  92. package/src/write/abc_absolute_element.js +112 -120
  93. package/src/write/abc_abstract_engraver.js +102 -253
  94. package/src/write/abc_beam_element.js +30 -290
  95. package/src/write/abc_brace_element.js +12 -121
  96. package/src/write/abc_create_clef.js +21 -32
  97. package/src/write/abc_create_key_signature.js +8 -26
  98. package/src/write/abc_create_note_head.js +107 -0
  99. package/src/write/abc_create_time_signature.js +2 -21
  100. package/src/write/abc_crescendo_element.js +3 -50
  101. package/src/write/abc_decoration.js +7 -30
  102. package/src/write/abc_dynamic_decoration.js +3 -37
  103. package/src/write/abc_ending_element.js +1 -57
  104. package/src/write/abc_engraver_controller.js +111 -234
  105. package/src/write/abc_glyphs.js +9 -19
  106. package/src/write/abc_relative_element.js +57 -97
  107. package/src/write/abc_renderer.js +10 -832
  108. package/src/write/abc_spacing.js +0 -15
  109. package/src/write/abc_staff_group_element.js +14 -349
  110. package/src/write/abc_tempo_element.js +9 -117
  111. package/src/write/abc_tie_element.js +5 -68
  112. package/src/write/abc_triplet_element.js +6 -124
  113. package/src/write/abc_voice_element.js +7 -222
  114. package/src/write/add-chord.js +103 -0
  115. package/src/write/add-text-if.js +33 -0
  116. package/src/write/bottom-text.js +79 -0
  117. package/src/write/calcHeight.js +17 -0
  118. package/src/write/classes.js +100 -0
  119. package/src/write/draw/absolute.js +68 -0
  120. package/src/write/draw/beam.js +56 -0
  121. package/src/write/draw/brace.js +106 -0
  122. package/src/write/draw/crescendo.js +38 -0
  123. package/src/write/draw/debug-box.js +8 -0
  124. package/src/write/draw/draw.js +56 -0
  125. package/src/write/draw/dynamics.js +20 -0
  126. package/src/write/draw/ending.js +46 -0
  127. package/src/write/draw/group-elements.js +66 -0
  128. package/src/write/draw/horizontal-line.js +25 -0
  129. package/src/write/draw/non-music.js +50 -0
  130. package/src/write/draw/print-line.js +24 -0
  131. package/src/write/draw/print-path.js +7 -0
  132. package/src/write/draw/print-stem.js +30 -0
  133. package/src/write/draw/print-symbol.js +59 -0
  134. package/src/write/draw/print-vertical-line.js +18 -0
  135. package/src/write/draw/relative.js +77 -0
  136. package/src/write/draw/round-number.js +5 -0
  137. package/src/write/draw/selectables.js +59 -0
  138. package/src/write/draw/separator.js +16 -0
  139. package/src/write/draw/set-paper-size.js +45 -0
  140. package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
  141. package/src/write/draw/staff-group.js +226 -0
  142. package/src/write/draw/staff-line.js +9 -0
  143. package/src/write/draw/staff.js +33 -0
  144. package/src/write/draw/tab-line.js +40 -0
  145. package/src/write/draw/tempo.js +45 -0
  146. package/src/write/draw/text.js +71 -0
  147. package/src/write/draw/tie.js +97 -0
  148. package/src/write/draw/triplet.js +46 -0
  149. package/src/write/draw/voice.js +102 -0
  150. package/src/write/format-jazz-chord.js +15 -0
  151. package/src/write/free-text.js +41 -0
  152. package/src/write/get-font-and-attr.js +37 -0
  153. package/src/write/get-text-size.js +56 -0
  154. package/src/write/highlight.js +11 -0
  155. package/src/write/layout/VoiceElements.js +121 -0
  156. package/src/write/layout/beam.js +213 -0
  157. package/src/write/layout/get-left-edge-of-staff.js +56 -0
  158. package/src/write/layout/getBarYAt.js +6 -0
  159. package/src/write/layout/layout.js +94 -0
  160. package/src/write/layout/setUpperAndLowerElements.js +232 -0
  161. package/src/write/layout/staffGroup.js +146 -0
  162. package/src/write/layout/triplet.js +75 -0
  163. package/src/write/layout/voice.js +137 -0
  164. package/src/write/selection.js +188 -70
  165. package/src/write/separator.js +10 -0
  166. package/src/write/set-class.js +21 -0
  167. package/src/write/subtitle.js +12 -0
  168. package/src/write/svg.js +95 -43
  169. package/src/write/top-text.js +54 -0
  170. package/src/write/unhighlight.js +11 -0
  171. package/temp.txt +17 -0
  172. package/test.js +27 -64
  173. package/types/index.d.ts +1095 -0
  174. package/version.js +1 -1
  175. package/.babelrc +0 -5
  176. package/.eslintrc +0 -3
  177. package/.gitmodules +0 -3
  178. package/abcjs-midi.css +0 -166
  179. package/build-utils/loadPresets.js +0 -14
  180. package/build-utils/presets/webpack.analyze.js +0 -6
  181. package/build-utils/presets/webpack.optimize.js +0 -30
  182. package/build-utils/webpack.development.js +0 -14
  183. package/build-utils/webpack.production.js +0 -35
  184. package/deploy-docs.sh +0 -25
  185. package/docs/README.md +0 -33
  186. package/fix-versions.sh +0 -23
  187. package/mei.js +0 -43
  188. package/midi.js +0 -62
  189. package/src/api/abc_tunebook_midi.js +0 -116
  190. package/src/midi/abc_midi_controls.js +0 -701
  191. package/src/midi/abc_midi_flattener.js +0 -1119
  192. package/src/midi/abc_midi_js_preparer.js +0 -243
  193. package/src/midi/abc_midi_sequencer.js +0 -401
  194. package/src/midi/abc_midi_ui_generator.js +0 -86
  195. package/src/plugin/abc_plugin_midi.js +0 -220
  196. package/src/synth/images/loop.svg +0 -61
  197. package/src/synth/images/pause.svg +0 -6
  198. package/src/synth/images/play.svg +0 -5
  199. package/src/transform/abc2abc_write.js +0 -395
  200. package/static-wrappers/basic.js +0 -2
  201. package/static-wrappers/midi.js +0 -2
  202. package/static-wrappers/plugin-midi.js +0 -6
  203. package/static-wrappers/plugin.js +0 -6
  204. package/webpack.config.js +0 -29
@@ -0,0 +1,213 @@
1
+ var RelativeElement = require('../abc_relative_element');
2
+ var spacing = require('../abc_spacing');
3
+ var getBarYAt = require('./getBarYAt');
4
+
5
+ var layoutBeam = function(beam) {
6
+ if (beam.elems.length === 0 || beam.allrests) return;
7
+
8
+ var dy = calcDy(beam.stemsUp, beam.isgrace); // This is the width of the beam line.
9
+
10
+ // create the main beam
11
+ var firstElement = beam.elems[0];
12
+ var lastElement = beam.elems[beam.elems.length - 1];
13
+ var minStemHeight = 0; // The following is to leave space for "!///!" marks.
14
+ var referencePitch = beam.stemsUp ? firstElement.abcelem.maxpitch : firstElement.abcelem.minpitch;
15
+ minStemHeight = minStem(firstElement, beam.stemsUp, referencePitch, minStemHeight);
16
+ minStemHeight = minStem(lastElement, beam.stemsUp, referencePitch, minStemHeight);
17
+ minStemHeight = Math.max(beam.stemHeight, minStemHeight + 3); // TODO-PER: The 3 is the width of a 16th beam. The actual height of the beam should be used instead.
18
+ var yPos = calcYPos(beam.average, beam.elems.length, minStemHeight, beam.stemsUp, firstElement.abcelem.averagepitch, lastElement.abcelem.averagepitch, beam.isflat, beam.min, beam.max, beam.isgrace);
19
+ var xPos = calcXPos(beam.stemsUp, firstElement, lastElement);
20
+ beam.addBeam({ startX: xPos[0], endX: xPos[1], startY: yPos[0], endY: yPos[1], dy: dy });
21
+
22
+ // create the rest of the beams (in the case of 1/16th notes, etc.
23
+ var beams = createAdditionalBeams(beam.elems, beam.stemsUp, beam.beams[0], beam.isgrace, dy);
24
+ for (var i = 0; i < beams.length; i++)
25
+ beam.addBeam(beams[i]);
26
+
27
+ // Now that the main beam is defined, we know how tall the stems should be, so create them and attach them to the original notes.
28
+ createStems(beam.elems, beam.stemsUp, beam.beams[0], dy, beam.mainNote);
29
+ };
30
+
31
+ var getDurlog = function(duration) {
32
+ // TODO-PER: This is a hack to prevent a Chrome lockup. Duration should have been defined already,
33
+ // but there's definitely a case where it isn't. [Probably something to do with triplets.]
34
+ if (duration === undefined) {
35
+ return 0;
36
+ }
37
+ // console.log("getDurlog: " + duration);
38
+ return Math.floor(Math.log(duration)/Math.log(2));
39
+ };
40
+
41
+ //
42
+ // private functions
43
+ //
44
+ function minStem(element, stemsUp, referencePitch, minStemHeight) {
45
+ if (!element.children)
46
+ return minStemHeight;
47
+ for (var i = 0; i < element.children.length; i++) {
48
+ var elem = element.children[i];
49
+ if (stemsUp && elem.top !== undefined && elem.c === "flags.ugrace")
50
+ minStemHeight = Math.max(minStemHeight, elem.top - referencePitch);
51
+ else if (!stemsUp && elem.bottom !== undefined && elem.c === "flags.ugrace")
52
+ minStemHeight = Math.max(minStemHeight, referencePitch - elem.bottom + 7); // The extra 7 is because we are measuring the slash from the top.
53
+ }
54
+ return minStemHeight;
55
+ }
56
+
57
+ function calcSlant(leftAveragePitch, rightAveragePitch, numStems, isFlat) {
58
+ if (isFlat)
59
+ return 0;
60
+ var slant = leftAveragePitch - rightAveragePitch;
61
+ var maxSlant = numStems / 2;
62
+
63
+ if (slant > maxSlant) slant = maxSlant;
64
+ if (slant < -maxSlant) slant = -maxSlant;
65
+ return slant;
66
+ }
67
+
68
+ function calcDy(asc, isGrace) {
69
+ var dy = (asc) ? spacing.STEP : -spacing.STEP;
70
+ if (isGrace) dy = dy * 0.4;
71
+ return dy;
72
+ }
73
+
74
+ function calcXPos(asc, firstElement, lastElement) {
75
+ var starthead = firstElement.heads[asc ? 0 : firstElement.heads.length - 1];
76
+ var endhead = lastElement.heads[asc ? 0 : lastElement.heads.length - 1];
77
+ var startX = starthead.x;
78
+ if (asc) startX += starthead.w - 0.6;
79
+ var endX = endhead.x;
80
+ endX += (asc) ? endhead.w : 0.6;
81
+ return [ startX, endX ];
82
+ }
83
+
84
+ function calcYPos(average, numElements, stemHeight, asc, firstAveragePitch, lastAveragePitch, isFlat, minPitch, maxPitch, isGrace) {
85
+ var barpos = stemHeight - 2; // (isGrace)? 5:7;
86
+ var barminpos = stemHeight - 2;
87
+ var pos = Math.round(asc ? Math.max(average + barpos, maxPitch + barminpos) : Math.min(average - barpos, minPitch - barminpos));
88
+
89
+ var slant = calcSlant(firstAveragePitch, lastAveragePitch, numElements, isFlat);
90
+ var startY = pos + Math.floor(slant / 2);
91
+ var endY = pos + Math.floor(-slant / 2);
92
+
93
+ // If the notes are too high or too low, make the beam go down to the middle
94
+ if (!isGrace) {
95
+ if (asc && pos < 6) {
96
+ startY = 6;
97
+ endY = 6;
98
+ } else if (!asc && pos > 6) {
99
+ startY = 6;
100
+ endY = 6;
101
+ }
102
+ }
103
+
104
+ return [ startY, endY];
105
+ }
106
+
107
+ function createStems(elems, asc, beam, dy, mainNote) {
108
+ for (var i = 0; i < elems.length; i++) {
109
+ var elem = elems[i];
110
+ if (elem.abcelem.rest)
111
+ continue;
112
+ // TODO-PER: This is odd. If it is a regular beam then elems is an array of AbsoluteElements, if it is a grace beam then it is an array of objects , so we directly attach the element to the parent. We tell it if is a grace note because they are passed in as a generic object instead of an AbsoluteElement.
113
+ var isGrace = elem.addExtra ? false : true;
114
+ var parent = isGrace ? mainNote : elem;
115
+ var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1];
116
+ var ovalDelta = 1 / 5;//(isGrace)?1/3:1/5;
117
+ var pitch = furthestHead.pitch + ((asc) ? ovalDelta : -ovalDelta);
118
+ var dx = asc ? furthestHead.w : 0; // down-pointing stems start on the left side of the note, up-pointing stems start on the right side, so we offset by the note width.
119
+ if (!isGrace)
120
+ dx += furthestHead.dx;
121
+ var x = furthestHead.x + dx; // this is now the actual x location in pixels.
122
+ var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x);
123
+ var lineWidth = (asc) ? -0.6 : 0.6;
124
+ if (!asc)
125
+ bary -= (dy / 2) / spacing.STEP; // TODO-PER: This is just a fudge factor so the down-pointing stems don't overlap.
126
+ if (isGrace)
127
+ dx += elem.heads[0].dx;
128
+ // TODO-PER-HACK: One type of note head has a different placement of the stem. This should be more generically calculated:
129
+ if (furthestHead.c === 'noteheads.slash.quarter') {
130
+ if (asc)
131
+ pitch += 1;
132
+ else
133
+ pitch -= 1;
134
+ }
135
+ var stem = new RelativeElement(null, dx, 0, pitch, {
136
+ "type": "stem",
137
+ "pitch2": bary,
138
+ linewidth: lineWidth
139
+ });
140
+ stem.setX(parent.x); // This is after the x coordinates were set, so we have to set it directly.
141
+ parent.addRight(stem);
142
+ }
143
+
144
+ }
145
+
146
+ function createAdditionalBeams(elems, asc, beam, isGrace, dy) {
147
+ var beams = [];
148
+ var auxBeams = []; // auxbeam will be {x, y, durlog, single} auxbeam[0] should match with durlog=-4 (16th) (j=-4-durlog)
149
+ for (var i = 0; i < elems.length; i++) {
150
+ var elem = elems[i];
151
+ if (elem.abcelem.rest)
152
+ continue;
153
+ var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1];
154
+ var x = furthestHead.x + ((asc) ? furthestHead.w : 0);
155
+ var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x);
156
+
157
+ var sy = (asc) ? -1.5 : 1.5;
158
+ if (isGrace) sy = sy * 2 / 3; // This makes the second beam on grace notes closer to the first one.
159
+ var duration = elem.abcelem.duration; // get the duration via abcelem because of triplets
160
+ if (duration === 0) duration = 0.25; // if this is stemless, then we use quarter note as the duration.
161
+ for (var durlog = getDurlog(duration); durlog < -3; durlog++) {
162
+ var index = -4 - durlog;
163
+ if (auxBeams[index]) {
164
+ auxBeams[index].single = false;
165
+ } else {
166
+ auxBeams[index] = {
167
+ x: x + ((asc) ? -0.6 : 0), y: bary + sy * (index + 1),
168
+ durlog: durlog, single: true
169
+ };
170
+ }
171
+ if (i > 0 && elem.abcelem.beambr && elem.abcelem.beambr <= (index+1)) {
172
+ if (!auxBeams[index].split)
173
+ auxBeams[index].split = [auxBeams[index].x];
174
+ var xPos = calcXPos(asc, elems[i-1], elem);
175
+ if (auxBeams[index].split[auxBeams[index].split.length-1] >= xPos[0]) {
176
+ // the reduction in beams leaves a note unattached so create a small flag for it.
177
+ xPos[0] += elem.w;
178
+ }
179
+ auxBeams[index].split.push(xPos[0]);
180
+ auxBeams[index].split.push(xPos[1]);
181
+ }
182
+ }
183
+
184
+ for (var j = auxBeams.length - 1; j >= 0; j--) {
185
+ if (i === elems.length - 1 || getDurlog(elems[i + 1].abcelem.duration) > (-j - 4)) {
186
+
187
+ var auxBeamEndX = x;
188
+ var auxBeamEndY = bary + sy * (j + 1);
189
+
190
+
191
+ if (auxBeams[j].single) {
192
+ auxBeamEndX = (i === 0) ? x + 5 : x - 5;
193
+ auxBeamEndY = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, auxBeamEndX) + sy * (j + 1);
194
+ }
195
+ var b = { startX: auxBeams[j].x, endX: auxBeamEndX, startY: auxBeams[j].y, endY: auxBeamEndY, dy: dy }
196
+ if (auxBeams[j].split !== undefined) {
197
+ var split = auxBeams[j].split;
198
+ if (b.endX <= split[split.length-1]) {
199
+ // the reduction in beams leaves the last note by itself, so create a little flag for it
200
+ split[split.length-1] -= elem.w;
201
+ }
202
+ split.push(b.endX);
203
+ b.split = auxBeams[j].split;
204
+ }
205
+ beams.push(b);
206
+ auxBeams = auxBeams.slice(0, j);
207
+ }
208
+ }
209
+ }
210
+ return beams;
211
+ }
212
+
213
+ module.exports = layoutBeam;
@@ -0,0 +1,56 @@
1
+ function getLeftEdgeOfStaff(renderer, getTextSize, voices, brace, bracket) {
2
+ var x = renderer.padding.left;
3
+
4
+ // find out how much space will be taken up by voice headers
5
+ var voiceheaderw = 0;
6
+ var i;
7
+ var size;
8
+ for (i=0;i<voices.length;i++) {
9
+ if(voices[i].header) {
10
+ size = getTextSize.calc(voices[i].header, 'voicefont', '');
11
+ voiceheaderw = Math.max(voiceheaderw,size.width);
12
+ }
13
+ }
14
+ voiceheaderw = addBraceSize(voiceheaderw, brace, getTextSize);
15
+ voiceheaderw = addBraceSize(voiceheaderw, bracket, getTextSize);
16
+
17
+ if (voiceheaderw) {
18
+ // Give enough spacing to the right - we use the width of an A for the amount of spacing.
19
+ var sizeW = getTextSize.calc("A", 'voicefont', '');
20
+ voiceheaderw += sizeW.width;
21
+ }
22
+ x += voiceheaderw;
23
+
24
+ var ofs = 0;
25
+ ofs = setBraceLocation(brace, x, ofs);
26
+ ofs = setBraceLocation(bracket, x, ofs);
27
+ return x + ofs;
28
+ }
29
+
30
+ function addBraceSize(voiceheaderw, brace, getTextSize) {
31
+ if (brace) {
32
+ for (var i = 0; i < brace.length; i++) {
33
+ if (brace[i].header) {
34
+ var size = getTextSize.calc(brace[i].header, 'voicefont', '');
35
+ voiceheaderw = Math.max(voiceheaderw,size.width);
36
+ }
37
+ }
38
+ }
39
+ return voiceheaderw;
40
+ }
41
+
42
+ function setBraceLocation(brace, x, ofs) {
43
+ if (brace) {
44
+ for (var i = 0; i < brace.length; i++) {
45
+ setLocation(x, brace[i]);
46
+ ofs = Math.max(ofs, brace[i].getWidth());
47
+ }
48
+ }
49
+ return ofs;
50
+ }
51
+
52
+ function setLocation(x, element) {
53
+ element.x = x;
54
+ }
55
+
56
+ module.exports = getLeftEdgeOfStaff;
@@ -0,0 +1,6 @@
1
+ function getBarYAt(startx, starty, endx, endy, x) {
2
+ return starty + (endy - starty) / (endx - startx) * (x - startx);
3
+ }
4
+
5
+ module.exports = getBarYAt;
6
+
@@ -0,0 +1,94 @@
1
+ var layoutVoice = require('./voice');
2
+ var setUpperAndLowerElements = require('./setUpperAndLowerElements');
3
+ var layoutStaffGroup = require('./staffGroup');
4
+ var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
5
+
6
+ var layout = function (renderer, abctune, width, space) {
7
+ var i;
8
+ var abcLine;
9
+ // Adjust the x-coordinates to their absolute positions
10
+ var maxWidth = width;
11
+ for(i=0; i<abctune.lines.length; i++) {
12
+ abcLine = abctune.lines[i];
13
+ if (abcLine.staff) {
14
+ setXSpacing(renderer, width, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
15
+ if (abcLine.staffGroup.w > maxWidth) maxWidth = abcLine.staffGroup.w;
16
+ }
17
+ }
18
+
19
+ // Layout the beams and add the stems to the beamed notes.
20
+ for(i=0; i<abctune.lines.length; i++) {
21
+ abcLine = abctune.lines[i];
22
+ if (abcLine.staffGroup && abcLine.staffGroup.voices) {
23
+ for (var j = 0; j < abcLine.staffGroup.voices.length; j++)
24
+ layoutVoice(abcLine.staffGroup.voices[j]);
25
+ setUpperAndLowerElements(renderer, abcLine.staffGroup);
26
+ }
27
+ }
28
+
29
+ // Set the staff spacing
30
+ // TODO-PER: we should have been able to do this by the time we called setUpperAndLowerElements, but for some reason the "bottom" element seems to be set as a side effect of setting the X spacing.
31
+ for(i=0; i<abctune.lines.length; i++) {
32
+ abcLine = abctune.lines[i];
33
+ if (abcLine.staffGroup) {
34
+ abcLine.staffGroup.setHeight();
35
+ }
36
+ }
37
+ return maxWidth;
38
+ }
39
+ // Do the x-axis positioning for a single line (a group of related staffs)
40
+ var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) {
41
+ var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
42
+ var newspace = space;
43
+ 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.)
44
+ var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
45
+ newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width+renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left+renderer.padding.right);
46
+ if (debug)
47
+ console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
48
+ if (newspace === null) break;
49
+ }
50
+ centerWholeRests(staffGroup.voices);
51
+ };
52
+
53
+ function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
54
+ if (isLastLine) {
55
+ if (stretchLast === undefined) {
56
+ if (lineWidth / targetWidth < 0.66) return null; // keep this for backward compatibility. The break isn't quite the same for some reason.
57
+ } else {
58
+ // "Stretch the last music line of a tune when it lacks less than the float fraction of the page width."
59
+ var lack = 1 - (lineWidth+padding) / targetWidth;
60
+ var stretch = lack < stretchLast;
61
+ if (!stretch) return null; // don't stretch last line too much
62
+ }
63
+ }
64
+ if (Math.abs(targetWidth-lineWidth) < 2) return null; // if we are already near the target width, we're done.
65
+ var relSpace = spacingUnits * spacing;
66
+ var constSpace = lineWidth - relSpace;
67
+ if (spacingUnits > 0) {
68
+ spacing = (targetWidth - constSpace) / spacingUnits;
69
+ if (spacing * minSpace > 50) {
70
+ spacing = 50 / minSpace;
71
+ }
72
+ return spacing;
73
+ }
74
+ return null;
75
+ }
76
+
77
+ function centerWholeRests(voices) {
78
+ // whole rests are a special case: if they are by themselves in a measure, then they should be centered.
79
+ // (If they are not by themselves, that is probably a user error, but we'll just center it between the two items to either side of it.)
80
+ for (var i = 0; i < voices.length; i++) {
81
+ var voice = voices[i];
82
+ // Look through all of the elements except for the first and last. If the whole note appears there then there isn't anything to center it between anyway.
83
+ for (var j = 1; j < voice.children.length-1; j++) {
84
+ var absElem = voice.children[j];
85
+ if (absElem.abcelem.rest && (absElem.abcelem.rest.type === 'whole' || absElem.abcelem.rest.type === 'multimeasure')) {
86
+ var before = voice.children[j-1];
87
+ var after = voice.children[j+1];
88
+ absElem.center(before, after);
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ module.exports = layout;
@@ -0,0 +1,232 @@
1
+ var spacing = require('../abc_spacing');
2
+
3
+ var setUpperAndLowerElements = function(renderer, staffGroup) {
4
+ // Each staff already has the top and bottom set, now we see if there are elements that are always on top and bottom, and resolve their pitch.
5
+ // Also, get the overall height of all the staves in this group.
6
+ var lastStaffBottom;
7
+ for (var i = 0; i < staffGroup.staffs.length; i++) {
8
+ var staff = staffGroup.staffs[i];
9
+ // the vertical order of elements that are above is: tempo, part, volume/dynamic, ending/chord, lyric
10
+ // the vertical order of elements that are below is: lyric, chord, volume/dynamic
11
+ var positionY = {
12
+ tempoHeightAbove: 0,
13
+ partHeightAbove: 0,
14
+ volumeHeightAbove: 0,
15
+ dynamicHeightAbove: 0,
16
+ endingHeightAbove: 0,
17
+ chordHeightAbove: 0,
18
+ lyricHeightAbove: 0,
19
+
20
+ lyricHeightBelow: 0,
21
+ chordHeightBelow: 0,
22
+ volumeHeightBelow: 0,
23
+ dynamicHeightBelow: 0
24
+ };
25
+
26
+ if (renderer.showDebug && renderer.showDebug.indexOf("box") >= 0) {
27
+ staff.originalTop = staff.top; // This is just being stored for debugging purposes.
28
+ staff.originalBottom = staff.bottom; // This is just being stored for debugging purposes.
29
+ }
30
+
31
+ incTop(staff, positionY, 'lyricHeightAbove');
32
+ incTop(staff, positionY, 'chordHeightAbove', staff.specialY.chordLines.above);
33
+ if (staff.specialY.endingHeightAbove) {
34
+ if (staff.specialY.chordHeightAbove)
35
+ staff.top += 2;
36
+ else
37
+ staff.top += staff.specialY.endingHeightAbove + margin;
38
+ positionY.endingHeightAbove = staff.top;
39
+ }
40
+ if (staff.specialY.dynamicHeightAbove && staff.specialY.volumeHeightAbove) {
41
+ staff.top += Math.max(staff.specialY.dynamicHeightAbove, staff.specialY.volumeHeightAbove) + margin;
42
+ positionY.dynamicHeightAbove = staff.top;
43
+ positionY.volumeHeightAbove = staff.top;
44
+ } else {
45
+ incTop(staff, positionY, 'dynamicHeightAbove');
46
+ incTop(staff, positionY, 'volumeHeightAbove');
47
+ }
48
+ incTop(staff, positionY, 'partHeightAbove');
49
+ incTop(staff, positionY, 'tempoHeightAbove');
50
+
51
+ if (staff.specialY.lyricHeightBelow) {
52
+ staff.specialY.lyricHeightBelow += renderer.spacing.vocal/spacing.STEP;
53
+ positionY.lyricHeightBelow = staff.bottom;
54
+ staff.bottom -= (staff.specialY.lyricHeightBelow + margin);
55
+ }
56
+ if (staff.specialY.chordHeightBelow) {
57
+ positionY.chordHeightBelow = staff.bottom;
58
+ var hgt = staff.specialY.chordHeightBelow;
59
+ if (staff.specialY.chordLines.below)
60
+ hgt *= staff.specialY.chordLines.below;
61
+ staff.bottom -= (hgt + margin);
62
+ }
63
+ if (staff.specialY.volumeHeightBelow && staff.specialY.dynamicHeightBelow) {
64
+ positionY.volumeHeightBelow = staff.bottom;
65
+ positionY.dynamicHeightBelow = staff.bottom;
66
+ staff.bottom -= (Math.max(staff.specialY.volumeHeightBelow, staff.specialY.dynamicHeightBelow) + margin);
67
+ } else if (staff.specialY.volumeHeightBelow) {
68
+ positionY.volumeHeightBelow = staff.bottom; staff.bottom -= (staff.specialY.volumeHeightBelow + margin);
69
+ } else if (staff.specialY.dynamicHeightBelow) {
70
+ positionY.dynamicHeightBelow = staff.bottom; staff.bottom -= (staff.specialY.dynamicHeightBelow + margin);
71
+ }
72
+
73
+ if (renderer.showDebug && renderer.showDebug.indexOf("box") >= 0)
74
+ staff.positionY = positionY; // This is just being stored for debugging purposes.
75
+
76
+ for (var j = 0; j < staff.voices.length; j++) {
77
+ var voice = staffGroup.voices[staff.voices[j]];
78
+ setUpperAndLowerVoiceElements(positionY, voice, renderer.spacing);
79
+ }
80
+ // We might need a little space in between staves if the staves haven't been pushed far enough apart by notes or extra vertical stuff.
81
+ // Only try to put in extra space if this isn't the top staff.
82
+ if (lastStaffBottom !== undefined) {
83
+ var thisStaffTop = staff.top - 10;
84
+ var forcedSpacingBetween = lastStaffBottom + thisStaffTop;
85
+ var minSpacingInPitches = renderer.spacing.systemStaffSeparation/spacing.STEP;
86
+ var addedSpace = minSpacingInPitches - forcedSpacingBetween;
87
+ if (addedSpace > 0)
88
+ staff.top += addedSpace;
89
+ }
90
+ lastStaffBottom = 2 - staff.bottom; // the staff starts at position 2 and the bottom variable is negative. Therefore to find out how large the bottom is, we reverse the sign of the bottom, and add the 2 in.
91
+
92
+ // Now we need a little margin on the top, so we'll just throw that in.
93
+ //staff.top += 4;
94
+ //console.log("Staff Y: ",i,heightInPitches,staff.top,staff.bottom);
95
+ }
96
+ //console.log("Staff Height: ",heightInPitches,this.height);
97
+ };
98
+
99
+ var margin = 1;
100
+ function incTop(staff, positionY, item, count) {
101
+ if (staff.specialY[item]) {
102
+ var height = staff.specialY[item];
103
+ if (count)
104
+ height *= count;
105
+ staff.top += height + margin;
106
+ positionY[item] = staff.top;
107
+ }
108
+ }
109
+
110
+ function setUpperAndLowerVoiceElements(positionY, voice, spacing) {
111
+ var i;
112
+ var abselem;
113
+ for (i = 0; i < voice.children.length; i++) {
114
+ abselem = voice.children[i];
115
+ setUpperAndLowerAbsoluteElements(positionY, abselem, spacing);
116
+ }
117
+ for (i = 0; i < voice.otherchildren.length; i++) {
118
+ abselem = voice.otherchildren[i];
119
+ switch (abselem.type) {
120
+ case 'CrescendoElem':
121
+ setUpperAndLowerCrescendoElements(positionY, abselem);
122
+ break;
123
+ case 'DynamicDecoration':
124
+ setUpperAndLowerDynamicElements(positionY, abselem);
125
+ break;
126
+ case 'EndingElem':
127
+ setUpperAndLowerEndingElements(positionY, abselem);
128
+ break;
129
+ }
130
+ }
131
+ }
132
+
133
+ // For each of the relative elements that can't be placed in advance (because their vertical placement depends on everything
134
+ // else on the line), this iterates through them and sets their pitch. By the time this is called, specialYResolved contains a
135
+ // hash with the vertical placement (in pitch units) for each type.
136
+ // TODO-PER: I think this needs to be separated by "above" and "below". How do we know that for dynamics at the point where they are being defined, though? We need a pass through all the relative elements to set "above" and "below".
137
+ function setUpperAndLowerAbsoluteElements(specialYResolved, element, spacing) {
138
+ // specialYResolved contains the actual pitch for each of the classes of elements.
139
+ for (var i = 0; i < element.children.length; i++) {
140
+ var child = element.children[i];
141
+ for (var key in element.specialY) { // for each class of element that needs to be placed vertically
142
+ if (element.specialY.hasOwnProperty(key)) {
143
+ if (child[key]) { // If this relative element has defined a height for this class of element
144
+ child.pitch = specialYResolved[key];
145
+ if (child.top === undefined) { // TODO-PER: HACK! Not sure this is the right place to do this.
146
+ if (child.type === 'TempoElement') {
147
+ setUpperAndLowerTempoElement(specialYResolved, child);
148
+ } else {
149
+ setUpperAndLowerRelativeElements(specialYResolved, child, spacing);
150
+ }
151
+ element.pushTop(child.top);
152
+ element.pushBottom(child.bottom);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ function setUpperAndLowerCrescendoElements(positionY, element) {
161
+ if (element.dynamicHeightAbove)
162
+ element.pitch = positionY.dynamicHeightAbove;
163
+ else
164
+ element.pitch = positionY.dynamicHeightBelow;
165
+ }
166
+
167
+ function setUpperAndLowerDynamicElements(positionY, element) {
168
+ if (element.volumeHeightAbove)
169
+ element.pitch = positionY.volumeHeightAbove;
170
+ else
171
+ element.pitch = positionY.volumeHeightBelow;
172
+ }
173
+
174
+ function setUpperAndLowerEndingElements(positionY, element) {
175
+ element.pitch = positionY.endingHeightAbove - 2;
176
+ }
177
+
178
+ function setUpperAndLowerTempoElement(positionY, element) {
179
+ element.pitch = positionY.tempoHeightAbove;
180
+ element.top = positionY.tempoHeightAbove;
181
+ element.bottom = positionY.tempoHeightAbove;
182
+ if (element.note) {
183
+ var tempoPitch = element.pitch - element.totalHeightInPitches + 1; // The pitch we receive is the top of the allotted area: change that to practically the bottom.
184
+ element.note.top = tempoPitch;
185
+ element.note.bottom = tempoPitch;
186
+ for (var i = 0; i < element.note.children.length; i++) {
187
+ var child = element.note.children[i];
188
+ child.top += tempoPitch;
189
+ child.bottom += tempoPitch;
190
+ child.pitch += tempoPitch;
191
+ if (child.pitch2 !== undefined)
192
+ child.pitch2 += tempoPitch;
193
+ }
194
+ }
195
+ }
196
+
197
+ function setUpperAndLowerRelativeElements(positionY, element, renderSpacing) {
198
+ switch(element.type) {
199
+ case "part":
200
+ element.top = positionY.partHeightAbove + element.height;
201
+ element.bottom = positionY.partHeightAbove;
202
+ break;
203
+ case "text":
204
+ case "chord":
205
+ if (element.chordHeightAbove) {
206
+ element.top = positionY.chordHeightAbove;
207
+ element.bottom = positionY.chordHeightAbove;
208
+ } else {
209
+ element.top = positionY.chordHeightBelow;
210
+ element.bottom = positionY.chordHeightBelow;
211
+ }
212
+ break;
213
+ case "lyric":
214
+ if (element.lyricHeightAbove) {
215
+ element.top = positionY.lyricHeightAbove;
216
+ element.bottom = positionY.lyricHeightAbove;
217
+ } else {
218
+ element.top = positionY.lyricHeightBelow + renderSpacing.vocal/spacing.STEP;
219
+ element.bottom = positionY.lyricHeightBelow + renderSpacing.vocal/spacing.STEP;
220
+ element.pitch -= renderSpacing.vocal/spacing.STEP;
221
+ }
222
+ break;
223
+ case "debug":
224
+ element.top = positionY.chordHeightAbove;
225
+ element.bottom = positionY.chordHeightAbove;
226
+ break;
227
+ }
228
+ if (element.pitch === undefined || element.top === undefined)
229
+ console.error("RelativeElement position not set.", element.type, element.pitch, element.top, positionY);
230
+ }
231
+
232
+ module.exports = setUpperAndLowerElements;