smoosic 1.0.21 → 1.0.22

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.
@@ -745,7 +745,8 @@ export class SuiLayoutFormatter {
745
745
  clefLast: string, keySigLast: string, timeSigLast: TimeSignature, tempoLast: SmoTempoText) {
746
746
  // The key signature is set based on the transpose index already, i.e. an Eb part in concert C already has 3 sharps.
747
747
  const xposeScore = this.score?.preferences?.transposingScore && (this.score?.isPartExposed() === false);
748
- const xposeOffset = xposeScore ? measure.transposeIndex : 0;
748
+ // const xposeOffset = xposeScore ? measure.transposeIndex : 0;
749
+ const xposeOffset = 0;
749
750
  const measureKeySig = SmoMusic.vexKeySignatureTranspose(measure.keySignature, xposeOffset);
750
751
  measure.svg.forceClef = (systemIndex === 0 || measure.clef !== clefLast);
751
752
  measure.svg.forceTimeSignature = (measure.measureNumber.measureIndex === 0 ||
@@ -610,6 +610,22 @@ export class SuiScoreRender {
610
610
 
611
611
  measures.forEach((measure) => {
612
612
  const context = this.vexContainers.getRenderer(measure.svg.logicalBox);
613
+ // For scores with more than one part, put a helper part number on there.
614
+ if (this.score?.preferences.showPartNames && !printing && !this.score?.isPartExposed()) {
615
+ this.score?.staves.forEach((curStaff) => {
616
+ if (curStaff.partInfo.partAbbreviation.length > 0) {
617
+ const numAr: any[] = [];
618
+ const mm = curStaff.measures[measure.measureNumber.localIndex];
619
+ const modBox = context.offsetSvgPoint(mm.svg.logicalBox);
620
+ numAr.push({ y: modBox.y + mm.svg.logicalBox.height / 2 });
621
+ numAr.push({ x: modBox.x - (20 + curStaff.partInfo.partAbbreviation.length) });
622
+ numAr.push({ 'font-family': SourceSansProFont.fontFamily });
623
+ numAr.push({ 'font-size': '10pt' });
624
+ SvgHelpers.placeSvgText(context.svg, numAr, 'measure-number',
625
+ curStaff.partInfo.partAbbreviation);
626
+ }
627
+ });
628
+ }
613
629
  if (measure.measureNumber.localIndex > 0 && measure.measureNumber.systemIndex === 0 && measure.svg.logicalBox && context) {
614
630
  const numAr: any[] = [];
615
631
  const modBox = context.offsetSvgPoint(measure.svg.logicalBox);
@@ -617,9 +617,11 @@ export abstract class SuiScoreView {
617
617
  const row = rows[i];
618
618
  if (row.show) {
619
619
  const srcStave = this.storeScore.staves[i];
620
- const jsonObj = srcStave.serialize({ skipMaps: false, preserveIds: true });
620
+ const jsonObj = srcStave.serialize({ skipMaps: false, preserveIds: true,
621
+ transposeInstruments: !this.storeScore.preferences.transposingScore
622
+ });
621
623
  jsonObj.staffId = staffMap.length;
622
- const nStave = SmoSystemStaff.deserialize(jsonObj);
624
+ const nStave = SmoSystemStaff.deserialize(jsonObj, !this.storeScore.preferences.transposingScore);
623
625
  nStave.mapStaffFromTo(i, nscore.staves.length);
624
626
  nscore.staves.push(nStave);
625
627
  if (srcStave.keySignatureMap) {
@@ -673,13 +675,14 @@ export abstract class SuiScoreView {
673
675
  this.renderer.setViewport();
674
676
  }
675
677
  /**
676
- * Update score based on transposing flag.
678
+ * If the score is non-transposed, transpose the part so it is in the
679
+ * correct key.
677
680
  */
678
681
  _setTransposing() {
679
- if (!this.isPartExposed()) {
682
+ if (this.isPartExposed()) {
680
683
  const xpose = this.score.preferences?.transposingScore;
681
684
  if (xpose) {
682
- this.score.setTransposing();
685
+ this.score.setNonTransposing();
683
686
  }
684
687
  }
685
688
  }
@@ -727,14 +730,9 @@ export abstract class SuiScoreView {
727
730
  serialized.keySignature = concertKey;
728
731
  const rmeasure = SmoMeasure.deserialize(serialized);
729
732
  rmeasure.svg = svg;
730
- // If this is a tranposed score, the displayed score needs to be in 'C'.
731
- // We do this step last since serialize/unserialize work in a pitch transposed
732
- // for the instrument
733
- if (this.score.preferences.transposingScore && !this.score.isPartExposed()) {
734
- rmeasure.transposeToOffset(-1 * xpose, 'c');
735
- rmeasure.keySignature = 'c';
736
- rmeasure.transposeIndex = 0;
737
- }
733
+ // We used to force a tranposing score stff into 'c' but now we keep the
734
+ // measures in the concert key, and the transpose offset to 0, so no need to do anything
735
+ // special here.
738
736
  const selector: SmoSelector = { staff: staffId, measure: i, voice: 0, tick: 0, pitches: [] };
739
737
  this.score.replaceMeasure(selector, rmeasure);
740
738
  });
@@ -224,8 +224,10 @@ export class SuiScoreViewOperations extends SuiScoreView {
224
224
  this.storeScore.updateScorePreferences(new SmoScorePreferences(pref));
225
225
  if (curXpose === false && oldXpose === true) {
226
226
  this.score.setNonTransposing();
227
+ this.storeScore.setNonTransposing();
227
228
  } else if (curXpose === true && oldXpose === false) {
228
229
  this.score.setTransposing();
230
+ this.storeScore.setTransposing();
229
231
  }
230
232
  this.renderer.setDirty();
231
233
  return this.renderer.updatePromise();
@@ -579,35 +579,40 @@ export class SuiTracker extends SuiMapper implements TrackerKeyHandler {
579
579
  this.selections = ar;
580
580
  }
581
581
 
582
- _selectFromToInStaff(score: SmoScore, sel1: SmoSelection, sel2: SmoSelection) {
583
- const selections = SmoSelection.innerSelections(score, sel1.selector, sel2.selector);
584
- /* .filter((ff) =>
585
- ff.selector.voice === sel1.measure.activeVoice
586
- ); */
587
- this.selections = [];
588
- // Get the actual selections from our map, since the client bounding boxes are already computed
589
- selections.forEach((sel) => {
590
- const key = SmoSelector.getNoteKey(sel.selector);
591
- sel.measure.setActiveVoice(sel.selector.voice);
592
- // Skip measures that are not rendered because they are part of a multi-rest
593
- if (this.measureNoteMap && this.measureNoteMap[key]) {
594
- this.selections.push(this.measureNoteMap[key]);
595
- }
596
- });
597
-
598
- if (this.selections.length === 0) {
599
- this.selections = [sel1];
600
- }
601
- this.idleTimer = Date.now();
602
- }
603
- _selectBetweenSelections(s1: SmoSelection, s2: SmoSelection) {
582
+ _selectBetweenSelections(s1o: SmoSelection, s2o: SmoSelection) {
604
583
  const score = this.renderer.score ?? null;
605
584
  if (!score) {
606
585
  return;
607
586
  }
608
- const min = SmoSelector.gt(s1.selector, s2.selector) ? s2 : s1;
609
- const max = SmoSelector.lt(min.selector, s2.selector) ? s2 : s1;
610
- this._selectFromToInStaff(score, min, max);
587
+ const staffMin = Math.min(s1o.selector.staff, s2o.selector.staff);
588
+ const staffMax = Math.max(s1o.selector.staff, s2o.selector.staff);
589
+ this.selections = [];
590
+ for (let i = staffMin; i <= staffMax; ++i) {
591
+ const s1 = JSON.parse(JSON.stringify(s1o.selector));
592
+ s1.staff = i;
593
+ const s2 = JSON.parse(JSON.stringify(s2o.selector));
594
+ s2.staff = i;
595
+ const s1s = SmoSelection.selectionFromSelector(this.score!, s1);
596
+ const s2s = SmoSelection.selectionFromSelector(this.score!, s2);
597
+ if (s1s && s2s) {
598
+ const min = SmoSelector.gt(s1s.selector, s2s.selector) ? s2s : s1s;
599
+ const max = SmoSelector.lt(min.selector, s2s.selector) ? s2s : s1s;
600
+ const selections = SmoSelection.innerSelections(score, min.selector, max.selector);
601
+ // Get the actual selections from our map, since the client bounding boxes are already computed
602
+ selections.forEach((sel) => {
603
+ const key = SmoSelector.getNoteKey(sel.selector);
604
+ sel.measure.setActiveVoice(sel.selector.voice);
605
+ // Skip measures that are not rendered because they are part of a multi-rest
606
+ if (this.measureNoteMap && this.measureNoteMap[key]) {
607
+ this.selections.push(this.measureNoteMap[key]);
608
+ }
609
+ });
610
+
611
+ if (this.selections.length === 0) {
612
+ this.selections = [min];
613
+ }
614
+ }
615
+ }
611
616
  this._createLocalModifiersList();
612
617
  this.highlightQueue.selectionCount = this.selections.length;
613
618
  this.deferHighlight();
@@ -634,10 +639,10 @@ export class SuiTracker extends SuiMapper implements TrackerKeyHandler {
634
639
 
635
640
  if (ev.shiftKey) {
636
641
  const sel1 = this.getExtremeSelection(-1);
637
- if (sel1.selector.staff === this.suggestion.selector.staff) {
638
- this._selectBetweenSelections(sel1, this.suggestion);
639
- return;
640
- }
642
+ //if (sel1.selector.staff === this.suggestion.selector.staff) {
643
+ this._selectBetweenSelections(sel1, this.suggestion);
644
+ return;
645
+ //}
641
646
  }
642
647
 
643
648
  if (ev.ctrlKey) {
@@ -15,7 +15,7 @@ import { SmoOrnament, SmoDynamicText,
15
15
  import { SmoSelection } from '../../smo/xform/selections';
16
16
  import { SmoMeasure, MeasureTickmaps } from '../../smo/data/measure';
17
17
  import { SvgHelpers } from '../sui/svgHelpers';
18
- import { Clef, IsClef, ElementLike } from '../../smo/data/common';
18
+ import { Clef, IsClef, ElementLike, Pitch } from '../../smo/data/common';
19
19
  import { SvgPage } from '../sui/svgPageMap';
20
20
  import { SmoTabStave } from '../../smo/data/staffModifiers';
21
21
  import { toVexBarlineType, vexBarlineType, vexBarlinePosition, toVexBarlinePosition, toVexSymbol,
@@ -70,8 +70,10 @@ export class VxMeasure implements VxMeasureIf {
70
70
  collisionMap: Record<number, SmoNote[]> = {};
71
71
  dbgLeftX: number = 0;
72
72
  dbgWidth: number = 0;
73
+ tiedOverPitches: Pitch[] = [];
73
74
 
74
- constructor(context: SvgPage, selection: SmoSelection, printing: boolean, softmax: number) {
75
+ constructor(context: SvgPage, selection: SmoSelection,
76
+ printing: boolean, softmax: number, tiedOverPitches: Pitch[]) {
75
77
  this.context = context;
76
78
  this.rendered = false;
77
79
  this.selection = selection;
@@ -85,6 +87,7 @@ export class VxMeasure implements VxMeasureIf {
85
87
  this.beamToVexMap = {};
86
88
  this.softmax = softmax;
87
89
  this.smoTabStave = selection.staff.getTabStaveForMeasure(selection.selector);
90
+ this.tiedOverPitches = tiedOverPitches;
88
91
  }
89
92
 
90
93
  static get fillStyle() {
@@ -229,12 +232,14 @@ export class VxMeasure implements VxMeasureIf {
229
232
  vexNote.setStave(this.stave);
230
233
  }
231
234
  }
235
+ const tiedOverPitches = tickIndex === 0 ? this.tiedOverPitches : [];
232
236
  const noteData: VexNoteModifierIf = {
233
237
  smoMeasure: this.smoMeasure,
234
238
  vxMeasure: this,
235
239
  smoNote: smoNote,
236
240
  staveNote: vexNote,
237
241
  voiceIndex: voiceIx,
242
+ tiedOverPitches,
238
243
  tickIndex: tickIndex
239
244
  }
240
245
  if (tabNote) {
@@ -9,7 +9,7 @@ import { SmoOrnament, SmoArticulation, SmoDynamicText, SmoLyric,
9
9
  import { SmoSelection } from '../../smo/xform/selections';
10
10
  import { SmoMeasure, MeasureTickmaps } from '../../smo/data/measure';
11
11
  import { SvgHelpers } from '../sui/svgHelpers';
12
- import { Clef, IsClef } from '../../smo/data/common';
12
+ import { Clef, IsClef, Pitch } from '../../smo/data/common';
13
13
  import { SvgPage } from '../sui/svgPageMap';
14
14
  import { toVexBarlineType, vexBarlineType, vexBarlinePosition, toVexBarlinePosition, toVexSymbol,
15
15
  toVexTextJustification, toVexTextPosition, getVexChordBlocks, toVexStemDirection } from './smoAdapter';
@@ -42,6 +42,7 @@ export interface VexNoteModifierIf {
42
42
  staveNote: Note,
43
43
  voiceIndex: number,
44
44
  tickIndex: number,
45
+ tiedOverPitches: Pitch[],
45
46
  tabNote?: StemmableNote | TabNote,
46
47
  }
47
48
  /**
@@ -96,6 +97,20 @@ export class VxNote {
96
97
  this.noteData.smoNote.accidentalsRendered = [];
97
98
  for (i = 0; i < this.noteData.smoNote.pitches.length && this.noteData.vxMeasure.tickmapObject !== null; ++i) {
98
99
  const pitch = this.noteData.smoNote.pitches[i];
100
+ const tiedOver = this.noteData.tiedOverPitches.find((pp) => pp.letter === pitch.letter && pp.octave === pitch.octave);
101
+ if (tiedOver) {
102
+ if (tiedOver.accidental === pitch.accidental) {
103
+ // Why do we do this?
104
+ this.noteData.smoNote.accidentalsRendered.push('');
105
+ continue;
106
+ }
107
+ else {
108
+ const acc = new VF.Accidental(pitch.accidental);
109
+ this.noteData.smoNote.accidentalsRendered.push(pitch.accidental);
110
+ this.noteData.staveNote.addModifier(acc, i);
111
+ continue;
112
+ }
113
+ }
99
114
  const zz = SmoMusic.accidentalDisplay(pitch, this.noteData.smoMeasure.keySignature,
100
115
  this.noteData.vxMeasure.tickmapObject.tickmaps[this.noteData.voiceIndex].durationMap[this.noteData.tickIndex],
101
116
  this.noteData.vxMeasure.tickmapObject.accidentalArray);
@@ -621,7 +621,8 @@ export class VxSystem {
621
621
  if (softmax === SmoMeasureFormat.defaultProportionality) {
622
622
  softmax = this.score.layoutManager?.getGlobalLayout().proportionality ?? 0;
623
623
  }
624
- const vxMeasure: VxMeasure = new VxMeasure(this.context, selection, printing, softmax);
624
+ const tiedOverPitches = selection.staff.getTiedPitchesForNextMeasure(smoMeasure.measureNumber.measureIndex - 1);
625
+ const vxMeasure: VxMeasure = new VxMeasure(this.context, selection, printing, softmax, tiedOverPitches);
625
626
 
626
627
  // create the vex notes, beam groups etc. for the measure
627
628
  vxMeasure.preFormat();
@@ -592,7 +592,7 @@ export class SmoMusic {
592
592
  { letter: 'b', accidental: 'bb', role: 'b5'},
593
593
  { letter: 'b', accidental: 'b', role: '5'},
594
594
  { letter: 'b', accidental: 'n', role: '7/6'},
595
- { letter: 'c', accidental: 'b', role: '6'},
595
+ { letter: 'c', accidental: 'b', role: 'b6'},
596
596
  { letter: 'c', accidental: 'n', role: '6'},
597
597
  { letter: 'c', accidental: '#', role: '7/7'},
598
598
  { letter: 'd', accidental: 'b', role: 'b7'},
@@ -366,7 +366,10 @@ export class SmoScore {
366
366
  const current = func(measure);
367
367
  const ix = measure.measureNumber.measureIndex;
368
368
  const currentInstrument = this.staves[0].getStaffInstrument(ix);
369
- current.keySignature = SmoMusic.vexKeySigWithOffset(current.keySignature, -1 * currentInstrument.keyOffset);
369
+ // If this is a non-transposing score, adjust the key signature
370
+ if (!this.preferences.transposingScore) {
371
+ current.keySignature = SmoMusic.vexKeySigWithOffset(current.keySignature, -1 * currentInstrument.keyOffset);
372
+ }
370
373
  if (ix === 0) {
371
374
  keySignature[0] = current.keySignature;
372
375
  tempo[0] = current.tempo;
@@ -483,7 +486,9 @@ export class SmoScore {
483
486
  obj.audioSettings = this.audioSettings.serialize();
484
487
  if (!skipStaves) {
485
488
  this.staves.forEach((staff: SmoSystemStaff) => {
486
- obj.staves!.push(staff.serialize({ skipMaps: true, preserveIds: preserveIds }));
489
+ obj.staves!.push(staff.serialize({ skipMaps: true, preserveIds: preserveIds,
490
+ transposeInstruments: !this.preferences.transposingScore
491
+ }));
487
492
  });
488
493
  } else {
489
494
  obj.staves = [];
@@ -687,7 +692,7 @@ export class SmoScore {
687
692
  jsonObj.staves.forEach((staffObj: any, staffIx: number) => {
688
693
  staffObj.staffId = staffIx;
689
694
  staffObj.renumberingMap = renumberingMap;
690
- const staff = SmoSystemStaff.deserialize(staffObj);
695
+ const staff = SmoSystemStaff.deserialize(staffObj, !params.preferences?.transposingScore);
691
696
  staves.push(staff);
692
697
  });
693
698
 
@@ -124,10 +124,11 @@ export interface SmoScoreInfo {
124
124
  }
125
125
 
126
126
 
127
- export type SmoScorePreferenceBool = 'autoPlay' | 'autoAdvance' | 'showPiano' | 'hideEmptyLines' | 'transposingScore' | 'autoScrollPlayback';
127
+ export type SmoScorePreferenceBool = 'autoPlay' | 'autoAdvance' | 'showPiano' | 'hideEmptyLines'
128
+ | 'transposingScore' | 'autoScrollPlayback' | 'showPartNames';
128
129
  export type SmoScorePreferenceNumber = 'defaultDupleDuration' | 'defaultTripleDuration';
129
130
  export const SmoScorePreferenceBools: SmoScorePreferenceBool[] = ['autoPlay', 'autoAdvance', 'showPiano', 'hideEmptyLines',
130
- 'transposingScore', 'autoScrollPlayback'];
131
+ 'transposingScore', 'autoScrollPlayback', 'showPartNames'];
131
132
  export const SmoScorePreferenceNumbers: SmoScorePreferenceNumber[] = ['defaultDupleDuration', 'defaultTripleDuration'];
132
133
  /**
133
134
  * Global score/program behavior preferences, see below for parameters
@@ -142,6 +143,7 @@ export interface SmoScorePreferencesParams {
142
143
  showPiano: boolean;
143
144
  hideEmptyLines: boolean;
144
145
  transposingScore: boolean;
146
+ showPartNames: boolean;
145
147
  }
146
148
  /**
147
149
  * Some default SMO behavior
@@ -163,6 +165,7 @@ export class SmoScorePreferences {
163
165
  hideEmptyLines: boolean = false;
164
166
  autoScrollPlayback: boolean = true;
165
167
  transposingScore: boolean = false;
168
+ showPartNames: boolean = false;
166
169
  static get defaults(): SmoScorePreferencesParams {
167
170
  return {
168
171
  autoPlay: true,
@@ -172,7 +175,8 @@ export class SmoScorePreferences {
172
175
  autoScrollPlayback: true,
173
176
  showPiano: false,
174
177
  hideEmptyLines: false,
175
- transposingScore: false
178
+ transposingScore: false,
179
+ showPartNames: false
176
180
  };
177
181
  }
178
182
  constructor(params: SmoScorePreferencesParams) {
@@ -184,9 +188,12 @@ export class SmoScorePreferences {
184
188
  this[nn] = params[nn];
185
189
  });
186
190
  // legacy, added later
187
- if (typeof(params.autoScrollPlayback) === 'undefined') {
191
+ if (typeof(params.autoScrollPlayback) === 'undefined') {
188
192
  this.autoScrollPlayback = true;
189
193
  }
194
+ if (typeof(params.showPartNames) === 'undefined') {
195
+ this.showPartNames = false;
196
+ }
190
197
  }
191
198
  }
192
199
  serialize(): SmoScorePreferencesParams {
@@ -6,13 +6,13 @@
6
6
  * @module /smo/data/systemStaff
7
7
  * **/
8
8
  import { SmoObjectParams, SmoAttrs, MeasureNumber, getId,
9
- ElementLike } from './common';
9
+ Pitch, ElementLike } from './common';
10
10
  import { SmoMusic } from './music';
11
11
  import { SmoMeasure, SmoMeasureParamsSer } from './measure';
12
12
  import { SmoMeasureFormat, SmoRehearsalMark, SmoRehearsalMarkParams, SmoTempoTextParams, SmoVolta, SmoBarline } from './measureModifiers';
13
13
  import { SmoInstrumentParams, StaffModifierBase, SmoInstrument, SmoInstrumentMeasure, SmoInstrumentStringParams, SmoInstrumentNumParams,
14
14
  SmoTie, SmoStaffTextBracket, SmoStaffTextBracketParamsSer,
15
- StaffModifierBaseSer, SmoTabStave, SmoTabStaveParamsSer } from './staffModifiers';
15
+ StaffModifierBaseSer, SmoTabStave, SmoTabStaveParamsSer, TieLine } from './staffModifiers';
16
16
  import { SmoPartInfo, SmoPartInfoParamsSer } from './partInfo';
17
17
  import { SmoTextGroup } from './scoreText';
18
18
  import { SmoSelector } from '../xform/selections';
@@ -26,7 +26,8 @@ import { FontInfo } from '../../common/vex';
26
26
  */
27
27
  export interface SmoStaffSerializationOptions {
28
28
  skipMaps: boolean,
29
- preserveIds: boolean
29
+ preserveIds: boolean,
30
+ transposeInstruments: boolean
30
31
  }
31
32
  /**
32
33
  * Constructor parameters for {@link SmoSystemStaff}.
@@ -295,7 +296,11 @@ export class SmoSystemStaff implements SmoObjectParams {
295
296
  params.measureInstrumentMap![parseInt(ikey, 10)] = this.measureInstrumentMap[parseInt(ikey, 10)].serialize();
296
297
  });
297
298
  this.measures.forEach((measure) => {
298
- params.measures!.push(measure.serialize());
299
+ const mp = measure.serialize();
300
+ if (!options.transposeInstruments) {
301
+ mp.transposeIndex = 0;
302
+ }
303
+ params.measures!.push(mp);
299
304
  });
300
305
  params.modifiers = [];
301
306
  this.modifiers.forEach((modifier) => {
@@ -314,7 +319,7 @@ export class SmoSystemStaff implements SmoObjectParams {
314
319
  }
315
320
  // ### deserialize
316
321
  // parse formerly serialized staff.
317
- static deserialize(jsonObj: SmoSystemStaffParamsSer): SmoSystemStaff {
322
+ static deserialize(jsonObj: SmoSystemStaffParamsSer, transposeInstruments: boolean): SmoSystemStaff {
318
323
  const params: SmoSystemStaffParams = SmoSystemStaff.defaults;
319
324
  params.staffId = jsonObj.staffId ?? 0;
320
325
  params.measures = [];
@@ -386,7 +391,9 @@ export class SmoSystemStaff implements SmoObjectParams {
386
391
  instrumentAr[curInstrumentIndex + 1].measureIndex) {
387
392
  curInstrumentIndex += 1;
388
393
  }
389
- measure.transposeIndex = instrumentAr[curInstrumentIndex].instrument.keyOffset;
394
+ if (transposeInstruments) {
395
+ measure.transposeIndex = instrumentAr[curInstrumentIndex].instrument.keyOffset;
396
+ }
390
397
  measure.lines = instrumentAr[curInstrumentIndex].instrument.lines;
391
398
  params.measures.push(measure);
392
399
  });
@@ -581,6 +588,32 @@ export class SmoSystemStaff implements SmoObjectParams {
581
588
  this.removeTabStaves(toRemove);
582
589
  this.tabStaves.push(ts);
583
590
  }
591
+ /**
592
+ * Get all the pitches that start ties to the next measure, so that their
593
+ * accidentals may be preserved
594
+ * @param selector
595
+ * @returns
596
+ */
597
+ getTiedPitchesForNextMeasure(measureIndex: number) {
598
+ const rv: Pitch[] = [];
599
+ if (measureIndex <= 0) {
600
+ return rv;
601
+ }
602
+ for (let i = 0; i < this.measures[measureIndex].voices.length; ++i) {
603
+ const voice = this.measures[measureIndex].voices[i];
604
+ const lastNote = voice.notes[voice.notes.length - 1];
605
+ const sel = SmoSelector.fromMeasure(this.measures[measureIndex]);
606
+ sel.voice = i;
607
+ sel.tick = voice.notes.length - 1;
608
+ const ties = this.getTiesStartingAt(sel);
609
+ if (ties.length) {
610
+ ties[0].lines.forEach((ll:TieLine ) => {
611
+ rv.push(lastNote.pitches[ll.from]);
612
+ });
613
+ }
614
+ }
615
+ return rv;
616
+ }
584
617
  getTabStaveForMeasure(selector: SmoSelector): SmoTabStave | undefined {
585
618
  return this.tabStaves.find((ts) =>
586
619
  SmoSelector.sameStaff(ts.startSelector, selector) && ts.startSelector.measure <= selector.measure