@coderline/alphatab 1.5.0-alpha.1394 → 1.5.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.
@@ -1,5 +1,5 @@
1
- /**
2
- * alphaTab v1.5.0-alpha.1394 (develop, build 1394)
1
+ /*!
2
+ * alphaTab v1.5.0 (, build 18)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -44,8 +44,13 @@
44
44
  * Copyright: Copyright (c) 2002-2020 Xiph.org Foundation
45
45
  * URL: https://github.com/xiph/vorbis
46
46
  * Purpose: NVorbis adopted some code from libvorbis.
47
+ *
48
+ * @preserve
49
+ * @license
47
50
  */
48
51
 
52
+ if(typeof Symbol.dispose==='undefined'){Symbol.dispose = Symbol('Symbol.dispose')}
53
+
49
54
  /**
50
55
  * Lists all layout modes that are supported.
51
56
  */
@@ -2570,6 +2575,321 @@ class MasterBar {
2570
2575
  }
2571
2576
  MasterBar.MaxAlternateEndings = 8;
2572
2577
 
2578
+ // The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT,
2579
+ // developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont)
2580
+ // TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny
2581
+ // Licensed under: MPL-2.0
2582
+ class SynthConstants {
2583
+ }
2584
+ SynthConstants.DefaultChannelCount = 16 + 1;
2585
+ SynthConstants.MetronomeChannel = SynthConstants.DefaultChannelCount - 1;
2586
+ SynthConstants.MetronomeKey = 33;
2587
+ SynthConstants.AudioChannels = 2;
2588
+ SynthConstants.MinVolume = 0;
2589
+ SynthConstants.MinProgram = 0;
2590
+ SynthConstants.MaxProgram = 127;
2591
+ SynthConstants.MinPlaybackSpeed = 0.125;
2592
+ SynthConstants.MaxPlaybackSpeed = 8;
2593
+ SynthConstants.PercussionChannel = 9;
2594
+ SynthConstants.PercussionBank = 128;
2595
+ /**
2596
+ * The Midi Pitch bend message is a 15-bit value
2597
+ */
2598
+ SynthConstants.MaxPitchWheel = 0x4000;
2599
+ /**
2600
+ * The Midi 2.0 Pitch bend message is a 32-bit value
2601
+ */
2602
+ SynthConstants.MaxPitchWheel20 = 0x100000000;
2603
+ /**
2604
+ * The pitch wheel value for no pitch change at all.
2605
+ */
2606
+ SynthConstants.DefaultPitchWheel = SynthConstants.MaxPitchWheel / 2;
2607
+ SynthConstants.MicroBufferCount = 32;
2608
+ SynthConstants.MicroBufferSize = 64;
2609
+
2610
+ /**
2611
+ * Represents a group of grace beats that belong together
2612
+ */
2613
+ class GraceGroup {
2614
+ constructor() {
2615
+ /**
2616
+ * All beats within this group.
2617
+ */
2618
+ this.beats = [];
2619
+ /**
2620
+ * Gets a unique ID for this grace group.
2621
+ */
2622
+ this.id = 'empty';
2623
+ /**
2624
+ * true if the grace beat are followed by a normal beat within the same
2625
+ * bar.
2626
+ */
2627
+ this.isComplete = false;
2628
+ }
2629
+ /**
2630
+ * Adds a new beat to this group
2631
+ * @param beat The beat to add
2632
+ */
2633
+ addBeat(beat) {
2634
+ beat.graceIndex = this.beats.length;
2635
+ beat.graceGroup = this;
2636
+ this.beats.push(beat);
2637
+ }
2638
+ finish() {
2639
+ if (this.beats.length > 0) {
2640
+ this.id = `${this.beats[0].absoluteDisplayStart}_${this.beats[0].voice.index}`;
2641
+ }
2642
+ }
2643
+ }
2644
+
2645
+ /**
2646
+ * Lists all graphical sub elements within a {@link Voice} which can be styled via {@link Voice.style}
2647
+ */
2648
+ var VoiceSubElement;
2649
+ (function (VoiceSubElement) {
2650
+ /**
2651
+ * All general glyphs (like notes heads and rests).
2652
+ */
2653
+ VoiceSubElement[VoiceSubElement["Glyphs"] = 0] = "Glyphs";
2654
+ })(VoiceSubElement || (VoiceSubElement = {}));
2655
+ /**
2656
+ * Defines the custom styles for voices.
2657
+ * @json
2658
+ * @json_strict
2659
+ */
2660
+ class VoiceStyle extends ElementStyle {
2661
+ }
2662
+ /**
2663
+ * A voice represents a group of beats
2664
+ * that can be played during a bar.
2665
+ * @json
2666
+ * @json_strict
2667
+ */
2668
+ let Voice$1 = class Voice {
2669
+ constructor() {
2670
+ this._isEmpty = true;
2671
+ this._isRestOnly = true;
2672
+ /**
2673
+ * Gets or sets the unique id of this bar.
2674
+ */
2675
+ this.id = Voice._globalVoiceId++;
2676
+ /**
2677
+ * Gets or sets the zero-based index of this voice within the bar.
2678
+ * @json_ignore
2679
+ */
2680
+ this.index = 0;
2681
+ /**
2682
+ * Gets or sets the list of beats contained in this voice.
2683
+ * @json_add addBeat
2684
+ */
2685
+ this.beats = [];
2686
+ }
2687
+ /**
2688
+ * @internal
2689
+ */
2690
+ static resetIds() {
2691
+ Voice._globalVoiceId = 0;
2692
+ }
2693
+ /**
2694
+ * Gets or sets a value indicating whether this voice is empty.
2695
+ */
2696
+ get isEmpty() {
2697
+ return this._isEmpty;
2698
+ }
2699
+ /**
2700
+ * @internal
2701
+ */
2702
+ forceNonEmpty() {
2703
+ this._isEmpty = false;
2704
+ }
2705
+ /**
2706
+ * Gets or sets a value indicating whether this voice is empty.
2707
+ */
2708
+ get isRestOnly() {
2709
+ return this._isRestOnly;
2710
+ }
2711
+ insertBeat(after, newBeat) {
2712
+ newBeat.nextBeat = after.nextBeat;
2713
+ if (newBeat.nextBeat) {
2714
+ newBeat.nextBeat.previousBeat = newBeat;
2715
+ }
2716
+ newBeat.previousBeat = after;
2717
+ newBeat.voice = this;
2718
+ after.nextBeat = newBeat;
2719
+ this.beats.splice(after.index + 1, 0, newBeat);
2720
+ }
2721
+ addBeat(beat) {
2722
+ beat.voice = this;
2723
+ beat.index = this.beats.length;
2724
+ this.beats.push(beat);
2725
+ if (!beat.isEmpty) {
2726
+ this._isEmpty = false;
2727
+ }
2728
+ if (!beat.isRest) {
2729
+ this._isRestOnly = false;
2730
+ }
2731
+ }
2732
+ chain(beat, sharedDataBag = null) {
2733
+ if (!this.bar) {
2734
+ return;
2735
+ }
2736
+ if (beat.index < this.beats.length - 1) {
2737
+ beat.nextBeat = this.beats[beat.index + 1];
2738
+ beat.nextBeat.previousBeat = beat;
2739
+ }
2740
+ else if (beat.isLastOfVoice && beat.voice.bar.nextBar) {
2741
+ const nextVoice = this.bar.nextBar.voices[this.index];
2742
+ if (nextVoice.beats.length > 0) {
2743
+ beat.nextBeat = nextVoice.beats[0];
2744
+ beat.nextBeat.previousBeat = beat;
2745
+ }
2746
+ else {
2747
+ beat.nextBeat.previousBeat = beat;
2748
+ }
2749
+ }
2750
+ beat.chain(sharedDataBag);
2751
+ }
2752
+ addGraceBeat(beat) {
2753
+ if (this.beats.length === 0) {
2754
+ this.addBeat(beat);
2755
+ return;
2756
+ }
2757
+ // remove last beat
2758
+ const lastBeat = this.beats[this.beats.length - 1];
2759
+ this.beats.splice(this.beats.length - 1, 1);
2760
+ // insert grace beat
2761
+ this.addBeat(beat);
2762
+ // reinsert last beat
2763
+ this.addBeat(lastBeat);
2764
+ this._isEmpty = false;
2765
+ this._isRestOnly = false;
2766
+ }
2767
+ getBeatAtPlaybackStart(playbackStart) {
2768
+ if (this._beatLookup.has(playbackStart)) {
2769
+ return this._beatLookup.get(playbackStart);
2770
+ }
2771
+ return null;
2772
+ }
2773
+ finish(settings, sharedDataBag = null) {
2774
+ this._isEmpty = true;
2775
+ this._isRestOnly = true;
2776
+ this._beatLookup = new Map();
2777
+ let currentGraceGroup = null;
2778
+ for (let index = 0; index < this.beats.length; index++) {
2779
+ const beat = this.beats[index];
2780
+ beat.index = index;
2781
+ this.chain(beat, sharedDataBag);
2782
+ if (beat.graceType === GraceType.None) {
2783
+ beat.graceGroup = currentGraceGroup;
2784
+ if (currentGraceGroup) {
2785
+ currentGraceGroup.isComplete = true;
2786
+ }
2787
+ currentGraceGroup = null;
2788
+ }
2789
+ else {
2790
+ if (!currentGraceGroup) {
2791
+ currentGraceGroup = new GraceGroup();
2792
+ }
2793
+ currentGraceGroup.addBeat(beat);
2794
+ }
2795
+ if (!beat.isEmpty) {
2796
+ this._isEmpty = false;
2797
+ }
2798
+ if (!beat.isRest) {
2799
+ this._isRestOnly = false;
2800
+ }
2801
+ }
2802
+ let currentDisplayTick = 0;
2803
+ let currentPlaybackTick = 0;
2804
+ for (let i = 0; i < this.beats.length; i++) {
2805
+ const beat = this.beats[i];
2806
+ beat.index = i;
2807
+ beat.finish(settings, sharedDataBag);
2808
+ // if this beat is a non-grace but has grace notes
2809
+ // we need to first steal the duration from the right beat
2810
+ // and place the grace beats correctly
2811
+ if (beat.graceType === GraceType.None) {
2812
+ if (beat.graceGroup) {
2813
+ const firstGraceBeat = beat.graceGroup.beats[0];
2814
+ const lastGraceBeat = beat.graceGroup.beats[beat.graceGroup.beats.length - 1];
2815
+ if (firstGraceBeat.graceType !== GraceType.BendGrace) {
2816
+ // find out the stolen duration first
2817
+ const stolenDuration = lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration - firstGraceBeat.playbackStart;
2818
+ switch (firstGraceBeat.graceType) {
2819
+ case GraceType.BeforeBeat:
2820
+ // steal duration from previous beat and then place grace beats newly
2821
+ if (firstGraceBeat.previousBeat) {
2822
+ firstGraceBeat.previousBeat.playbackDuration -= stolenDuration;
2823
+ // place beats starting after new beat end
2824
+ if (firstGraceBeat.previousBeat.voice === this) {
2825
+ currentPlaybackTick =
2826
+ firstGraceBeat.previousBeat.playbackStart +
2827
+ firstGraceBeat.previousBeat.playbackDuration;
2828
+ }
2829
+ else {
2830
+ // stealing into the previous bar
2831
+ currentPlaybackTick = -stolenDuration;
2832
+ }
2833
+ }
2834
+ else {
2835
+ // before-beat on start is somehow not possible as it causes negative ticks
2836
+ currentPlaybackTick = -stolenDuration;
2837
+ }
2838
+ for (const graceBeat of beat.graceGroup.beats) {
2839
+ this._beatLookup.delete(graceBeat.playbackStart);
2840
+ graceBeat.playbackStart = currentPlaybackTick;
2841
+ this._beatLookup.set(graceBeat.playbackStart, beat);
2842
+ currentPlaybackTick += graceBeat.playbackDuration;
2843
+ }
2844
+ break;
2845
+ case GraceType.OnBeat:
2846
+ // steal duration from current beat
2847
+ beat.playbackDuration -= stolenDuration;
2848
+ if (lastGraceBeat.voice === this) {
2849
+ // with changed durations, update current position to be after the last grace beat
2850
+ currentPlaybackTick = lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration;
2851
+ }
2852
+ else {
2853
+ // if last grace beat is on the previous bar, we shift the time back to have the note played earlier
2854
+ currentPlaybackTick = -stolenDuration;
2855
+ }
2856
+ break;
2857
+ }
2858
+ }
2859
+ }
2860
+ beat.displayStart = currentDisplayTick;
2861
+ beat.playbackStart = currentPlaybackTick;
2862
+ if (beat.fermata) {
2863
+ this.bar.masterBar.addFermata(beat.playbackStart, beat.fermata);
2864
+ }
2865
+ else {
2866
+ beat.fermata = this.bar.masterBar.getFermata(beat);
2867
+ }
2868
+ this._beatLookup.set(beat.playbackStart, beat);
2869
+ }
2870
+ else {
2871
+ beat.displayStart = currentDisplayTick;
2872
+ beat.playbackStart = currentPlaybackTick;
2873
+ }
2874
+ beat.finishTuplet();
2875
+ if (beat.graceGroup) {
2876
+ beat.graceGroup.finish();
2877
+ }
2878
+ currentDisplayTick += beat.displayDuration;
2879
+ currentPlaybackTick += beat.playbackDuration;
2880
+ }
2881
+ }
2882
+ calculateDuration() {
2883
+ if (this.isEmpty || this.beats.length === 0) {
2884
+ return 0;
2885
+ }
2886
+ const lastBeat = this.beats[this.beats.length - 1];
2887
+ const firstBeat = this.beats[0];
2888
+ return lastBeat.playbackStart + lastBeat.playbackDuration - firstBeat.playbackStart;
2889
+ }
2890
+ };
2891
+ Voice$1._globalVoiceId = 0;
2892
+
2573
2893
  class TuningParseResult {
2574
2894
  constructor() {
2575
2895
  this.note = null;
@@ -2999,6 +3319,85 @@ class ModelUtils {
2999
3319
  }
3000
3320
  return headerFooterStyle;
3001
3321
  }
3322
+ /**
3323
+ * Performs some general consolidations of inconsistencies on the given score like
3324
+ * missing bars, beats, duplicated midi channels etc
3325
+ */
3326
+ static consolidate(score) {
3327
+ // empty score?
3328
+ if (score.masterBars.length === 0) {
3329
+ const master = new MasterBar();
3330
+ score.addMasterBar(master);
3331
+ const tempoAutomation = new Automation();
3332
+ tempoAutomation.isLinear = false;
3333
+ tempoAutomation.type = AutomationType.Tempo;
3334
+ tempoAutomation.value = score.tempo;
3335
+ master.tempoAutomations.push(tempoAutomation);
3336
+ const bar = new Bar();
3337
+ score.tracks[0].staves[0].addBar(bar);
3338
+ const v = new Voice$1();
3339
+ bar.addVoice(v);
3340
+ const emptyBeat = new Beat();
3341
+ emptyBeat.isEmpty = true;
3342
+ v.addBeat(emptyBeat);
3343
+ return;
3344
+ }
3345
+ const usedChannels = new Set([SynthConstants.PercussionChannel]);
3346
+ for (const track of score.tracks) {
3347
+ // ensure percussion channel
3348
+ if (track.staves.length === 1 && track.staves[0].isPercussion) {
3349
+ track.playbackInfo.primaryChannel = SynthConstants.PercussionChannel;
3350
+ track.playbackInfo.secondaryChannel = SynthConstants.PercussionChannel;
3351
+ }
3352
+ else {
3353
+ // unique midi channels and generate secondary channels
3354
+ if (track.playbackInfo.primaryChannel !== SynthConstants.PercussionChannel) {
3355
+ while (usedChannels.has(track.playbackInfo.primaryChannel)) {
3356
+ track.playbackInfo.primaryChannel++;
3357
+ }
3358
+ }
3359
+ usedChannels.add(track.playbackInfo.primaryChannel);
3360
+ if (track.playbackInfo.secondaryChannel !== SynthConstants.PercussionChannel) {
3361
+ while (usedChannels.has(track.playbackInfo.secondaryChannel)) {
3362
+ track.playbackInfo.secondaryChannel++;
3363
+ }
3364
+ }
3365
+ usedChannels.add(track.playbackInfo.secondaryChannel);
3366
+ }
3367
+ for (const staff of track.staves) {
3368
+ // fill empty beats
3369
+ for (const b of staff.bars) {
3370
+ for (const v of b.voices) {
3371
+ if (v.isEmpty && v.beats.length === 0) {
3372
+ const emptyBeat = new Beat();
3373
+ emptyBeat.isEmpty = true;
3374
+ v.addBeat(emptyBeat);
3375
+ }
3376
+ }
3377
+ }
3378
+ // fill missing bars
3379
+ const voiceCount = staff.bars.length === 0 ? 1 : staff.bars[0].voices.length;
3380
+ while (staff.bars.length < score.masterBars.length) {
3381
+ const bar = new Bar();
3382
+ staff.addBar(bar);
3383
+ const previousBar = bar.previousBar;
3384
+ if (previousBar) {
3385
+ bar.clef = previousBar.clef;
3386
+ bar.clefOttava = previousBar.clefOttava;
3387
+ bar.keySignature = bar.previousBar.keySignature;
3388
+ bar.keySignatureType = bar.previousBar.keySignatureType;
3389
+ }
3390
+ for (let i = 0; i < voiceCount; i++) {
3391
+ const v = new Voice$1();
3392
+ bar.addVoice(v);
3393
+ const emptyBeat = new Beat();
3394
+ emptyBeat.isEmpty = true;
3395
+ v.addBeat(emptyBeat);
3396
+ }
3397
+ }
3398
+ }
3399
+ }
3400
+ }
3002
3401
  }
3003
3402
  ModelUtils.TuningLetters = new Set([
3004
3403
  0x43 /* C */, 0x44 /* D */, 0x45 /* E */, 0x46 /* F */, 0x47 /* G */, 0x41 /* A */, 0x42 /* B */, 0x63 /* c */,
@@ -5356,41 +5755,6 @@ class BeatCloner {
5356
5755
  }
5357
5756
  }
5358
5757
 
5359
- /**
5360
- * Represents a group of grace beats that belong together
5361
- */
5362
- class GraceGroup {
5363
- constructor() {
5364
- /**
5365
- * All beats within this group.
5366
- */
5367
- this.beats = [];
5368
- /**
5369
- * Gets a unique ID for this grace group.
5370
- */
5371
- this.id = 'empty';
5372
- /**
5373
- * true if the grace beat are followed by a normal beat within the same
5374
- * bar.
5375
- */
5376
- this.isComplete = false;
5377
- }
5378
- /**
5379
- * Adds a new beat to this group
5380
- * @param beat The beat to add
5381
- */
5382
- addBeat(beat) {
5383
- beat.graceIndex = this.beats.length;
5384
- beat.graceGroup = this;
5385
- this.beats.push(beat);
5386
- }
5387
- finish() {
5388
- if (this.beats.length > 0) {
5389
- this.id = `${this.beats[0].absoluteDisplayStart}_${this.beats[0].voice.index}`;
5390
- }
5391
- }
5392
- }
5393
-
5394
5758
  /**
5395
5759
  * Lists all golpe types.
5396
5760
  */
@@ -6438,254 +6802,6 @@ class Beat {
6438
6802
  }
6439
6803
  Beat._globalBeatId = 0;
6440
6804
 
6441
- /**
6442
- * Lists all graphical sub elements within a {@link Voice} which can be styled via {@link Voice.style}
6443
- */
6444
- var VoiceSubElement;
6445
- (function (VoiceSubElement) {
6446
- /**
6447
- * All general glyphs (like notes heads and rests).
6448
- */
6449
- VoiceSubElement[VoiceSubElement["Glyphs"] = 0] = "Glyphs";
6450
- })(VoiceSubElement || (VoiceSubElement = {}));
6451
- /**
6452
- * Defines the custom styles for voices.
6453
- * @json
6454
- * @json_strict
6455
- */
6456
- class VoiceStyle extends ElementStyle {
6457
- }
6458
- /**
6459
- * A voice represents a group of beats
6460
- * that can be played during a bar.
6461
- * @json
6462
- * @json_strict
6463
- */
6464
- let Voice$1 = class Voice {
6465
- constructor() {
6466
- this._isEmpty = true;
6467
- this._isRestOnly = true;
6468
- /**
6469
- * Gets or sets the unique id of this bar.
6470
- */
6471
- this.id = Voice._globalVoiceId++;
6472
- /**
6473
- * Gets or sets the zero-based index of this voice within the bar.
6474
- * @json_ignore
6475
- */
6476
- this.index = 0;
6477
- /**
6478
- * Gets or sets the list of beats contained in this voice.
6479
- * @json_add addBeat
6480
- */
6481
- this.beats = [];
6482
- }
6483
- /**
6484
- * @internal
6485
- */
6486
- static resetIds() {
6487
- Voice._globalVoiceId = 0;
6488
- }
6489
- /**
6490
- * Gets or sets a value indicating whether this voice is empty.
6491
- */
6492
- get isEmpty() {
6493
- return this._isEmpty;
6494
- }
6495
- /**
6496
- * @internal
6497
- */
6498
- forceNonEmpty() {
6499
- this._isEmpty = false;
6500
- }
6501
- /**
6502
- * Gets or sets a value indicating whether this voice is empty.
6503
- */
6504
- get isRestOnly() {
6505
- return this._isRestOnly;
6506
- }
6507
- insertBeat(after, newBeat) {
6508
- newBeat.nextBeat = after.nextBeat;
6509
- if (newBeat.nextBeat) {
6510
- newBeat.nextBeat.previousBeat = newBeat;
6511
- }
6512
- newBeat.previousBeat = after;
6513
- newBeat.voice = this;
6514
- after.nextBeat = newBeat;
6515
- this.beats.splice(after.index + 1, 0, newBeat);
6516
- }
6517
- addBeat(beat) {
6518
- beat.voice = this;
6519
- beat.index = this.beats.length;
6520
- this.beats.push(beat);
6521
- if (!beat.isEmpty) {
6522
- this._isEmpty = false;
6523
- }
6524
- if (!beat.isRest) {
6525
- this._isRestOnly = false;
6526
- }
6527
- }
6528
- chain(beat, sharedDataBag = null) {
6529
- if (!this.bar) {
6530
- return;
6531
- }
6532
- if (beat.index < this.beats.length - 1) {
6533
- beat.nextBeat = this.beats[beat.index + 1];
6534
- beat.nextBeat.previousBeat = beat;
6535
- }
6536
- else if (beat.isLastOfVoice && beat.voice.bar.nextBar) {
6537
- const nextVoice = this.bar.nextBar.voices[this.index];
6538
- if (nextVoice.beats.length > 0) {
6539
- beat.nextBeat = nextVoice.beats[0];
6540
- beat.nextBeat.previousBeat = beat;
6541
- }
6542
- else {
6543
- beat.nextBeat.previousBeat = beat;
6544
- }
6545
- }
6546
- beat.chain(sharedDataBag);
6547
- }
6548
- addGraceBeat(beat) {
6549
- if (this.beats.length === 0) {
6550
- this.addBeat(beat);
6551
- return;
6552
- }
6553
- // remove last beat
6554
- const lastBeat = this.beats[this.beats.length - 1];
6555
- this.beats.splice(this.beats.length - 1, 1);
6556
- // insert grace beat
6557
- this.addBeat(beat);
6558
- // reinsert last beat
6559
- this.addBeat(lastBeat);
6560
- this._isEmpty = false;
6561
- this._isRestOnly = false;
6562
- }
6563
- getBeatAtPlaybackStart(playbackStart) {
6564
- if (this._beatLookup.has(playbackStart)) {
6565
- return this._beatLookup.get(playbackStart);
6566
- }
6567
- return null;
6568
- }
6569
- finish(settings, sharedDataBag = null) {
6570
- this._isEmpty = true;
6571
- this._isRestOnly = true;
6572
- this._beatLookup = new Map();
6573
- let currentGraceGroup = null;
6574
- for (let index = 0; index < this.beats.length; index++) {
6575
- const beat = this.beats[index];
6576
- beat.index = index;
6577
- this.chain(beat, sharedDataBag);
6578
- if (beat.graceType === GraceType.None) {
6579
- beat.graceGroup = currentGraceGroup;
6580
- if (currentGraceGroup) {
6581
- currentGraceGroup.isComplete = true;
6582
- }
6583
- currentGraceGroup = null;
6584
- }
6585
- else {
6586
- if (!currentGraceGroup) {
6587
- currentGraceGroup = new GraceGroup();
6588
- }
6589
- currentGraceGroup.addBeat(beat);
6590
- }
6591
- if (!beat.isEmpty) {
6592
- this._isEmpty = false;
6593
- }
6594
- if (!beat.isRest) {
6595
- this._isRestOnly = false;
6596
- }
6597
- }
6598
- let currentDisplayTick = 0;
6599
- let currentPlaybackTick = 0;
6600
- for (let i = 0; i < this.beats.length; i++) {
6601
- const beat = this.beats[i];
6602
- beat.index = i;
6603
- beat.finish(settings, sharedDataBag);
6604
- // if this beat is a non-grace but has grace notes
6605
- // we need to first steal the duration from the right beat
6606
- // and place the grace beats correctly
6607
- if (beat.graceType === GraceType.None) {
6608
- if (beat.graceGroup) {
6609
- const firstGraceBeat = beat.graceGroup.beats[0];
6610
- const lastGraceBeat = beat.graceGroup.beats[beat.graceGroup.beats.length - 1];
6611
- if (firstGraceBeat.graceType !== GraceType.BendGrace) {
6612
- // find out the stolen duration first
6613
- const stolenDuration = lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration - firstGraceBeat.playbackStart;
6614
- switch (firstGraceBeat.graceType) {
6615
- case GraceType.BeforeBeat:
6616
- // steal duration from previous beat and then place grace beats newly
6617
- if (firstGraceBeat.previousBeat) {
6618
- firstGraceBeat.previousBeat.playbackDuration -= stolenDuration;
6619
- // place beats starting after new beat end
6620
- if (firstGraceBeat.previousBeat.voice === this) {
6621
- currentPlaybackTick =
6622
- firstGraceBeat.previousBeat.playbackStart +
6623
- firstGraceBeat.previousBeat.playbackDuration;
6624
- }
6625
- else {
6626
- // stealing into the previous bar
6627
- currentPlaybackTick = -stolenDuration;
6628
- }
6629
- }
6630
- else {
6631
- // before-beat on start is somehow not possible as it causes negative ticks
6632
- currentPlaybackTick = -stolenDuration;
6633
- }
6634
- for (const graceBeat of beat.graceGroup.beats) {
6635
- this._beatLookup.delete(graceBeat.playbackStart);
6636
- graceBeat.playbackStart = currentPlaybackTick;
6637
- this._beatLookup.set(graceBeat.playbackStart, beat);
6638
- currentPlaybackTick += graceBeat.playbackDuration;
6639
- }
6640
- break;
6641
- case GraceType.OnBeat:
6642
- // steal duration from current beat
6643
- beat.playbackDuration -= stolenDuration;
6644
- if (lastGraceBeat.voice === this) {
6645
- // with changed durations, update current position to be after the last grace beat
6646
- currentPlaybackTick = lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration;
6647
- }
6648
- else {
6649
- // if last grace beat is on the previous bar, we shift the time back to have the note played earlier
6650
- currentPlaybackTick = -stolenDuration;
6651
- }
6652
- break;
6653
- }
6654
- }
6655
- }
6656
- beat.displayStart = currentDisplayTick;
6657
- beat.playbackStart = currentPlaybackTick;
6658
- if (beat.fermata) {
6659
- this.bar.masterBar.addFermata(beat.playbackStart, beat.fermata);
6660
- }
6661
- else {
6662
- beat.fermata = this.bar.masterBar.getFermata(beat);
6663
- }
6664
- this._beatLookup.set(beat.playbackStart, beat);
6665
- }
6666
- else {
6667
- beat.displayStart = currentDisplayTick;
6668
- beat.playbackStart = currentPlaybackTick;
6669
- }
6670
- beat.finishTuplet();
6671
- if (beat.graceGroup) {
6672
- beat.graceGroup.finish();
6673
- }
6674
- currentDisplayTick += beat.displayDuration;
6675
- currentPlaybackTick += beat.playbackDuration;
6676
- }
6677
- }
6678
- calculateDuration() {
6679
- if (this.isEmpty || this.beats.length === 0) {
6680
- return 0;
6681
- }
6682
- const lastBeat = this.beats[this.beats.length - 1];
6683
- const firstBeat = this.beats[0];
6684
- return lastBeat.playbackStart + lastBeat.playbackDuration - firstBeat.playbackStart;
6685
- }
6686
- };
6687
- Voice$1._globalVoiceId = 0;
6688
-
6689
6805
  /**
6690
6806
  * Lists all graphical sub elements within a {@link Score} which can be styled via {@link Score.style}
6691
6807
  */
@@ -8168,38 +8284,6 @@ class ByteBuffer {
8168
8284
  }
8169
8285
  }
8170
8286
 
8171
- // The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT,
8172
- // developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont)
8173
- // TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny
8174
- // Licensed under: MPL-2.0
8175
- class SynthConstants {
8176
- }
8177
- SynthConstants.DefaultChannelCount = 16 + 1;
8178
- SynthConstants.MetronomeChannel = SynthConstants.DefaultChannelCount - 1;
8179
- SynthConstants.MetronomeKey = 33;
8180
- SynthConstants.AudioChannels = 2;
8181
- SynthConstants.MinVolume = 0;
8182
- SynthConstants.MinProgram = 0;
8183
- SynthConstants.MaxProgram = 127;
8184
- SynthConstants.MinPlaybackSpeed = 0.125;
8185
- SynthConstants.MaxPlaybackSpeed = 8;
8186
- SynthConstants.PercussionChannel = 9;
8187
- SynthConstants.PercussionBank = 128;
8188
- /**
8189
- * The Midi Pitch bend message is a 15-bit value
8190
- */
8191
- SynthConstants.MaxPitchWheel = 0x4000;
8192
- /**
8193
- * The Midi 2.0 Pitch bend message is a 32-bit value
8194
- */
8195
- SynthConstants.MaxPitchWheel20 = 0x100000000;
8196
- /**
8197
- * The pitch wheel value for no pitch change at all.
8198
- */
8199
- SynthConstants.DefaultPitchWheel = SynthConstants.MaxPitchWheel / 2;
8200
- SynthConstants.MicroBufferCount = 32;
8201
- SynthConstants.MicroBufferSize = 64;
8202
-
8203
8287
  /**
8204
8288
  * Lists all directions which can be applied to a masterbar.
8205
8289
  */
@@ -8404,7 +8488,7 @@ class AlphaTexImporter extends ScoreImporter {
8404
8488
  throw new UnsupportedFormatError('No alphaTex data found');
8405
8489
  }
8406
8490
  }
8407
- this.consolidate();
8491
+ ModelUtils.consolidate(this._score);
8408
8492
  this._score.finish(this.settings);
8409
8493
  this._score.rebuildRepeatGroups();
8410
8494
  for (const [track, lyrics] of this._lyrics) {
@@ -8423,57 +8507,6 @@ class AlphaTexImporter extends ScoreImporter {
8423
8507
  throw e;
8424
8508
  }
8425
8509
  }
8426
- /**
8427
- * Ensures all staffs of all tracks have the correct number of bars
8428
- * (the number of bars per staff and track could be inconsistent)
8429
- */
8430
- consolidate() {
8431
- // empty score?
8432
- if (this._score.masterBars.length === 0) {
8433
- const master = new MasterBar();
8434
- this._score.addMasterBar(master);
8435
- const tempoAutomation = new Automation();
8436
- tempoAutomation.isLinear = false;
8437
- tempoAutomation.type = AutomationType.Tempo;
8438
- tempoAutomation.value = this._score.tempo;
8439
- master.tempoAutomations.push(tempoAutomation);
8440
- const bar = new Bar();
8441
- this._score.tracks[0].staves[0].addBar(bar);
8442
- const v = new Voice$1();
8443
- bar.addVoice(v);
8444
- const emptyBeat = new Beat();
8445
- emptyBeat.isEmpty = true;
8446
- v.addBeat(emptyBeat);
8447
- return;
8448
- }
8449
- for (const track of this._score.tracks) {
8450
- for (const staff of track.staves) {
8451
- // fill empty beats
8452
- for (const b of staff.bars) {
8453
- for (const v of b.voices) {
8454
- if (v.isEmpty && v.beats.length === 0) {
8455
- const emptyBeat = new Beat();
8456
- emptyBeat.isEmpty = true;
8457
- v.addBeat(emptyBeat);
8458
- }
8459
- }
8460
- }
8461
- // fill missing bars
8462
- const voiceCount = staff.bars.length === 0 ? 1 : staff.bars[0].voices.length;
8463
- while (staff.bars.length < this._score.masterBars.length) {
8464
- const bar = new Bar();
8465
- staff.addBar(bar);
8466
- for (let i = 0; i < voiceCount; i++) {
8467
- const v = new Voice$1();
8468
- bar.addVoice(v);
8469
- const emptyBeat = new Beat();
8470
- emptyBeat.isEmpty = true;
8471
- v.addBeat(emptyBeat);
8472
- }
8473
- }
8474
- }
8475
- }
8476
- }
8477
8510
  error(nonterm, expected, wrongSymbol = true) {
8478
8511
  let receivedSymbol;
8479
8512
  let showSyData = false;
@@ -11597,6 +11630,7 @@ class Gp3To5Importer extends ScoreImporter {
11597
11630
  automation.text = this._score.tempoLabel;
11598
11631
  this._score.masterBars[0].tempoAutomations.push(automation);
11599
11632
  }
11633
+ ModelUtils.consolidate(this._score);
11600
11634
  this._score.finish(this.settings);
11601
11635
  if (this._lyrics && this._lyricsTrack >= 0) {
11602
11636
  this._score.tracks[this._lyricsTrack].applyLyrics(this._lyrics);
@@ -11715,10 +11749,11 @@ class Gp3To5Importer extends ScoreImporter {
11715
11749
  }
11716
11750
  readPlaybackInfos() {
11717
11751
  this._playbackInfos = [];
11752
+ let channel = 0;
11718
11753
  for (let i = 0; i < 64; i++) {
11719
11754
  const info = new PlaybackInformation();
11720
- info.primaryChannel = i;
11721
- info.secondaryChannel = i;
11755
+ info.primaryChannel = channel++;
11756
+ info.secondaryChannel = channel++;
11722
11757
  info.program = IOHelper.readInt32LE(this.data);
11723
11758
  info.volume = this.data.readByte();
11724
11759
  info.balance = this.data.readByte();
@@ -14150,6 +14185,7 @@ class GpifParser {
14150
14185
  }
14151
14186
  this.parseDom(dom);
14152
14187
  this.buildModel();
14188
+ ModelUtils.consolidate(this.score);
14153
14189
  this.score.finish(settings);
14154
14190
  if (!this._skipApplyLyrics && this._lyricsByTrack.size > 0) {
14155
14191
  for (const [t, lyrics] of this._lyricsByTrack) {
@@ -14213,26 +14249,26 @@ class GpifParser {
14213
14249
  for (const c of element.childElements()) {
14214
14250
  switch (c.localName) {
14215
14251
  case 'Title':
14216
- this.score.title = c.firstChild.innerText;
14252
+ this.score.title = c.innerText;
14217
14253
  break;
14218
14254
  case 'SubTitle':
14219
- this.score.subTitle = c.firstChild.innerText;
14255
+ this.score.subTitle = c.innerText;
14220
14256
  break;
14221
14257
  case 'Artist':
14222
- this.score.artist = c.firstChild.innerText;
14258
+ this.score.artist = c.innerText;
14223
14259
  break;
14224
14260
  case 'Album':
14225
- this.score.album = c.firstChild.innerText;
14261
+ this.score.album = c.innerText;
14226
14262
  break;
14227
14263
  case 'Words':
14228
- this.score.words = c.firstChild.innerText;
14264
+ this.score.words = c.innerText;
14229
14265
  break;
14230
14266
  case 'Music':
14231
- this.score.music = c.firstChild.innerText;
14267
+ this.score.music = c.innerText;
14232
14268
  break;
14233
14269
  case 'WordsAndMusic':
14234
- if (c.firstChild && c.firstChild.innerText !== '') {
14235
- const wordsAndMusic = c.firstChild.innerText;
14270
+ const wordsAndMusic = c.innerText;
14271
+ if (wordsAndMusic !== '') {
14236
14272
  if (wordsAndMusic && !this.score.words) {
14237
14273
  this.score.words = wordsAndMusic;
14238
14274
  }
@@ -14242,26 +14278,52 @@ class GpifParser {
14242
14278
  }
14243
14279
  break;
14244
14280
  case 'Copyright':
14245
- this.score.copyright = c.firstChild.innerText;
14281
+ this.score.copyright = c.innerText;
14246
14282
  break;
14247
14283
  case 'Tabber':
14248
- this.score.tab = c.firstChild.innerText;
14284
+ this.score.tab = c.innerText;
14249
14285
  break;
14250
14286
  case 'Instructions':
14251
- this.score.instructions = c.firstChild.innerText;
14287
+ this.score.instructions = c.innerText;
14252
14288
  break;
14253
14289
  case 'Notices':
14254
- this.score.notices = c.firstChild.innerText;
14290
+ this.score.notices = c.innerText;
14255
14291
  break;
14256
14292
  case 'ScoreSystemsDefaultLayout':
14257
- this.score.defaultSystemsLayout = Number.parseInt(c.innerText);
14293
+ this.score.defaultSystemsLayout = GpifParser.parseIntSafe(c.innerText, 4);
14258
14294
  break;
14259
14295
  case 'ScoreSystemsLayout':
14260
- this.score.systemsLayout = c.innerText.split(' ').map(i => Number.parseInt(i));
14296
+ this.score.systemsLayout = GpifParser.splitSafe(c.innerText).map(i => GpifParser.parseIntSafe(i, 4));
14261
14297
  break;
14262
14298
  }
14263
14299
  }
14264
14300
  }
14301
+ static parseIntSafe(text, fallback) {
14302
+ if (!text) {
14303
+ return fallback;
14304
+ }
14305
+ const i = Number.parseInt(text);
14306
+ if (!Number.isNaN(i)) {
14307
+ return i;
14308
+ }
14309
+ return fallback;
14310
+ }
14311
+ static parseFloatSafe(text, fallback) {
14312
+ if (!text) {
14313
+ return fallback;
14314
+ }
14315
+ const i = Number.parseFloat(text);
14316
+ if (!Number.isNaN(i)) {
14317
+ return i;
14318
+ }
14319
+ return fallback;
14320
+ }
14321
+ static splitSafe(text, separator = ' ') {
14322
+ if (!text) {
14323
+ return [];
14324
+ }
14325
+ return text.split(separator).map(t => t.trim()).filter(t => t.length > 0);
14326
+ }
14265
14327
  //
14266
14328
  // <MasterTrack>...</MasterTrack>
14267
14329
  //
@@ -14272,7 +14334,7 @@ class GpifParser {
14272
14334
  this.parseAutomations(c, this._masterTrackAutomations, null, null);
14273
14335
  break;
14274
14336
  case 'Tracks':
14275
- this._tracksMapping = c.innerText.split(' ');
14337
+ this._tracksMapping = GpifParser.splitSafe(c.innerText);
14276
14338
  break;
14277
14339
  case 'Anacrusis':
14278
14340
  this._hasAnacrusis = true;
@@ -14307,26 +14369,26 @@ class GpifParser {
14307
14369
  isLinear = c.innerText.toLowerCase() === 'true';
14308
14370
  break;
14309
14371
  case 'Bar':
14310
- barIndex = Number.parseInt(c.innerText);
14372
+ barIndex = GpifParser.parseIntSafe(c.innerText, 0);
14311
14373
  break;
14312
14374
  case 'Position':
14313
- ratioPosition = Number.parseFloat(c.innerText);
14375
+ ratioPosition = GpifParser.parseFloatSafe(c.innerText, 0);
14314
14376
  break;
14315
14377
  case 'Value':
14316
14378
  if (c.firstElement && c.firstElement.nodeType === XmlNodeType.CDATA) {
14317
14379
  textValue = c.innerText;
14318
14380
  }
14319
14381
  else {
14320
- const parts = c.innerText.split(' ');
14382
+ const parts = GpifParser.splitSafe(c.innerText);
14321
14383
  // Issue 391: Some GPX files might have
14322
14384
  // single floating point value.
14323
14385
  if (parts.length === 1) {
14324
- numberValue = Number.parseFloat(parts[0]);
14386
+ numberValue = GpifParser.parseFloatSafe(parts[0], 0);
14325
14387
  reference = 1;
14326
14388
  }
14327
14389
  else {
14328
- numberValue = Number.parseFloat(parts[0]);
14329
- reference = Number.parseInt(parts[1]);
14390
+ numberValue = GpifParser.parseFloatSafe(parts[0], 0);
14391
+ reference = GpifParser.parseIntSafe(parts[1], 0);
14330
14392
  }
14331
14393
  }
14332
14394
  break;
@@ -14410,11 +14472,11 @@ class GpifParser {
14410
14472
  track.name = c.innerText;
14411
14473
  break;
14412
14474
  case 'Color':
14413
- const parts = c.innerText.split(' ');
14475
+ const parts = GpifParser.splitSafe(c.innerText);
14414
14476
  if (parts.length >= 3) {
14415
- const r = Number.parseInt(parts[0]);
14416
- const g = Number.parseInt(parts[1]);
14417
- const b = Number.parseInt(parts[2]);
14477
+ const r = GpifParser.parseIntSafe(parts[0], 0);
14478
+ const g = GpifParser.parseIntSafe(parts[1], 0);
14479
+ const b = GpifParser.parseIntSafe(parts[2], 0);
14418
14480
  track.color = new Color(r, g, b, 0xff);
14419
14481
  }
14420
14482
  break;
@@ -14435,10 +14497,10 @@ class GpifParser {
14435
14497
  track.shortName = c.innerText;
14436
14498
  break;
14437
14499
  case 'SystemsDefautLayout': // not a typo by alphaTab, this is a typo in the GPIF files.
14438
- track.defaultSystemsLayout = Number.parseInt(c.innerText);
14500
+ track.defaultSystemsLayout = GpifParser.parseIntSafe(c.innerText, 4);
14439
14501
  break;
14440
14502
  case 'SystemsLayout':
14441
- track.systemsLayout = c.innerText.split(' ').map(i => Number.parseInt(i));
14503
+ track.systemsLayout = GpifParser.splitSafe(c.innerText).map(i => GpifParser.parseIntSafe(i, 4));
14442
14504
  break;
14443
14505
  case 'Lyrics':
14444
14506
  this.parseLyrics(trackId, c);
@@ -14489,7 +14551,7 @@ class GpifParser {
14489
14551
  for (const c of node.childElements()) {
14490
14552
  switch (c.localName) {
14491
14553
  case 'LineCount':
14492
- const lineCount = Number.parseInt(c.innerText);
14554
+ const lineCount = GpifParser.parseIntSafe(c.innerText, 5);
14493
14555
  for (const staff of track.staves) {
14494
14556
  staff.standardNotationLineCount = lineCount;
14495
14557
  }
@@ -14504,13 +14566,6 @@ class GpifParser {
14504
14566
  for (const c of node.childElements()) {
14505
14567
  switch (c.localName) {
14506
14568
  case 'Type':
14507
- switch (c.innerText) {
14508
- case 'drumKit':
14509
- for (const staff of track.staves) {
14510
- staff.isPercussion = true;
14511
- }
14512
- break;
14513
- }
14514
14569
  if (c.innerText === 'drumKit') {
14515
14570
  for (const staff of track.staves) {
14516
14571
  staff.isPercussion = true;
@@ -14521,7 +14576,7 @@ class GpifParser {
14521
14576
  this.parseElements(track, c);
14522
14577
  break;
14523
14578
  case 'LineCount':
14524
- const lineCount = Number.parseInt(c.innerText);
14579
+ const lineCount = GpifParser.parseIntSafe(c.innerText, 5);
14525
14580
  for (const staff of track.staves) {
14526
14581
  staff.standardNotationLineCount = lineCount;
14527
14582
  }
@@ -14539,8 +14594,7 @@ class GpifParser {
14539
14594
  }
14540
14595
  }
14541
14596
  parseElement(track, node) {
14542
- const typeElement = node.findChildElement('Type');
14543
- const type = typeElement ? typeElement.innerText : '';
14597
+ const type = node.findChildElement('Type')?.innerText ?? '';
14544
14598
  for (const c of node.childElements()) {
14545
14599
  switch (c.localName) {
14546
14600
  case 'Name':
@@ -14571,9 +14625,7 @@ class GpifParser {
14571
14625
  name = c.innerText;
14572
14626
  break;
14573
14627
  case 'OutputMidiNumber':
14574
- if (txt.length > 0) {
14575
- articulation.outputMidiNumber = Number.parseInt(txt);
14576
- }
14628
+ articulation.outputMidiNumber = GpifParser.parseIntSafe(txt, 0);
14577
14629
  break;
14578
14630
  case 'TechniqueSymbol':
14579
14631
  articulation.techniqueSymbol = this.parseTechniqueSymbol(txt);
@@ -14595,7 +14647,7 @@ class GpifParser {
14595
14647
  }
14596
14648
  break;
14597
14649
  case 'Noteheads':
14598
- const noteHeadsTxt = txt.split(' ');
14650
+ const noteHeadsTxt = GpifParser.splitSafe(txt);
14599
14651
  if (noteHeadsTxt.length >= 1) {
14600
14652
  articulation.noteHeadDefault = this.parseNoteHead(noteHeadsTxt[0]);
14601
14653
  }
@@ -14613,9 +14665,7 @@ class GpifParser {
14613
14665
  }
14614
14666
  break;
14615
14667
  case 'StaffLine':
14616
- if (txt.length > 0) {
14617
- articulation.staffLine = Number.parseInt(txt);
14618
- }
14668
+ articulation.staffLine = GpifParser.parseIntSafe(txt, 0);
14619
14669
  break;
14620
14670
  }
14621
14671
  }
@@ -14736,10 +14786,10 @@ class GpifParser {
14736
14786
  for (const c of node.childElements()) {
14737
14787
  switch (c.localName) {
14738
14788
  case 'Pitches':
14739
- const tuningParts = node.findChildElement('Pitches').innerText.split(' ');
14789
+ const tuningParts = GpifParser.splitSafe(node.findChildElement('Pitches')?.innerText);
14740
14790
  const tuning = new Array(tuningParts.length);
14741
14791
  for (let i = 0; i < tuning.length; i++) {
14742
- tuning[tuning.length - 1 - i] = Number.parseInt(tuningParts[i]);
14792
+ tuning[tuning.length - 1 - i] = GpifParser.parseIntSafe(tuningParts[i], 0);
14743
14793
  }
14744
14794
  staff.stringTuning.tunings = tuning;
14745
14795
  break;
@@ -14757,7 +14807,7 @@ class GpifParser {
14757
14807
  this.parseDiagramCollectionForStaff(staff, node);
14758
14808
  break;
14759
14809
  case 'CapoFret':
14760
- const capo = Number.parseInt(node.findChildElement('Fret').innerText);
14810
+ const capo = GpifParser.parseIntSafe(node.findChildElement('Fret')?.innerText, 0);
14761
14811
  staff.capo = capo;
14762
14812
  break;
14763
14813
  }
@@ -14778,7 +14828,7 @@ class GpifParser {
14778
14828
  for (const c of node.childElements()) {
14779
14829
  switch (c.localName) {
14780
14830
  case 'Offset':
14781
- lyrics.startBar = Number.parseInt(c.innerText);
14831
+ lyrics.startBar = GpifParser.parseIntSafe(c.innerText, 0);
14782
14832
  break;
14783
14833
  case 'Text':
14784
14834
  lyrics.text = c.innerText;
@@ -14789,21 +14839,25 @@ class GpifParser {
14789
14839
  }
14790
14840
  parseDiagramCollectionForTrack(track, node) {
14791
14841
  const items = node.findChildElement('Items');
14792
- for (const c of items.childElements()) {
14793
- switch (c.localName) {
14794
- case 'Item':
14795
- this.parseDiagramItemForTrack(track, c);
14796
- break;
14842
+ if (items) {
14843
+ for (const c of items.childElements()) {
14844
+ switch (c.localName) {
14845
+ case 'Item':
14846
+ this.parseDiagramItemForTrack(track, c);
14847
+ break;
14848
+ }
14797
14849
  }
14798
14850
  }
14799
14851
  }
14800
14852
  parseDiagramCollectionForStaff(staff, node) {
14801
14853
  const items = node.findChildElement('Items');
14802
- for (const c of items.childElements()) {
14803
- switch (c.localName) {
14804
- case 'Item':
14805
- this.parseDiagramItemForStaff(staff, c);
14806
- break;
14854
+ if (items) {
14855
+ for (const c of items.childElements()) {
14856
+ switch (c.localName) {
14857
+ case 'Item':
14858
+ this.parseDiagramItemForStaff(staff, c);
14859
+ break;
14860
+ }
14807
14861
  }
14808
14862
  }
14809
14863
  }
@@ -14829,10 +14883,8 @@ class GpifParser {
14829
14883
  chord.showFingering = false;
14830
14884
  return;
14831
14885
  }
14832
- const stringCount = Number.parseInt(diagram.getAttribute('stringCount'));
14833
- const baseFret = diagram.attributes.has('baseFret')
14834
- ? Number.parseInt(diagram.getAttribute('baseFret'))
14835
- : 0;
14886
+ const stringCount = GpifParser.parseIntSafe(diagram.getAttribute('stringCount'), 6);
14887
+ const baseFret = GpifParser.parseIntSafe(diagram.getAttribute('baseFret'), 0);
14836
14888
  chord.firstFret = baseFret + 1;
14837
14889
  for (let i = 0; i < stringCount; i++) {
14838
14890
  chord.strings.push(-1);
@@ -14840,8 +14892,9 @@ class GpifParser {
14840
14892
  for (const c of diagram.childElements()) {
14841
14893
  switch (c.localName) {
14842
14894
  case 'Fret':
14843
- const guitarString = Number.parseInt(c.getAttribute('string'));
14844
- chord.strings[stringCount - guitarString - 1] = baseFret + Number.parseInt(c.getAttribute('fret'));
14895
+ const guitarString = GpifParser.parseIntSafe(c.getAttribute('string'), 0);
14896
+ chord.strings[stringCount - guitarString - 1] =
14897
+ baseFret + GpifParser.parseIntSafe(c.getAttribute('fret'), 0);
14845
14898
  break;
14846
14899
  case 'Fingering':
14847
14900
  const existingFingers = new Map();
@@ -14849,7 +14902,7 @@ class GpifParser {
14849
14902
  switch (p.localName) {
14850
14903
  case 'Position':
14851
14904
  let finger = Fingers.Unknown;
14852
- const fret = baseFret + Number.parseInt(p.getAttribute('fret'));
14905
+ const fret = baseFret + GpifParser.parseIntSafe(p.getAttribute('fret'), 0);
14853
14906
  switch (p.getAttribute('finger')) {
14854
14907
  case 'Index':
14855
14908
  finger = Fingers.IndexFinger;
@@ -14908,10 +14961,10 @@ class GpifParser {
14908
14961
  const propertyName = node.getAttribute('name');
14909
14962
  switch (propertyName) {
14910
14963
  case 'Tuning':
14911
- const tuningParts = node.findChildElement('Pitches').innerText.split(' ');
14964
+ const tuningParts = GpifParser.splitSafe(node.findChildElement('Pitches')?.innerText);
14912
14965
  const tuning = new Array(tuningParts.length);
14913
14966
  for (let i = 0; i < tuning.length; i++) {
14914
- tuning[tuning.length - 1 - i] = Number.parseInt(tuningParts[i]);
14967
+ tuning[tuning.length - 1 - i] = GpifParser.parseIntSafe(tuningParts[i], 0);
14915
14968
  }
14916
14969
  for (const staff of track.staves) {
14917
14970
  staff.stringTuning.tunings = tuning;
@@ -14924,7 +14977,7 @@ class GpifParser {
14924
14977
  this.parseDiagramCollectionForTrack(track, node);
14925
14978
  break;
14926
14979
  case 'CapoFret':
14927
- const capo = Number.parseInt(node.findChildElement('Fret').innerText);
14980
+ const capo = GpifParser.parseIntSafe(node.findChildElement('Fret')?.innerText, 0);
14928
14981
  for (const staff of track.staves) {
14929
14982
  staff.capo = capo;
14930
14983
  }
@@ -14935,16 +14988,16 @@ class GpifParser {
14935
14988
  for (const c of node.childElements()) {
14936
14989
  switch (c.localName) {
14937
14990
  case 'Program':
14938
- track.playbackInfo.program = Number.parseInt(c.innerText);
14991
+ track.playbackInfo.program = GpifParser.parseIntSafe(c.innerText, 0);
14939
14992
  break;
14940
14993
  case 'Port':
14941
- track.playbackInfo.port = Number.parseInt(c.innerText);
14994
+ track.playbackInfo.port = GpifParser.parseIntSafe(c.innerText, 0);
14942
14995
  break;
14943
14996
  case 'PrimaryChannel':
14944
- track.playbackInfo.primaryChannel = Number.parseInt(c.innerText);
14997
+ track.playbackInfo.primaryChannel = GpifParser.parseIntSafe(c.innerText, 0);
14945
14998
  break;
14946
14999
  case 'SecondaryChannel':
14947
- track.playbackInfo.secondaryChannel = Number.parseInt(c.innerText);
15000
+ track.playbackInfo.secondaryChannel = GpifParser.parseIntSafe(c.innerText, 0);
14948
15001
  break;
14949
15002
  }
14950
15003
  }
@@ -14994,7 +15047,7 @@ class GpifParser {
14994
15047
  for (const c of node.childElements()) {
14995
15048
  switch (c.localName) {
14996
15049
  case 'Program':
14997
- sound.program = Number.parseInt(c.innerText);
15050
+ sound.program = GpifParser.parseIntSafe(c.innerText, 0);
14998
15051
  break;
14999
15052
  }
15000
15053
  }
@@ -15004,7 +15057,7 @@ class GpifParser {
15004
15057
  switch (c.localName) {
15005
15058
  case 'TranspositionPitch':
15006
15059
  for (const staff of track.staves) {
15007
- staff.displayTranspositionPitch = Number.parseInt(c.innerText);
15060
+ staff.displayTranspositionPitch = GpifParser.parseIntSafe(c.innerText, 0);
15008
15061
  }
15009
15062
  break;
15010
15063
  }
@@ -15016,10 +15069,10 @@ class GpifParser {
15016
15069
  for (const c of node.childElements()) {
15017
15070
  switch (c.localName) {
15018
15071
  case 'Chromatic':
15019
- chromatic = Number.parseInt(c.innerText);
15072
+ chromatic = GpifParser.parseIntSafe(c.innerText, 0);
15020
15073
  break;
15021
15074
  case 'Octave':
15022
- octave = Number.parseInt(c.innerText);
15075
+ octave = GpifParser.parseIntSafe(c.innerText, 0);
15023
15076
  break;
15024
15077
  }
15025
15078
  }
@@ -15047,10 +15100,10 @@ class GpifParser {
15047
15100
  }
15048
15101
  parseChannelStripParameters(track, node) {
15049
15102
  if (node.firstChild && node.firstChild.value) {
15050
- const parameters = node.firstChild.value.split(' ');
15103
+ const parameters = GpifParser.splitSafe(node.firstChild.value);
15051
15104
  if (parameters.length >= 12) {
15052
- track.playbackInfo.balance = Math.floor(Number.parseFloat(parameters[11]) * 16);
15053
- track.playbackInfo.volume = Math.floor(Number.parseFloat(parameters[12]) * 16);
15105
+ track.playbackInfo.balance = Math.floor(GpifParser.parseFloatSafe(parameters[11], 0.5) * 16);
15106
+ track.playbackInfo.volume = Math.floor(GpifParser.parseFloatSafe(parameters[12], 0.9) * 16);
15054
15107
  }
15055
15108
  }
15056
15109
  }
@@ -15075,8 +15128,8 @@ class GpifParser {
15075
15128
  switch (c.localName) {
15076
15129
  case 'Time':
15077
15130
  const timeParts = c.innerText.split('/');
15078
- masterBar.timeSignatureNumerator = Number.parseInt(timeParts[0]);
15079
- masterBar.timeSignatureDenominator = Number.parseInt(timeParts[1]);
15131
+ masterBar.timeSignatureNumerator = GpifParser.parseIntSafe(timeParts[0], 4);
15132
+ masterBar.timeSignatureDenominator = GpifParser.parseIntSafe(timeParts[1], 4);
15080
15133
  break;
15081
15134
  case 'FreeTime':
15082
15135
  masterBar.isFreeTime = true;
@@ -15087,27 +15140,27 @@ class GpifParser {
15087
15140
  break;
15088
15141
  case 'Section':
15089
15142
  masterBar.section = new Section();
15090
- masterBar.section.marker = c.findChildElement('Letter').innerText;
15091
- masterBar.section.text = c.findChildElement('Text').innerText;
15143
+ masterBar.section.marker = c.findChildElement('Letter')?.innerText ?? '';
15144
+ masterBar.section.text = c.findChildElement('Text')?.innerText ?? '';
15092
15145
  break;
15093
15146
  case 'Repeat':
15094
15147
  if (c.getAttribute('start').toLowerCase() === 'true') {
15095
15148
  masterBar.isRepeatStart = true;
15096
15149
  }
15097
15150
  if (c.getAttribute('end').toLowerCase() === 'true' && c.getAttribute('count')) {
15098
- masterBar.repeatCount = Number.parseInt(c.getAttribute('count'));
15151
+ masterBar.repeatCount = GpifParser.parseIntSafe(c.getAttribute('count'), 1);
15099
15152
  }
15100
15153
  break;
15101
15154
  case 'AlternateEndings':
15102
- const alternateEndings = c.innerText.split(' ');
15155
+ const alternateEndings = GpifParser.splitSafe(c.innerText);
15103
15156
  let i = 0;
15104
15157
  for (let k = 0; k < alternateEndings.length; k++) {
15105
- i = i | (1 << (-1 + Number.parseInt(alternateEndings[k])));
15158
+ i = i | (1 << (-1 + GpifParser.parseIntSafe(alternateEndings[k], 0)));
15106
15159
  }
15107
15160
  masterBar.alternateEndings = i;
15108
15161
  break;
15109
15162
  case 'Bars':
15110
- this._barsOfMasterBar.push(c.innerText.split(' '));
15163
+ this._barsOfMasterBar.push(GpifParser.splitSafe(c.innerText));
15111
15164
  break;
15112
15165
  case 'TripletFeel':
15113
15166
  switch (c.innerText) {
@@ -15135,7 +15188,7 @@ class GpifParser {
15135
15188
  }
15136
15189
  break;
15137
15190
  case 'Key':
15138
- const keySignature = Number.parseInt(c.findChildElement('AccidentalCount').innerText);
15191
+ const keySignature = GpifParser.parseIntSafe(c.findChildElement('AccidentalCount')?.innerText, 0);
15139
15192
  const mode = c.findChildElement('Mode');
15140
15193
  let keySignatureType = KeySignatureType.Major;
15141
15194
  if (mode) {
@@ -15263,13 +15316,13 @@ class GpifParser {
15263
15316
  }
15264
15317
  break;
15265
15318
  case 'Length':
15266
- fermata.length = Number.parseFloat(c.innerText);
15319
+ fermata.length = GpifParser.parseFloatSafe(c.innerText, 0);
15267
15320
  break;
15268
15321
  case 'Offset':
15269
15322
  const parts = c.innerText.split('/');
15270
15323
  if (parts.length === 2) {
15271
- const numerator = Number.parseInt(parts[0]);
15272
- const denominator = Number.parseInt(parts[1]);
15324
+ const numerator = GpifParser.parseIntSafe(parts[0], 4);
15325
+ const denominator = GpifParser.parseIntSafe(parts[1], 4);
15273
15326
  offset = ((numerator / denominator) * MidiUtils.QuarterTime) | 0;
15274
15327
  }
15275
15328
  break;
@@ -15295,7 +15348,7 @@ class GpifParser {
15295
15348
  for (const c of node.childElements()) {
15296
15349
  switch (c.localName) {
15297
15350
  case 'Voices':
15298
- this._voicesOfBar.set(barId, c.innerText.split(' '));
15351
+ this._voicesOfBar.set(barId, GpifParser.splitSafe(c.innerText));
15299
15352
  break;
15300
15353
  case 'Clef':
15301
15354
  switch (c.innerText) {
@@ -15370,7 +15423,7 @@ class GpifParser {
15370
15423
  for (const c of node.childElements()) {
15371
15424
  switch (c.localName) {
15372
15425
  case 'Beats':
15373
- this._beatsOfVoice.set(voiceId, c.innerText.split(' '));
15426
+ this._beatsOfVoice.set(voiceId, GpifParser.splitSafe(c.innerText));
15374
15427
  break;
15375
15428
  }
15376
15429
  }
@@ -15394,7 +15447,7 @@ class GpifParser {
15394
15447
  for (const c of node.childElements()) {
15395
15448
  switch (c.localName) {
15396
15449
  case 'Notes':
15397
- this._notesOfBeat.set(beatId, c.innerText.split(' '));
15450
+ this._notesOfBeat.set(beatId, GpifParser.splitSafe(c.innerText));
15398
15451
  break;
15399
15452
  case 'Rhythm':
15400
15453
  this._rhythmOfBeat.set(beatId, c.getAttribute('ref'));
@@ -15510,20 +15563,20 @@ class GpifParser {
15510
15563
  break;
15511
15564
  case 'Whammy':
15512
15565
  const whammyOrigin = new BendPoint(0, 0);
15513
- whammyOrigin.value = this.toBendValue(Number.parseFloat(c.getAttribute('originValue')));
15514
- whammyOrigin.offset = this.toBendOffset(Number.parseFloat(c.getAttribute('originOffset')));
15566
+ whammyOrigin.value = this.toBendValue(GpifParser.parseFloatSafe(c.getAttribute('originValue'), 0));
15567
+ whammyOrigin.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.getAttribute('originOffset'), 0));
15515
15568
  beat.addWhammyBarPoint(whammyOrigin);
15516
15569
  const whammyMiddle1 = new BendPoint(0, 0);
15517
- whammyMiddle1.value = this.toBendValue(Number.parseFloat(c.getAttribute('middleValue')));
15518
- whammyMiddle1.offset = this.toBendOffset(Number.parseFloat(c.getAttribute('middleOffset1')));
15570
+ whammyMiddle1.value = this.toBendValue(GpifParser.parseFloatSafe(c.getAttribute('middleValue'), 0));
15571
+ whammyMiddle1.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.getAttribute('middleOffset1'), 0));
15519
15572
  beat.addWhammyBarPoint(whammyMiddle1);
15520
15573
  const whammyMiddle2 = new BendPoint(0, 0);
15521
- whammyMiddle2.value = this.toBendValue(Number.parseFloat(c.getAttribute('middleValue')));
15522
- whammyMiddle2.offset = this.toBendOffset(Number.parseFloat(c.getAttribute('middleOffset2')));
15574
+ whammyMiddle2.value = this.toBendValue(GpifParser.parseFloatSafe(c.getAttribute('middleValue'), 0));
15575
+ whammyMiddle2.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.getAttribute('middleOffset2'), 0));
15523
15576
  beat.addWhammyBarPoint(whammyMiddle2);
15524
15577
  const whammyDestination = new BendPoint(0, 0);
15525
- whammyDestination.value = this.toBendValue(Number.parseFloat(c.getAttribute('destinationValue')));
15526
- whammyDestination.offset = this.toBendOffset(Number.parseFloat(c.getAttribute('destinationOffset')));
15578
+ whammyDestination.value = this.toBendValue(GpifParser.parseFloatSafe(c.getAttribute('destinationValue'), 0));
15579
+ whammyDestination.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.getAttribute('destinationOffset'), 0));
15527
15580
  beat.addWhammyBarPoint(whammyDestination);
15528
15581
  break;
15529
15582
  case 'Ottavia':
@@ -15584,8 +15637,9 @@ class GpifParser {
15584
15637
  break;
15585
15638
  case 'Timer':
15586
15639
  beat.showTimer = true;
15587
- if (c.innerText.length > 0) {
15588
- beat.timer = Number.parseInt(c.innerText);
15640
+ beat.timer = GpifParser.parseIntSafe(c.innerText, -1);
15641
+ if (beat.timer < 0) {
15642
+ beat.timer = null;
15589
15643
  }
15590
15644
  break;
15591
15645
  }
@@ -15611,7 +15665,7 @@ class GpifParser {
15611
15665
  let value = 0;
15612
15666
  switch (id) {
15613
15667
  case '1124204546':
15614
- value = Number.parseInt(c.findChildElement('Int').innerText);
15668
+ value = GpifParser.parseIntSafe(c.findChildElement('Int')?.innerText, 0);
15615
15669
  switch (value) {
15616
15670
  case 1:
15617
15671
  beat.beamingMode = BeatBeamingMode.ForceMergeWithNext;
@@ -15622,7 +15676,7 @@ class GpifParser {
15622
15676
  }
15623
15677
  break;
15624
15678
  case '1124204552':
15625
- value = Number.parseInt(c.findChildElement('Int').innerText);
15679
+ value = GpifParser.parseIntSafe(c.findChildElement('Int')?.innerText, 0);
15626
15680
  switch (value) {
15627
15681
  case 1:
15628
15682
  if (beat.beamingMode !== BeatBeamingMode.ForceSplitToNext) {
@@ -15632,11 +15686,11 @@ class GpifParser {
15632
15686
  }
15633
15687
  break;
15634
15688
  case '1124204545':
15635
- value = Number.parseInt(c.findChildElement('Int').innerText);
15689
+ value = GpifParser.parseIntSafe(c.findChildElement('Int')?.innerText, 0);
15636
15690
  beat.invertBeamDirection = value === 1;
15637
15691
  break;
15638
15692
  case '687935489':
15639
- value = Number.parseInt(c.findChildElement('Int').innerText);
15693
+ value = GpifParser.parseIntSafe(c.findChildElement('Int')?.innerText, 0);
15640
15694
  beat.brushDuration = value;
15641
15695
  break;
15642
15696
  }
@@ -15652,7 +15706,7 @@ class GpifParser {
15652
15706
  switch (id) {
15653
15707
  case '1124139520':
15654
15708
  const childNode = c.findChildElement('Double') ?? c.findChildElement('Float');
15655
- bar.displayScale = Number.parseFloat(childNode.innerText);
15709
+ bar.displayScale = GpifParser.parseFloatSafe(childNode?.innerText, 1);
15656
15710
  break;
15657
15711
  }
15658
15712
  break;
@@ -15666,7 +15720,7 @@ class GpifParser {
15666
15720
  const id = c.getAttribute('id');
15667
15721
  switch (id) {
15668
15722
  case '1124073984':
15669
- masterBar.displayScale = Number.parseFloat(c.findChildElement('Double').innerText);
15723
+ masterBar.displayScale = GpifParser.parseFloatSafe(c.findChildElement('Double')?.innerText, 1);
15670
15724
  break;
15671
15725
  }
15672
15726
  break;
@@ -15686,7 +15740,7 @@ class GpifParser {
15686
15740
  const name = c.getAttribute('name');
15687
15741
  switch (name) {
15688
15742
  case 'Brush':
15689
- if (c.findChildElement('Direction').innerText === 'Up') {
15743
+ if (c.findChildElement('Direction')?.innerText === 'Up') {
15690
15744
  beat.brushType = BrushType.BrushUp;
15691
15745
  }
15692
15746
  else {
@@ -15694,7 +15748,7 @@ class GpifParser {
15694
15748
  }
15695
15749
  break;
15696
15750
  case 'PickStroke':
15697
- if (c.findChildElement('Direction').innerText === 'Up') {
15751
+ if (c.findChildElement('Direction')?.innerText === 'Up') {
15698
15752
  beat.pickStroke = PickStroke.Up;
15699
15753
  }
15700
15754
  else {
@@ -15712,7 +15766,7 @@ class GpifParser {
15712
15766
  }
15713
15767
  break;
15714
15768
  case 'VibratoWTremBar':
15715
- switch (c.findChildElement('Strength').innerText) {
15769
+ switch (c.findChildElement('Strength')?.innerText) {
15716
15770
  case 'Wide':
15717
15771
  beat.vibrato = VibratoType.Wide;
15718
15772
  break;
@@ -15731,40 +15785,40 @@ class GpifParser {
15731
15785
  if (!whammyOrigin) {
15732
15786
  whammyOrigin = new BendPoint(0, 0);
15733
15787
  }
15734
- whammyOrigin.value = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
15788
+ whammyOrigin.value = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15735
15789
  break;
15736
15790
  case 'WhammyBarOriginOffset':
15737
15791
  if (!whammyOrigin) {
15738
15792
  whammyOrigin = new BendPoint(0, 0);
15739
15793
  }
15740
- whammyOrigin.offset = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
15794
+ whammyOrigin.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15741
15795
  break;
15742
15796
  case 'WhammyBarMiddleValue':
15743
- whammyMiddleValue = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
15797
+ whammyMiddleValue = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15744
15798
  break;
15745
15799
  case 'WhammyBarMiddleOffset1':
15746
- whammyMiddleOffset1 = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
15800
+ whammyMiddleOffset1 = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15747
15801
  break;
15748
15802
  case 'WhammyBarMiddleOffset2':
15749
- whammyMiddleOffset2 = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
15803
+ whammyMiddleOffset2 = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15750
15804
  break;
15751
15805
  case 'WhammyBarDestinationValue':
15752
15806
  if (!whammyDestination) {
15753
15807
  whammyDestination = new BendPoint(BendPoint.MaxPosition, 0);
15754
15808
  }
15755
- whammyDestination.value = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
15809
+ whammyDestination.value = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15756
15810
  break;
15757
15811
  case 'WhammyBarDestinationOffset':
15758
15812
  if (!whammyDestination) {
15759
15813
  whammyDestination = new BendPoint(0, 0);
15760
15814
  }
15761
- whammyDestination.offset = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
15815
+ whammyDestination.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
15762
15816
  break;
15763
15817
  case 'BarreFret':
15764
- beat.barreFret = Number.parseInt(c.findChildElement('Fret').innerText);
15818
+ beat.barreFret = GpifParser.parseIntSafe(c.findChildElement('Fret')?.innerText, 0);
15765
15819
  break;
15766
15820
  case 'BarreString':
15767
- switch (c.findChildElement('String').innerText) {
15821
+ switch (c.findChildElement('String')?.innerText) {
15768
15822
  case '0':
15769
15823
  beat.barreShape = BarreShape.Full;
15770
15824
  break;
@@ -15774,7 +15828,7 @@ class GpifParser {
15774
15828
  }
15775
15829
  break;
15776
15830
  case 'Rasgueado':
15777
- switch (c.findChildElement('Rasgueado').innerText) {
15831
+ switch (c.findChildElement('Rasgueado')?.innerText) {
15778
15832
  case 'ii_1':
15779
15833
  beat.rasgueado = Rasgueado.Ii;
15780
15834
  break;
@@ -15884,11 +15938,11 @@ class GpifParser {
15884
15938
  note.isLetRing = true;
15885
15939
  break;
15886
15940
  case 'Trill':
15887
- note.trillValue = Number.parseInt(c.innerText);
15941
+ note.trillValue = GpifParser.parseIntSafe(c.innerText, -1);
15888
15942
  note.trillSpeed = Duration.Sixteenth;
15889
15943
  break;
15890
15944
  case 'Accent':
15891
- const accentFlags = Number.parseInt(c.innerText);
15945
+ const accentFlags = GpifParser.parseIntSafe(c.innerText, 0);
15892
15946
  if ((accentFlags & 0x01) !== 0) {
15893
15947
  note.isStaccato = true;
15894
15948
  }
@@ -15956,7 +16010,7 @@ class GpifParser {
15956
16010
  }
15957
16011
  break;
15958
16012
  case 'InstrumentArticulation':
15959
- note.percussionArticulation = Number.parseInt(c.innerText);
16013
+ note.percussionArticulation = GpifParser.parseIntSafe(c.innerText, 0);
15960
16014
  break;
15961
16015
  case 'Ornament':
15962
16016
  switch (c.innerText) {
@@ -15999,16 +16053,16 @@ class GpifParser {
15999
16053
  }
16000
16054
  break;
16001
16055
  case 'String':
16002
- note.string = Number.parseInt(c.findChildElement('String').innerText) + 1;
16056
+ note.string = GpifParser.parseIntSafe(c.findChildElement('String')?.innerText, 0) + 1;
16003
16057
  break;
16004
16058
  case 'Fret':
16005
- note.fret = Number.parseInt(c.findChildElement('Fret').innerText);
16059
+ note.fret = GpifParser.parseIntSafe(c.findChildElement('Fret')?.innerText, 0);
16006
16060
  break;
16007
16061
  case 'Element':
16008
- element = Number.parseInt(c.findChildElement('Element').innerText);
16062
+ element = GpifParser.parseIntSafe(c.findChildElement('Element')?.innerText, 0);
16009
16063
  break;
16010
16064
  case 'Variation':
16011
- variation = Number.parseInt(c.findChildElement('Variation').innerText);
16065
+ variation = GpifParser.parseIntSafe(c.findChildElement('Variation')?.innerText, 0);
16012
16066
  break;
16013
16067
  case 'Tapped':
16014
16068
  this._tappedNotes.set(noteId, true);
@@ -16044,7 +16098,7 @@ class GpifParser {
16044
16098
  case 'HarmonicFret':
16045
16099
  const hfret = c.findChildElement('HFret');
16046
16100
  if (hfret) {
16047
- note.harmonicValue = Number.parseFloat(hfret.innerText);
16101
+ note.harmonicValue = GpifParser.parseFloatSafe(hfret.innerText, 0);
16048
16102
  }
16049
16103
  break;
16050
16104
  case 'Muted':
@@ -16058,14 +16112,14 @@ class GpifParser {
16058
16112
  }
16059
16113
  break;
16060
16114
  case 'Octave':
16061
- note.octave = Number.parseInt(c.findChildElement('Number').innerText);
16115
+ note.octave = GpifParser.parseIntSafe(c.findChildElement('Number')?.innerText, 0);
16062
16116
  // when exporting GP6 from GP7 the tone might be missing
16063
16117
  if (note.tone === -1) {
16064
16118
  note.tone = 0;
16065
16119
  }
16066
16120
  break;
16067
16121
  case 'Tone':
16068
- note.tone = Number.parseInt(c.findChildElement('Step').innerText);
16122
+ note.tone = GpifParser.parseIntSafe(c.findChildElement('Step')?.innerText, 0);
16069
16123
  break;
16070
16124
  case 'ConcertPitch':
16071
16125
  this.parseConcertPitch(c, note);
@@ -16077,34 +16131,34 @@ class GpifParser {
16077
16131
  if (!bendOrigin) {
16078
16132
  bendOrigin = new BendPoint(0, 0);
16079
16133
  }
16080
- bendOrigin.value = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
16134
+ bendOrigin.value = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16081
16135
  break;
16082
16136
  case 'BendOriginOffset':
16083
16137
  if (!bendOrigin) {
16084
16138
  bendOrigin = new BendPoint(0, 0);
16085
16139
  }
16086
- bendOrigin.offset = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
16140
+ bendOrigin.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16087
16141
  break;
16088
16142
  case 'BendMiddleValue':
16089
- bendMiddleValue = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
16143
+ bendMiddleValue = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16090
16144
  break;
16091
16145
  case 'BendMiddleOffset1':
16092
- bendMiddleOffset1 = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
16146
+ bendMiddleOffset1 = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16093
16147
  break;
16094
16148
  case 'BendMiddleOffset2':
16095
- bendMiddleOffset2 = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
16149
+ bendMiddleOffset2 = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16096
16150
  break;
16097
16151
  case 'BendDestinationValue':
16098
16152
  if (!bendDestination) {
16099
16153
  bendDestination = new BendPoint(BendPoint.MaxPosition, 0);
16100
16154
  }
16101
- bendDestination.value = this.toBendValue(Number.parseFloat(c.findChildElement('Float').innerText));
16155
+ bendDestination.value = this.toBendValue(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16102
16156
  break;
16103
16157
  case 'BendDestinationOffset':
16104
16158
  if (!bendDestination) {
16105
16159
  bendDestination = new BendPoint(0, 0);
16106
16160
  }
16107
- bendDestination.offset = this.toBendOffset(Number.parseFloat(c.findChildElement('Float').innerText));
16161
+ bendDestination.offset = this.toBendOffset(GpifParser.parseFloatSafe(c.findChildElement('Float')?.innerText, 0));
16108
16162
  break;
16109
16163
  case 'HopoOrigin':
16110
16164
  if (c.findChildElement('Enable')) {
@@ -16120,7 +16174,7 @@ class GpifParser {
16120
16174
  note.isLeftHandTapped = true;
16121
16175
  break;
16122
16176
  case 'Slide':
16123
- const slideFlags = Number.parseInt(c.findChildElement('Flags').innerText);
16177
+ const slideFlags = GpifParser.parseIntSafe(c.findChildElement('Flags')?.innerText, 0);
16124
16178
  if ((slideFlags & 1) !== 0) {
16125
16179
  note.slideOutType = SlideOutType.Shift;
16126
16180
  }
@@ -16258,11 +16312,11 @@ class GpifParser {
16258
16312
  }
16259
16313
  break;
16260
16314
  case 'PrimaryTuplet':
16261
- rhythm.tupletNumerator = Number.parseInt(c.getAttribute('num'));
16262
- rhythm.tupletDenominator = Number.parseInt(c.getAttribute('den'));
16315
+ rhythm.tupletNumerator = GpifParser.parseIntSafe(c.getAttribute('num'), -1);
16316
+ rhythm.tupletDenominator = GpifParser.parseIntSafe(c.getAttribute('den'), -1);
16263
16317
  break;
16264
16318
  case 'AugmentationDot':
16265
- rhythm.dots = Number.parseInt(c.getAttribute('count'));
16319
+ rhythm.dots = GpifParser.parseIntSafe(c.getAttribute('count'), 0);
16266
16320
  break;
16267
16321
  }
16268
16322
  }
@@ -18277,61 +18331,11 @@ class MusicXmlImporter extends ScoreImporter {
18277
18331
  this._score.tempo = 120;
18278
18332
  this._score.stylesheet.hideDynamics = true;
18279
18333
  this.parseDom(dom);
18280
- this.consolidate();
18334
+ ModelUtils.consolidate(this._score);
18281
18335
  this._score.finish(this.settings);
18282
18336
  this._score.rebuildRepeatGroups();
18283
18337
  return this._score;
18284
18338
  }
18285
- consolidate() {
18286
- const usedChannels = new Set([SynthConstants.PercussionChannel]);
18287
- for (const track of this._score.tracks) {
18288
- // unique midi channels and generate secondary channels
18289
- if (track.playbackInfo.primaryChannel !== SynthConstants.PercussionChannel) {
18290
- while (usedChannels.has(track.playbackInfo.primaryChannel)) {
18291
- track.playbackInfo.primaryChannel++;
18292
- }
18293
- }
18294
- usedChannels.add(track.playbackInfo.primaryChannel);
18295
- if (track.playbackInfo.secondaryChannel !== SynthConstants.PercussionChannel) {
18296
- while (usedChannels.has(track.playbackInfo.secondaryChannel)) {
18297
- track.playbackInfo.secondaryChannel++;
18298
- }
18299
- }
18300
- usedChannels.add(track.playbackInfo.secondaryChannel);
18301
- for (const staff of track.staves) {
18302
- // fill empty beats
18303
- for (const b of staff.bars) {
18304
- for (const v of b.voices) {
18305
- if (v.isEmpty && v.beats.length === 0) {
18306
- const emptyBeat = new Beat();
18307
- emptyBeat.isEmpty = true;
18308
- v.addBeat(emptyBeat);
18309
- }
18310
- }
18311
- }
18312
- // fill missing bars
18313
- const voiceCount = staff.bars.length === 0 ? 1 : staff.bars[0].voices.length;
18314
- while (staff.bars.length < this._score.masterBars.length) {
18315
- const bar = new Bar();
18316
- staff.addBar(bar);
18317
- const previousBar = bar.previousBar;
18318
- if (previousBar) {
18319
- bar.clef = previousBar.clef;
18320
- bar.clefOttava = previousBar.clefOttava;
18321
- bar.keySignature = bar.previousBar.keySignature;
18322
- bar.keySignatureType = bar.previousBar.keySignatureType;
18323
- }
18324
- for (let i = 0; i < voiceCount; i++) {
18325
- const v = new Voice$1();
18326
- bar.addVoice(v);
18327
- const emptyBeat = new Beat();
18328
- emptyBeat.isEmpty = true;
18329
- v.addBeat(emptyBeat);
18330
- }
18331
- }
18332
- }
18333
- }
18334
- }
18335
18339
  extractMusicXml() {
18336
18340
  const zip = new ZipReader(this.data);
18337
18341
  let entries;
@@ -48032,7 +48036,7 @@ class DynamicsGlyph extends MusicFontGlyph {
48032
48036
  case DynamicValue.RFZ:
48033
48037
  return MusicFontSymbol.DynamicRinforzando2;
48034
48038
  case DynamicValue.SFZ:
48035
- return MusicFontSymbol.DynamicSforzando1;
48039
+ return MusicFontSymbol.DynamicSforzato;
48036
48040
  case DynamicValue.SFFZ:
48037
48041
  return MusicFontSymbol.DynamicSforzatoFF;
48038
48042
  case DynamicValue.FZ:
@@ -56037,29 +56041,7 @@ class CapellaParser {
56037
56041
  this.score.finish(settings);
56038
56042
  }
56039
56043
  consolidate() {
56040
- // voice counts and contents might be inconsistent
56041
- // we need to ensure we have an equal amount of voices across all bars
56042
- // and voices must contain an empty beat at minimum
56043
- for (const track of this.score.tracks) {
56044
- const trackVoiceCount = this._voiceCounts.get(track.index);
56045
- for (const staff of track.staves) {
56046
- while (staff.bars.length < this.score.masterBars.length) {
56047
- this.addNewBar(staff);
56048
- }
56049
- for (const bar of staff.bars) {
56050
- while (bar.voices.length < trackVoiceCount) {
56051
- bar.addVoice(new Voice$1());
56052
- }
56053
- for (const voice of bar.voices) {
56054
- if (voice.beats.length === 0) {
56055
- const emptyBeat = new Beat();
56056
- emptyBeat.isEmpty = true;
56057
- voice.addBeat(emptyBeat);
56058
- }
56059
- }
56060
- }
56061
- }
56062
- }
56044
+ ModelUtils.consolidate(this.score);
56063
56045
  CapellaParser.applyEffectRange(this._slurs, (_, beat) => {
56064
56046
  beat.isLegatoOrigin = true;
56065
56047
  });
@@ -59311,9 +59293,9 @@ class VersionInfo {
59311
59293
  print(`build date: ${VersionInfo.date}`);
59312
59294
  }
59313
59295
  }
59314
- VersionInfo.version = '1.5.0-alpha.1394';
59315
- VersionInfo.date = '2025-05-03T02:02:13.897Z';
59316
- VersionInfo.commit = '762e8a813be30f60be07af4121f5aac01da170bb';
59296
+ VersionInfo.version = '1.5.0';
59297
+ VersionInfo.date = '2025-05-04T16:47:31.157Z';
59298
+ VersionInfo.commit = 'a7cda51f24b9e783ce3d3b719fa1e65198c24319';
59317
59299
 
59318
59300
  /**
59319
59301
  * A factory for custom layout engines.
@@ -63539,6 +63521,7 @@ const _barrel$3 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty(
63539
63521
  Automation,
63540
63522
  get AutomationType () { return AutomationType; },
63541
63523
  Bar,
63524
+ get BarLineStyle () { return BarLineStyle; },
63542
63525
  BarStyle,
63543
63526
  get BarSubElement () { return BarSubElement; },
63544
63527
  get BarreShape () { return BarreShape; },
@@ -63570,6 +63553,7 @@ const _barrel$3 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty(
63570
63553
  GraceGroup,
63571
63554
  get GraceType () { return GraceType; },
63572
63555
  get HarmonicType () { return HarmonicType; },
63556
+ HeaderFooterStyle,
63573
63557
  InstrumentArticulation,
63574
63558
  JsonConverter,
63575
63559
  get KeySignature () { return KeySignature; },