Repository navigation
Expand file tree
/
Copy pathScore.ts
More file actions
510 lines (456 loc) · 16.2 KB
/
Copy pathScore.ts
File metadata and controls
510 lines (456 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
import type { MasterBar } from '@coderline/alphatab/model/MasterBar';
import { RenderStylesheet } from '@coderline/alphatab/model/RenderStylesheet';
import { RepeatGroup } from '@coderline/alphatab/model/RepeatGroup';
import type { Track } from '@coderline/alphatab/model/Track';
import type { Settings } from '@coderline/alphatab/Settings';
import { ElementStyle } from '@coderline/alphatab/model/ElementStyle';
import { Bar } from '@coderline/alphatab/model/Bar';
import { Beat } from '@coderline/alphatab/model/Beat';
import { Voice } from '@coderline/alphatab/model/Voice';
import { Note } from '@coderline/alphatab/model/Note';
import type { NotationSettings } from '@coderline/alphatab/NotationSettings';
import { TextAlign } from '@coderline/alphatab/platform/ICanvas';
import type { BackingTrack } from '@coderline/alphatab/model/BackingTrack';
import { Automation, AutomationType, type FlatSyncPoint, SyncPointData } from '@coderline/alphatab/model/Automation';
/**
* Lists all graphical sub elements within a {@link Score} which can be styled via {@link Score.style}
* @public
*/
export enum ScoreSubElement {
/**
* The title of the song
*/
Title = 0,
/**
* The subtitle of the song
*/
SubTitle = 1,
/**
* The artist of the song
*/
Artist = 2,
/**
* The album of the song
*/
Album = 3,
/**
* The word author of the song
*/
Words = 4,
/**
* The Music author of the song
*/
Music = 5,
/**
* The Words&Music author of the song
*/
WordsAndMusic = 6,
/**
* The transcriber of the music sheet
*/
Transcriber = 7,
/**
* The copyright holder of the song
*/
Copyright = 8,
/**
* The second copyright line (typically something like 'All Rights Reserved')
*/
CopyrightSecondLine = 9,
/**
* The chord diagram list shown on top of the score.
*/
ChordDiagramList = 10
}
/**
* The additional style and display information for header and footer elements.
* @json
* @json_strict
* @public
*/
export class HeaderFooterStyle {
/**
* The template how the text should be formatted. Following placeholders exist and are filled from the song information:
* * `%TITLE%`
* * `%SUBTITLE%`
* * `%ARTIST%`
* * `%ALBUM%`
* * `%WORDS%`
* * `%WORDSMUSIC%`
* * `%MUSIC%`
* * `%TABBER%`
* * `%COPYRIGHT%`
*/
public template: string;
/**
* Whether the element should be visible. Overriden by {@link NotationSettings.elements} if specified.
*/
public isVisible?: boolean;
/**
* The alignment of the element on the page.
*/
public textAlign: TextAlign;
public constructor(
template: string = '',
isVisible: boolean | undefined = undefined,
textAlign: TextAlign = TextAlign.Left
) {
this.template = template;
this.isVisible = isVisible;
this.textAlign = textAlign;
}
public static equals(a: HeaderFooterStyle, b: HeaderFooterStyle) {
const aIsVisible = a.isVisible !== undefined ? a.isVisible! : true;
const bIsVisible = b.isVisible !== undefined ? b.isVisible! : true;
if (aIsVisible !== bIsVisible) {
return false;
}
if (a.template !== b.template) {
return false;
}
if (a.textAlign !== b.textAlign) {
return false;
}
return true;
}
public buildText(score: Score) {
let anyPlaceholderFilled = false;
let anyPlaceholder = false;
const replaced = this.template.replace(
HeaderFooterStyle._placeholderPattern,
(_match: string, variable: string) => {
anyPlaceholder = true;
let value = '';
switch (variable) {
case 'TITLE':
value = score.title;
break;
case 'SUBTITLE':
value = score.subTitle;
break;
case 'ARTIST':
value = score.artist;
break;
case 'ALBUM':
value = score.album;
break;
case 'WORDS':
case 'WORDSMUSIC':
value = score.words;
break;
case 'MUSIC':
value = score.music;
break;
case 'TABBER':
value = score.tab;
break;
case 'COPYRIGHT':
value = score.copyright;
break;
default:
value = '';
break;
}
if (value) {
anyPlaceholderFilled = true;
}
return value;
}
);
if (anyPlaceholder && !anyPlaceholderFilled) {
return '';
}
return replaced;
}
private static readonly _placeholderPattern = /%([^%]+)%/g;
}
/**
* Defines the custom styles for Scores.
* @json
* @json_strict
* @public
*/
export class ScoreStyle extends ElementStyle<ScoreSubElement> {
/**
* Changes additional style aspects fo the of the specified sub-element.
*/
public headerAndFooter: Map<ScoreSubElement, HeaderFooterStyle> = new Map<ScoreSubElement, HeaderFooterStyle>();
/**
* The default styles applied to headers and footers if not specified
*/
public static readonly defaultHeaderAndFooter: Map<ScoreSubElement, HeaderFooterStyle> = new Map<
ScoreSubElement,
HeaderFooterStyle
>([
[ScoreSubElement.Title, new HeaderFooterStyle('%TITLE%', undefined, TextAlign.Center)],
[ScoreSubElement.SubTitle, new HeaderFooterStyle('%SUBTITLE%', undefined, TextAlign.Center)],
[ScoreSubElement.Artist, new HeaderFooterStyle('%ARTIST%', undefined, TextAlign.Center)],
[ScoreSubElement.Album, new HeaderFooterStyle('%ALBUM%', undefined, TextAlign.Center)],
[ScoreSubElement.Words, new HeaderFooterStyle('Words by %WORDS%', undefined, TextAlign.Left)],
[ScoreSubElement.Music, new HeaderFooterStyle('Music by %MUSIC%', undefined, TextAlign.Right)],
[ScoreSubElement.WordsAndMusic, new HeaderFooterStyle('Words & Music by %MUSIC%', undefined, TextAlign.Right)],
[ScoreSubElement.Transcriber, new HeaderFooterStyle('Tabbed by %TABBER%', false, TextAlign.Right)],
[ScoreSubElement.Copyright, new HeaderFooterStyle('%COPYRIGHT%', undefined, TextAlign.Center)],
[
ScoreSubElement.CopyrightSecondLine,
new HeaderFooterStyle('All Rights Reserved - International Copyright Secured', true, TextAlign.Center)
]
]);
}
/**
* The score is the root node of the complete
* model. It stores the basic information of
* a song and stores the sub components.
* @json
* @json_strict
* @public
*/
export class Score {
private _currentRepeatGroup: RepeatGroup | null = null;
private _openedRepeatGroups: RepeatGroup[] = [];
private _properlyOpenedRepeatGroups: number = 0;
/**
* Resets all internal ID generators.
*/
public static resetIds() {
Bar.resetIds();
Beat.resetIds();
Voice.resetIds();
Note.resetIds();
}
/**
* The album of this song.
*/
public album: string = '';
/**
* The artist who performs this song.
*/
public artist: string = '';
/**
* The owner of the copyright of this song.
*/
public copyright: string = '';
/**
* Additional instructions
*/
public instructions: string = '';
/**
* The author of the music.
*/
public music: string = '';
/**
* Some additional notes about the song.
*/
public notices: string = '';
/**
* The subtitle of the song.
*/
public subTitle: string = '';
/**
* The title of the song.
*/
public title: string = '';
/**
* The author of the song lyrics
*/
public words: string = '';
/**
* The author of this tablature.
*/
public tab: string = '';
/**
* The initial tempo of the song in BPM. The tempo might change via {@link MasterBar.tempoAutomations}.
*/
public get tempo(): number {
return this.masterBars.length && this.masterBars[0].tempoAutomations.length > 0
? this.masterBars[0].tempoAutomations[0].value
: 120;
}
/**
* The name/label of the initial tempo.
*/
public get tempoLabel(): string {
return this.masterBars.length && this.masterBars[0].tempoAutomations.length > 0
? this.masterBars[0].tempoAutomations[0].text
: '';
}
/**
* Gets or sets a list of all masterbars contained in this song.
* @json_add addMasterBar
*/
public masterBars: MasterBar[] = [];
/**
* Gets or sets a list of all tracks contained in this song.
* @json_add addTrack
*/
public tracks: Track[] = [];
/**
* Defines how many bars are placed into the systems (rows) when displaying
* multiple tracks unless a value is set in the systemsLayout.
*/
public defaultSystemsLayout: number = 3;
/**
* Defines how many bars are placed into the systems (rows) when displaying
* multiple tracks.
*/
public systemsLayout: number[] = [];
/**
* Gets or sets the rendering stylesheet for this song.
*/
public stylesheet: RenderStylesheet = new RenderStylesheet();
/**
* Information about the backing track that can be used instead of the synthesized audio.
*/
public backingTrack: BackingTrack | undefined;
/**
* The style customizations for this item.
*/
public style?: ScoreStyle;
public rebuildRepeatGroups(): void {
this._currentRepeatGroup = null;
this._openedRepeatGroups = [];
this._properlyOpenedRepeatGroups = 0;
for (const bar of this.masterBars) {
this._addMasterBarToRepeatGroups(bar);
}
}
public addMasterBar(bar: MasterBar): void {
bar.score = this;
bar.index = this.masterBars.length;
if (this.masterBars.length !== 0) {
bar.previousMasterBar = this.masterBars[this.masterBars.length - 1];
bar.previousMasterBar.nextMasterBar = bar;
// NOTE: this will not work on anacrusis. Correct anacrusis durations are only working
// when there are beats with playback positions already computed which requires full finish
// chicken-egg problem here. temporarily forcing anacrusis length here to 0,
// .finish() will correct these times
bar.start =
bar.previousMasterBar.start +
(bar.previousMasterBar.isAnacrusis ? 0 : bar.previousMasterBar.calculateDuration());
}
this._addMasterBarToRepeatGroups(bar);
this.masterBars.push(bar);
}
/**
* Adds the given bar correctly into the current repeat group setup.
* @param bar
*/
private _addMasterBarToRepeatGroups(bar: MasterBar) {
// handling the repeats is quite tricky due to many invalid combinations a user might define
// there are also some complexities due to nested repeats and repeats with multiple endings but only one opening.
// all scenarios are handled below.
// NOTE: In all paths we need to ensure that the bar is added to some repeat group
// start a new repeat group if really a repeat is started
// or we don't have a group.
if (bar.isRepeatStart) {
// if the current group was already closed (this opening doesn't cause nesting)
// we consider the group as completed
if (this._currentRepeatGroup?.isClosed) {
this._openedRepeatGroups.pop();
this._properlyOpenedRepeatGroups--;
}
this._currentRepeatGroup = new RepeatGroup();
this._openedRepeatGroups.push(this._currentRepeatGroup);
this._properlyOpenedRepeatGroups++;
} else if (!this._currentRepeatGroup) {
this._currentRepeatGroup = new RepeatGroup();
this._openedRepeatGroups.push(this._currentRepeatGroup);
}
// close current group if there was one started
this._currentRepeatGroup.addMasterBar(bar);
// handle repeat ends
if (bar.isRepeatEnd) {
// if we have nested repeat groups a repeat end
// will treat the group as completed
if (this._properlyOpenedRepeatGroups > 1) {
this._openedRepeatGroups.pop();
this._properlyOpenedRepeatGroups--;
// restore outer group in cases like "open open close close"
this._currentRepeatGroup =
this._openedRepeatGroups.length > 0
? this._openedRepeatGroups[this._openedRepeatGroups.length - 1]
: null;
}
// else: if only one group is opened, this group stays active for
// scenarios like open close bar close
}
}
public addTrack(track: Track): void {
track.score = this;
track.index = this.tracks.length;
this.tracks.push(track);
}
public finish(settings: Settings): void {
const sharedDataBag = new Map<string, unknown>();
for (const t of this.tracks) {
t.finish(settings, sharedDataBag);
}
let barNumber = 1;
for (const mb of this.masterBars) {
if (mb.isAnacrusis) {
mb._realBarNumber = Number.NaN;
} else if (mb.customBarNumber) {
barNumber = mb.customBarNumber!;
mb._realBarNumber = barNumber;
barNumber++;
} else {
mb._realBarNumber = barNumber;
barNumber++;
}
mb.finish(sharedDataBag);
}
}
/**
* Applies the given list of {@link FlatSyncPoint} to this song.
* @param syncPoints The list of sync points to apply.
* @since 1.6.0
*/
public applyFlatSyncPoints(syncPoints: FlatSyncPoint[]) {
for (const b of this.masterBars) {
b.syncPoints = undefined;
}
for (const syncPoint of syncPoints) {
const automation = new Automation();
automation.ratioPosition = Math.min(1, Math.max(0, syncPoint.barPosition));
automation.type = AutomationType.SyncPoint;
automation.syncPointValue = new SyncPointData();
automation.syncPointValue.millisecondOffset = syncPoint.millisecondOffset;
automation.syncPointValue.barOccurence = syncPoint.barOccurence;
if (syncPoint.barIndex < this.masterBars.length) {
this.masterBars[syncPoint.barIndex].addSyncPoint(automation);
}
}
for (const b of this.masterBars) {
if (b.syncPoints) {
b.syncPoints!.sort((a, b) => {
const occurence = a.syncPointValue!.barOccurence - b.syncPointValue!.barOccurence;
if (occurence !== 0) {
return occurence;
}
return a.ratioPosition - b.ratioPosition;
});
}
}
}
/**
* Exports all sync points in this song to a {@link FlatSyncPoint} list.
* @since 1.6.0
*/
public exportFlatSyncPoints(): FlatSyncPoint[] {
const syncPoints: FlatSyncPoint[] = [];
for (const masterBar of this.masterBars) {
const masterBarSyncPoints = masterBar.syncPoints;
if (masterBarSyncPoints) {
for (const syncPoint of masterBarSyncPoints) {
syncPoints.push({
barIndex: masterBar.index,
barOccurence: syncPoint.syncPointValue!.barOccurence,
barPosition: syncPoint.ratioPosition,
millisecondOffset: syncPoint.syncPointValue!.millisecondOffset
});
}
}
}
return syncPoints;
}
}