@@ -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,95 +82,147 @@ 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 ) {
91
103
const acc : Accidental = accidentals [ i ] ;
92
104
93
105
const note = acc . getNote ( ) ;
106
+ const stemDirection = note . getStemDirection ( ) ;
94
107
const stave = note . getStave ( ) ;
95
108
const index = acc . checkIndex ( ) ;
96
109
const props = note . getKeyProps ( ) [ index ] ;
110
+
111
+ let headDisplacementXShift = 0 ;
97
112
if ( note !== prevNote ) {
98
113
// Iterate through all notes to get the displaced pixels
99
114
for ( let n = 0 ; n < note . keys . length ; ++ n ) {
100
- shiftL = Math . max ( note . getLeftDisplacedHeadPx ( ) - note . getXShift ( ) , shiftL ) ;
115
+ const priorShiftL = extraXSpaceNeededForLeftDisplacedNotehead ;
116
+ // The extra space we need for the accidental to the left is the amount
117
+ // that the left-displaced notehead has been shifted left, less any
118
+ // extra space that has already been added via the note's xShift.
119
+ const leftDisplacedNoteheadShift = note . getLeftDisplacedHeadPx ( ) - note . getXShift ( ) ;
120
+ // If the current extra left-space needed (shiftL) isn't as big as that,
121
+ // then we need to set it. But, this will give the accidental ownership of
122
+ // that x-space, and later when formatting the note, its x_begin would have
123
+ // twice the getLeftDisplacedHeadPx included in it. So: we need to remove
124
+ // it from the note's x shift.
125
+ // if (leftDisplacedNoteheadShift > extraXSpaceNeededForLeftDisplacedNotehead) {
126
+ // extraXSpaceNeededForLeftDisplacedNotehead = leftDisplacedNoteheadShift;
127
+ // note.setLeftDisplacedHeadPx(-note.getXShift());
128
+ // }
129
+ extraXSpaceNeededForLeftDisplacedNotehead = Math . max (
130
+ note . getLeftDisplacedHeadPx ( ) - note . getXShift ( ) ,
131
+ extraXSpaceNeededForLeftDisplacedNotehead
132
+ ) ;
133
+ headDisplacementXShift = note . getLeftDisplacedHeadPx ( ) ;
134
+
135
+ // console.log({
136
+ // getLeftDisplacedHeadPx: note.getLeftDisplacedHeadPx(),
137
+ // getXShift: note.getXShift(),
138
+ // getLeftDisplacedHeadPx_minus_getXShift: note.getLeftDisplacedHeadPx() - note.getXShift(),
139
+ // shiftL: extraXSpaceNeededForLeftDisplacedNotehead,
140
+ // priorShiftL,
141
+ // });
101
142
}
102
143
prevNote = note ;
103
144
}
104
145
if ( stave ) {
105
146
const lineSpace = stave . getSpacingBetweenLines ( ) ;
106
147
const y = stave . getYForLine ( props . line ) ;
107
148
const accLine = Math . round ( ( y / lineSpace ) * 2 ) / 2 ;
108
- accList . push ( { y, line : accLine , shift : shiftL , acc, lineSpace } ) ;
149
+ accidentalLinePositionsAndSpaceNeeds . push ( {
150
+ y,
151
+ line : accLine ,
152
+ extraXSpaceNeeded : extraXSpaceNeededForLeftDisplacedNotehead ,
153
+ acc,
154
+ spacingBetweenStaveLines : lineSpace ,
155
+ } ) ;
109
156
} else {
110
- accList . push ( { line : props . line , shift : shiftL , acc } ) ;
157
+ accidentalLinePositionsAndSpaceNeeds . push ( {
158
+ line : props . line ,
159
+ extraXSpaceNeeded : extraXSpaceNeededForLeftDisplacedNotehead ,
160
+ acc,
161
+ } ) ;
111
162
}
112
163
}
113
164
114
165
// Sort accidentals by line number.
115
- accList . sort ( ( a , b ) => b . line - a . line ) ;
166
+ accidentalLinePositionsAndSpaceNeeds . sort ( ( a , b ) => b . line - a . line ) ;
116
167
117
168
// FIXME: Confusing name. Each object in this array has a property called `line`.
118
169
// So if this is a list of lines, you end up with: `line.line` which is very awkward.
119
- const lineList : Line [ ] = [ ] ;
170
+ const staveLineAccidentalLayoutMetrics : StaveLineAccidentalLayoutMetrics [ ] = [ ] ;
120
171
121
172
// amount by which all accidentals must be shifted right or left for
122
173
// stem flipping, notehead shifting concerns.
123
- let accShift = 0 ;
124
- let previousLine = undefined ;
174
+ let maxExtraXSpaceNeeded = 0 ;
125
175
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 ] ;
176
+ // Create an array of unique line numbers (staveLineAccidentalLayoutMetrics)
177
+ // from accidentalLinePositionsAndSpaceNeeds
178
+ for ( let i = 0 ; i < accidentalLinePositionsAndSpaceNeeds . length ; i ++ ) {
179
+ const accidentalLinePositionAndSpaceNeeds = accidentalLinePositionsAndSpaceNeeds [ i ] ;
129
180
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 ,
181
+ const priorLineMetric = staveLineAccidentalLayoutMetrics [ staveLineAccidentalLayoutMetrics . length - 1 ] ;
182
+ let currentLineMetric : StaveLineAccidentalLayoutMetrics ;
183
+
184
+ // if this is the first line, or a new line, add a staveLineAccidentalLayoutMetric
185
+ if ( ! priorLineMetric || priorLineMetric ?. line !== accidentalLinePositionAndSpaceNeeds . line ) {
186
+ currentLineMetric = {
187
+ line : accidentalLinePositionAndSpaceNeeds . line ,
134
188
flatLine : true ,
135
189
dblSharpLine : true ,
136
190
numAcc : 0 ,
137
191
width : 0 ,
138
192
column : 0 ,
139
- } ) ;
193
+ } ;
194
+ staveLineAccidentalLayoutMetrics . push ( currentLineMetric ) ;
195
+ } else {
196
+ currentLineMetric = priorLineMetric ;
140
197
}
198
+
141
199
// if this accidental is not a flat, the accidental needs 3.0 lines lower
142
200
// clearance instead of 2.5 lines for b or bb.
143
201
// 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 ;
202
+ if (
203
+ accidentalLinePositionAndSpaceNeeds . acc . type !== 'b' &&
204
+ accidentalLinePositionAndSpaceNeeds . acc . type !== 'bb'
205
+ ) {
206
+ currentLineMetric . flatLine = false ;
146
207
}
147
208
148
209
// 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 ;
210
+ if ( accidentalLinePositionAndSpaceNeeds . acc . type !== '##' ) {
211
+ currentLineMetric . dblSharpLine = false ;
151
212
}
152
213
153
214
// Track how many accidentals are on this line:
154
- lineList [ lineList . length - 1 ] . numAcc ++ ;
215
+ currentLineMetric . numAcc ++ ;
155
216
156
217
// Track the total x_offset needed for this line which will be needed
157
218
// for formatting lines w/ multiple accidentals:
158
219
159
220
// 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 ;
221
+ currentLineMetric . width += accidentalLinePositionAndSpaceNeeds . acc . getWidth ( ) + accidentalSpacing ;
164
222
165
- previousLine = acc . line ;
223
+ // if this extraXSpaceNeeded is the largest so far, use it as the starting point for
224
+ // all accidental columns.
225
+ maxExtraXSpaceNeeded = Math . max ( accidentalLinePositionAndSpaceNeeds . extraXSpaceNeeded , maxExtraXSpaceNeeded ) ;
166
226
}
167
227
168
228
// ### Place Accidentals in Columns
@@ -186,14 +246,19 @@ export class Accidental extends Modifier {
186
246
let totalColumns = 0 ;
187
247
188
248
// establish the boundaries for a group of notes with clashing accidentals:
189
- for ( let i = 0 ; i < lineList . length ; i ++ ) {
249
+ for ( let i = 0 ; i < staveLineAccidentalLayoutMetrics . length ; i ++ ) {
190
250
let noFurtherConflicts = false ;
191
251
const groupStart = i ;
192
252
let groupEnd = i ;
193
253
194
- while ( groupEnd + 1 < lineList . length && ! noFurtherConflicts ) {
254
+ while ( groupEnd + 1 < staveLineAccidentalLayoutMetrics . length && ! noFurtherConflicts ) {
195
255
// if this note conflicts with the next:
196
- if ( this . checkCollision ( lineList [ groupEnd ] , lineList [ groupEnd + 1 ] ) ) {
256
+ if (
257
+ this . checkCollision (
258
+ staveLineAccidentalLayoutMetrics [ groupEnd ] ,
259
+ staveLineAccidentalLayoutMetrics [ groupEnd + 1 ]
260
+ )
261
+ ) {
197
262
// include the next note in the group:
198
263
groupEnd ++ ;
199
264
} else {
@@ -202,7 +267,7 @@ export class Accidental extends Modifier {
202
267
}
203
268
204
269
// Gets an a line from the `lineList`, relative to the current group
205
- const getGroupLine = ( index : number ) => lineList [ groupStart + index ] ;
270
+ const getGroupLine = ( index : number ) => staveLineAccidentalLayoutMetrics [ groupStart + index ] ;
206
271
const getGroupLines = ( indexes : number [ ] ) => indexes . map ( getGroupLine ) ;
207
272
const lineDifference = ( indexA : number , indexB : number ) => {
208
273
const [ a , b ] = getGroupLines ( [ indexA , indexB ] ) . map ( ( item ) => item . line ) ;
@@ -216,7 +281,12 @@ export class Accidental extends Modifier {
216
281
const groupLength = groupEnd - groupStart + 1 ;
217
282
218
283
// Set the accidental column for each line of the group
219
- let endCase = this . checkCollision ( lineList [ groupStart ] , lineList [ groupEnd ] ) ? 'a' : 'b' ;
284
+ let endCase = this . checkCollision (
285
+ staveLineAccidentalLayoutMetrics [ groupStart ] ,
286
+ staveLineAccidentalLayoutMetrics [ groupEnd ]
287
+ )
288
+ ? 'a'
289
+ : 'b' ;
220
290
221
291
switch ( groupLength ) {
222
292
case 3 :
@@ -259,8 +329,13 @@ export class Accidental extends Modifier {
259
329
let collisionDetected = true ;
260
330
while ( collisionDetected === true ) {
261
331
collisionDetected = false ;
262
- for ( let line = 0 ; line + patternLength < lineList . length ; line ++ ) {
263
- if ( this . checkCollision ( lineList [ line ] , lineList [ line + patternLength ] ) ) {
332
+ for ( let line = 0 ; line + patternLength < staveLineAccidentalLayoutMetrics . length ; line ++ ) {
333
+ if (
334
+ this . checkCollision (
335
+ staveLineAccidentalLayoutMetrics [ line ] ,
336
+ staveLineAccidentalLayoutMetrics [ line + patternLength ]
337
+ )
338
+ ) {
264
339
collisionDetected = true ;
265
340
patternLength ++ ;
266
341
break ;
@@ -270,15 +345,15 @@ export class Accidental extends Modifier {
270
345
// Then, assign a column to each line of accidentals
271
346
for ( groupMember = i ; groupMember <= groupEnd ; groupMember ++ ) {
272
347
column = ( ( groupMember - i ) % patternLength ) + 1 ;
273
- lineList [ groupMember ] . column = column ;
348
+ staveLineAccidentalLayoutMetrics [ groupMember ] . column = column ;
274
349
totalColumns = totalColumns > column ? totalColumns : column ;
275
350
}
276
351
} else {
277
352
// If the group contains fewer than seven members, use the layouts from
278
353
// the Tables.accidentalColumnsTable (See: tables.ts).
279
354
for ( groupMember = i ; groupMember <= groupEnd ; groupMember ++ ) {
280
355
column = Tables . accidentalColumnsTable [ groupLength ] [ endCase ] [ groupMember - i ] ;
281
- lineList [ groupMember ] . column = column ;
356
+ staveLineAccidentalLayoutMetrics [ groupMember ] . column = column ;
282
357
totalColumns = totalColumns > column ? totalColumns : column ;
283
358
}
284
359
}
@@ -308,12 +383,14 @@ export class Accidental extends Modifier {
308
383
columnXOffsets [ i ] = 0 ;
309
384
}
310
385
311
- columnWidths [ 0 ] = accShift + leftShift ;
312
- columnXOffsets [ 0 ] = accShift + leftShift ;
386
+ // columnWidths[0] = maxExtraXSpaceNeeded + leftShift;
387
+ // columnXOffsets[0] = maxExtraXSpaceNeeded + leftShift;
388
+ columnWidths [ 0 ] = leftShift + maxExtraXSpaceNeeded ;
389
+ columnXOffsets [ 0 ] = leftShift ;
313
390
314
391
// Fill columnWidths with widest needed x-space;
315
392
// this is what keeps the columns parallel.
316
- lineList . forEach ( ( line ) => {
393
+ staveLineAccidentalLayoutMetrics . forEach ( ( line ) => {
317
394
if ( line . width > columnWidths [ line . column ] ) columnWidths [ line . column ] = line . width ;
318
395
} ) ;
319
396
@@ -325,26 +402,27 @@ export class Accidental extends Modifier {
325
402
const totalShift = columnXOffsets [ columnXOffsets . length - 1 ] ;
326
403
// Set the xShift for each accidental according to column offsets:
327
404
let accCount = 0 ;
328
- lineList . forEach ( ( line ) => {
405
+ staveLineAccidentalLayoutMetrics . forEach ( ( line ) => {
329
406
let lineWidth = 0 ;
330
407
const lastAccOnLine = accCount + line . numAcc ;
331
408
// handle all of the accidentals on a given line:
332
409
for ( accCount ; accCount < lastAccOnLine ; accCount ++ ) {
333
- const xShift = columnXOffsets [ line . column - 1 ] + lineWidth ;
334
- accList [ accCount ] . acc . setXShift ( xShift ) ;
410
+ const xShift = columnXOffsets [ line . column - 1 ] + lineWidth + maxExtraXSpaceNeeded ;
411
+ accidentalLinePositionsAndSpaceNeeds [ accCount ] . acc . setXShift ( xShift ) ;
335
412
// keep track of the width of accidentals we've added so far, so that when
336
413
// we loop, we add space for them.
337
- lineWidth += accList [ accCount ] . acc . getWidth ( ) + accidentalSpacing ;
414
+ lineWidth += accidentalLinePositionsAndSpaceNeeds [ accCount ] . acc . getWidth ( ) + accidentalSpacing ;
415
+ // console.log('Line, accCount, shift: ', line.line, accCount, xShift);
338
416
L ( 'Line, accCount, shift: ' , line . line , accCount , xShift ) ;
339
417
}
340
418
} ) ;
341
-
419
+ // console.log({ totalShift, columnXOffsets, additionalPadding });
342
420
// update the overall layout with the full width of the accidental shapes:
343
- state . left_shift + = totalShift + additionalPadding ;
421
+ state . left_shift = totalShift + additionalPadding ;
344
422
}
345
423
346
424
/** Helper function to determine whether two lines of accidentals collide vertically */
347
- static checkCollision ( line1 : Line , line2 : Line ) : boolean {
425
+ static checkCollision ( line1 : StaveLineAccidentalLayoutMetrics , line2 : StaveLineAccidentalLayoutMetrics ) : boolean {
348
426
let clearance = line2 . line - line1 . line ;
349
427
let clearanceRequired = 3 ;
350
428
// But less clearance is required for certain accidentals: b, bb and ##.
0 commit comments