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.
- package/build/smoosic.js +17 -17
- package/package.json +4 -4
- package/release/smoosic.js +17 -17
- package/src/render/sui/formatter.ts +2 -1
- package/src/render/sui/scoreRender.ts +16 -0
- package/src/render/sui/scoreView.ts +11 -13
- package/src/render/sui/scoreViewOperations.ts +2 -0
- package/src/render/sui/tracker.ts +34 -29
- package/src/render/vex/vxMeasure.ts +7 -2
- package/src/render/vex/vxNote.ts +16 -1
- package/src/render/vex/vxSystem.ts +2 -1
- package/src/smo/data/music.ts +1 -1
- package/src/smo/data/score.ts +8 -3
- package/src/smo/data/scoreModifiers.ts +11 -4
- package/src/smo/data/systemStaff.ts +39 -6
- package/src/smo/xform/copypaste.ts +135 -48
- package/src/smo/xform/tickDuration.ts +4 -0
- package/src/smo/xform/tickMap.ts +1 -28
- package/src/smo/xform/undo.ts +1 -1
- package/src/ui/dialogs/preferences.ts +11 -0
|
@@ -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
|
-
*
|
|
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 (
|
|
682
|
+
if (this.isPartExposed()) {
|
|
680
683
|
const xpose = this.score.preferences?.transposingScore;
|
|
681
684
|
if (xpose) {
|
|
682
|
-
this.score.
|
|
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
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
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
|
-
|
|
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
|
|
609
|
-
const
|
|
610
|
-
this.
|
|
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
|
-
|
|
639
|
-
|
|
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,
|
|
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) {
|
package/src/render/vex/vxNote.ts
CHANGED
|
@@ -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
|
|
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();
|
package/src/smo/data/music.ts
CHANGED
|
@@ -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: '
|
|
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'},
|
package/src/smo/data/score.ts
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
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
|