@@ -15,10 +15,18 @@ import { Category, isAccidental, isGraceNote, isGraceNoteGroup, isStaveNote } fr
15
15
import { defined , log } from './util' ;
16
16
import { Voice } from './voice' ;
17
17
18
- export type Line = {
18
+ type StaveLineAccidentalLayoutMetrics = {
19
19
column : number ;
20
20
line : number ;
21
+ /**
22
+ * A flat line needs more clearance above than below. This is
23
+ * set to true if the accidental is either a flat or double flat.
24
+ */
21
25
flatLine : boolean ;
26
+ /**
27
+ * Double sharps need less clearance above and below than other
28
+ * accidentals.
29
+ */
22
30
dblSharpLine : boolean ;
23
31
numAcc : number ;
24
32
width : number ;
@@ -74,17 +82,21 @@ export class Accidental extends Modifier {
74
82
const additionalPadding = musicFont . lookupMetric ( 'accidental.leftPadding' ) ; // padding to the left of all accidentals
75
83
76
84
// A type used just in this formatting function.
77
- type AccidentalListItem = {
85
+ type AccidentalLinePositionsAndXSpaceNeeds = {
78
86
y ?: number ;
79
87
line : number ;
80
- shift : number ;
88
+ /**
89
+ * The amount by which the accidental requests notes be shifted to the right
90
+ * to accomodate its presence.
91
+ */
92
+ extraXSpaceNeeded : number ;
81
93
acc : Accidental ;
82
- lineSpace ?: number ;
94
+ spacingBetweenStaveLines ?: number ;
83
95
} ;
84
96
85
- const accList : AccidentalListItem [ ] = [ ] ;
97
+ const accidentalLinePositionsAndSpaceNeeds : AccidentalLinePositionsAndXSpaceNeeds [ ] = [ ] ;
86
98
let prevNote = undefined ;
87
- let shiftL = 0 ;
99
+ let extraXSpaceNeededForLeftDisplacedNotehead = 0 ;
88
100
89
101
// First determine the accidentals' Y positions from the note.keys
90
102
for ( let i = 0 ; i < accidentals . length ; ++ i ) {
@@ -94,75 +106,97 @@ export class Accidental extends Modifier {
94
106
const stave = note . getStave ( ) ;
95
107
const index = acc . checkIndex ( ) ;
96
108
const props = note . getKeyProps ( ) [ index ] ;
109
+
97
110
if ( note !== prevNote ) {
98
111
// Iterate through all notes to get the displaced pixels
99
112
for ( let n = 0 ; n < note . keys . length ; ++ n ) {
100
- shiftL = Math . max ( note . getLeftDisplacedHeadPx ( ) - note . getXShift ( ) , shiftL ) ;
113
+ // If the current extra left-space needed isn't as big as this note's,
114
+ // then we need to use this note's.
115
+ extraXSpaceNeededForLeftDisplacedNotehead = Math . max (
116
+ note . getLeftDisplacedHeadPx ( ) - note . getXShift ( ) ,
117
+ extraXSpaceNeededForLeftDisplacedNotehead
118
+ ) ;
101
119
}
102
120
prevNote = note ;
103
121
}
104
122
if ( stave ) {
105
123
const lineSpace = stave . getSpacingBetweenLines ( ) ;
106
124
const y = stave . getYForLine ( props . line ) ;
107
125
const accLine = Math . round ( ( y / lineSpace ) * 2 ) / 2 ;
108
- accList . push ( { y, line : accLine , shift : shiftL , acc, lineSpace } ) ;
126
+ accidentalLinePositionsAndSpaceNeeds . push ( {
127
+ y,
128
+ line : accLine ,
129
+ extraXSpaceNeeded : extraXSpaceNeededForLeftDisplacedNotehead ,
130
+ acc,
131
+ spacingBetweenStaveLines : lineSpace ,
132
+ } ) ;
109
133
} else {
110
- accList . push ( { line : props . line , shift : shiftL , acc } ) ;
134
+ accidentalLinePositionsAndSpaceNeeds . push ( {
135
+ line : props . line ,
136
+ extraXSpaceNeeded : extraXSpaceNeededForLeftDisplacedNotehead ,
137
+ acc,
138
+ } ) ;
111
139
}
112
140
}
113
141
114
142
// Sort accidentals by line number.
115
- accList . sort ( ( a , b ) => b . line - a . line ) ;
143
+ accidentalLinePositionsAndSpaceNeeds . sort ( ( a , b ) => b . line - a . line ) ;
116
144
117
- // FIXME: Confusing name. Each object in this array has a property called `line`.
118
- // So if this is a list of lines, you end up with: `line.line` which is very awkward.
119
- const lineList : Line [ ] = [ ] ;
145
+ const staveLineAccidentalLayoutMetrics : StaveLineAccidentalLayoutMetrics [ ] = [ ] ;
120
146
121
147
// amount by which all accidentals must be shifted right or left for
122
148
// stem flipping, notehead shifting concerns.
123
- let accShift = 0 ;
124
- let previousLine = undefined ;
149
+ let maxExtraXSpaceNeeded = 0 ;
125
150
126
- // Create an array of unique line numbers (lineList) from accList
127
- for ( let i = 0 ; i < accList . length ; i ++ ) {
128
- const acc = accList [ i ] ;
151
+ // Create an array of unique line numbers (staveLineAccidentalLayoutMetrics)
152
+ // from accidentalLinePositionsAndSpaceNeeds
153
+ for ( let i = 0 ; i < accidentalLinePositionsAndSpaceNeeds . length ; i ++ ) {
154
+ const accidentalLinePositionAndSpaceNeeds = accidentalLinePositionsAndSpaceNeeds [ i ] ;
129
155
130
- // if this is the first line, or a new line, add a lineList
131
- if ( previousLine === undefined || previousLine !== acc . line ) {
132
- lineList . push ( {
133
- line : acc . line ,
156
+ const priorLineMetric = staveLineAccidentalLayoutMetrics [ staveLineAccidentalLayoutMetrics . length - 1 ] ;
157
+ let currentLineMetric : StaveLineAccidentalLayoutMetrics ;
158
+
159
+ // if this is the first line, or a new line, add a staveLineAccidentalLayoutMetric
160
+ if ( ! priorLineMetric || priorLineMetric ?. line !== accidentalLinePositionAndSpaceNeeds . line ) {
161
+ currentLineMetric = {
162
+ line : accidentalLinePositionAndSpaceNeeds . line ,
134
163
flatLine : true ,
135
164
dblSharpLine : true ,
136
165
numAcc : 0 ,
137
166
width : 0 ,
138
167
column : 0 ,
139
- } ) ;
168
+ } ;
169
+ staveLineAccidentalLayoutMetrics . push ( currentLineMetric ) ;
170
+ } else {
171
+ currentLineMetric = priorLineMetric ;
140
172
}
173
+
141
174
// if this accidental is not a flat, the accidental needs 3.0 lines lower
142
175
// clearance instead of 2.5 lines for b or bb.
143
- // FIXME: Naming could use work. acc.acc is very awkward
144
- if ( acc . acc . type !== 'b' && acc . acc . type !== 'bb' ) {
145
- lineList [ lineList . length - 1 ] . flatLine = false ;
176
+ if (
177
+ accidentalLinePositionAndSpaceNeeds . acc . type !== 'b' &&
178
+ accidentalLinePositionAndSpaceNeeds . acc . type !== 'bb'
179
+ ) {
180
+ currentLineMetric . flatLine = false ;
146
181
}
147
182
148
183
// if this accidental is not a double sharp, the accidental needs 3.0 lines above
149
- if ( acc . acc . type !== '##' ) {
150
- lineList [ lineList . length - 1 ] . dblSharpLine = false ;
184
+ if ( accidentalLinePositionAndSpaceNeeds . acc . type !== '##' ) {
185
+ currentLineMetric . dblSharpLine = false ;
151
186
}
152
187
153
188
// Track how many accidentals are on this line:
154
- lineList [ lineList . length - 1 ] . numAcc ++ ;
189
+ currentLineMetric . numAcc ++ ;
155
190
156
191
// Track the total x_offset needed for this line which will be needed
157
192
// for formatting lines w/ multiple accidentals:
158
193
159
194
// width = accidental width + universal spacing between accidentals
160
- lineList [ lineList . length - 1 ] . width += acc . acc . getWidth ( ) + accidentalSpacing ;
161
-
162
- // if this accShift is larger, use it to keep first column accidentals in the same line
163
- accShift = acc . shift > accShift ? acc . shift : accShift ;
195
+ currentLineMetric . width += accidentalLinePositionAndSpaceNeeds . acc . getWidth ( ) + accidentalSpacing ;
164
196
165
- previousLine = acc . line ;
197
+ // if this extraXSpaceNeeded is the largest so far, use it as the starting point for
198
+ // all accidental columns.
199
+ maxExtraXSpaceNeeded = Math . max ( accidentalLinePositionAndSpaceNeeds . extraXSpaceNeeded , maxExtraXSpaceNeeded ) ;
166
200
}
167
201
168
202
// ### Place Accidentals in Columns
@@ -186,14 +220,19 @@ export class Accidental extends Modifier {
186
220
let totalColumns = 0 ;
187
221
188
222
// establish the boundaries for a group of notes with clashing accidentals:
189
- for ( let i = 0 ; i < lineList . length ; i ++ ) {
223
+ for ( let i = 0 ; i < staveLineAccidentalLayoutMetrics . length ; i ++ ) {
190
224
let noFurtherConflicts = false ;
191
225
const groupStart = i ;
192
226
let groupEnd = i ;
193
227
194
- while ( groupEnd + 1 < lineList . length && ! noFurtherConflicts ) {
228
+ while ( groupEnd + 1 < staveLineAccidentalLayoutMetrics . length && ! noFurtherConflicts ) {
195
229
// if this note conflicts with the next:
196
- if ( this . checkCollision ( lineList [ groupEnd ] , lineList [ groupEnd + 1 ] ) ) {
230
+ if (
231
+ this . checkCollision (
232
+ staveLineAccidentalLayoutMetrics [ groupEnd ] ,
233
+ staveLineAccidentalLayoutMetrics [ groupEnd + 1 ]
234
+ )
235
+ ) {
197
236
// include the next note in the group:
198
237
groupEnd ++ ;
199
238
} else {
@@ -202,7 +241,7 @@ export class Accidental extends Modifier {
202
241
}
203
242
204
243
// Gets an a line from the `lineList`, relative to the current group
205
- const getGroupLine = ( index : number ) => lineList [ groupStart + index ] ;
244
+ const getGroupLine = ( index : number ) => staveLineAccidentalLayoutMetrics [ groupStart + index ] ;
206
245
const getGroupLines = ( indexes : number [ ] ) => indexes . map ( getGroupLine ) ;
207
246
const lineDifference = ( indexA : number , indexB : number ) => {
208
247
const [ a , b ] = getGroupLines ( [ indexA , indexB ] ) . map ( ( item ) => item . line ) ;
@@ -216,7 +255,12 @@ export class Accidental extends Modifier {
216
255
const groupLength = groupEnd - groupStart + 1 ;
217
256
218
257
// Set the accidental column for each line of the group
219
- let endCase = this . checkCollision ( lineList [ groupStart ] , lineList [ groupEnd ] ) ? 'a' : 'b' ;
258
+ let endCase = this . checkCollision (
259
+ staveLineAccidentalLayoutMetrics [ groupStart ] ,
260
+ staveLineAccidentalLayoutMetrics [ groupEnd ]
261
+ )
262
+ ? 'a'
263
+ : 'b' ;
220
264
221
265
switch ( groupLength ) {
222
266
case 3 :
@@ -259,8 +303,13 @@ export class Accidental extends Modifier {
259
303
let collisionDetected = true ;
260
304
while ( collisionDetected === true ) {
261
305
collisionDetected = false ;
262
- for ( let line = 0 ; line + patternLength < lineList . length ; line ++ ) {
263
- if ( this . checkCollision ( lineList [ line ] , lineList [ line + patternLength ] ) ) {
306
+ for ( let line = 0 ; line + patternLength < staveLineAccidentalLayoutMetrics . length ; line ++ ) {
307
+ if (
308
+ this . checkCollision (
309
+ staveLineAccidentalLayoutMetrics [ line ] ,
310
+ staveLineAccidentalLayoutMetrics [ line + patternLength ]
311
+ )
312
+ ) {
264
313
collisionDetected = true ;
265
314
patternLength ++ ;
266
315
break ;
@@ -270,15 +319,15 @@ export class Accidental extends Modifier {
270
319
// Then, assign a column to each line of accidentals
271
320
for ( groupMember = i ; groupMember <= groupEnd ; groupMember ++ ) {
272
321
column = ( ( groupMember - i ) % patternLength ) + 1 ;
273
- lineList [ groupMember ] . column = column ;
322
+ staveLineAccidentalLayoutMetrics [ groupMember ] . column = column ;
274
323
totalColumns = totalColumns > column ? totalColumns : column ;
275
324
}
276
325
} else {
277
326
// If the group contains fewer than seven members, use the layouts from
278
327
// the Tables.accidentalColumnsTable (See: tables.ts).
279
328
for ( groupMember = i ; groupMember <= groupEnd ; groupMember ++ ) {
280
329
column = Tables . accidentalColumnsTable [ groupLength ] [ endCase ] [ groupMember - i ] ;
281
- lineList [ groupMember ] . column = column ;
330
+ staveLineAccidentalLayoutMetrics [ groupMember ] . column = column ;
282
331
totalColumns = totalColumns > column ? totalColumns : column ;
283
332
}
284
333
}
@@ -308,12 +357,12 @@ export class Accidental extends Modifier {
308
357
columnXOffsets [ i ] = 0 ;
309
358
}
310
359
311
- columnWidths [ 0 ] = accShift + leftShift ;
312
- columnXOffsets [ 0 ] = accShift + leftShift ;
360
+ columnWidths [ 0 ] = leftShift + maxExtraXSpaceNeeded ;
361
+ columnXOffsets [ 0 ] = leftShift ;
313
362
314
363
// Fill columnWidths with widest needed x-space;
315
364
// this is what keeps the columns parallel.
316
- lineList . forEach ( ( line ) => {
365
+ staveLineAccidentalLayoutMetrics . forEach ( ( line ) => {
317
366
if ( line . width > columnWidths [ line . column ] ) columnWidths [ line . column ] = line . width ;
318
367
} ) ;
319
368
@@ -325,26 +374,25 @@ export class Accidental extends Modifier {
325
374
const totalShift = columnXOffsets [ columnXOffsets . length - 1 ] ;
326
375
// Set the xShift for each accidental according to column offsets:
327
376
let accCount = 0 ;
328
- lineList . forEach ( ( line ) => {
377
+ staveLineAccidentalLayoutMetrics . forEach ( ( line ) => {
329
378
let lineWidth = 0 ;
330
379
const lastAccOnLine = accCount + line . numAcc ;
331
380
// handle all of the accidentals on a given line:
332
381
for ( accCount ; accCount < lastAccOnLine ; accCount ++ ) {
333
- const xShift = columnXOffsets [ line . column - 1 ] + lineWidth ;
334
- accList [ accCount ] . acc . setXShift ( xShift ) ;
382
+ const xShift = columnXOffsets [ line . column - 1 ] + lineWidth + maxExtraXSpaceNeeded ;
383
+ accidentalLinePositionsAndSpaceNeeds [ accCount ] . acc . setXShift ( xShift ) ;
335
384
// keep track of the width of accidentals we've added so far, so that when
336
385
// we loop, we add space for them.
337
- lineWidth += accList [ accCount ] . acc . getWidth ( ) + accidentalSpacing ;
386
+ lineWidth += accidentalLinePositionsAndSpaceNeeds [ accCount ] . acc . getWidth ( ) + accidentalSpacing ;
338
387
L ( 'Line, accCount, shift: ' , line . line , accCount , xShift ) ;
339
388
}
340
389
} ) ;
341
-
342
390
// update the overall layout with the full width of the accidental shapes:
343
- state . left_shift + = totalShift + additionalPadding ;
391
+ state . left_shift = totalShift + additionalPadding ;
344
392
}
345
393
346
394
/** Helper function to determine whether two lines of accidentals collide vertically */
347
- static checkCollision ( line1 : Line , line2 : Line ) : boolean {
395
+ static checkCollision ( line1 : StaveLineAccidentalLayoutMetrics , line2 : StaveLineAccidentalLayoutMetrics ) : boolean {
348
396
let clearance = line2 . line - line1 . line ;
349
397
let clearanceRequired = 3 ;
350
398
// But less clearance is required for certain accidentals: b, bb and ##.
0 commit comments