smoosic 1.0.21 → 1.0.23
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/README.md +1 -1
- package/build/smoosic.js +22 -33
- package/package.json +4 -4
- package/release/smoosic.js +22 -33
- package/release/styles/images/logo.png +0 -0
- package/release/styles/images/logo_small.png +0 -0
- package/src/application/exports.ts +1 -3
- package/src/render/sui/formatter.ts +2 -1
- package/src/render/sui/scoreRender.ts +18 -4
- package/src/render/sui/scoreView.ts +19 -13
- package/src/render/sui/scoreViewOperations.ts +39 -34
- 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 +21 -3
- package/src/smo/data/scoreModifiers.ts +11 -4
- package/src/smo/data/systemStaff.ts +39 -6
- package/src/smo/data/tuplet.ts +4 -0
- package/src/smo/xform/copypaste.ts +143 -49
- 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/styles/images/logo.PNG +0 -0
- package/src/styles/images/logo_small.png +0 -0
- package/src/ui/dialogs/dynamics.ts +25 -10
- package/src/ui/dialogs/preferences.ts +11 -0
- package/src/ui/menus/manager.ts +1 -3
- package/src/ui/menus/text.ts +6 -4
- package/typedoc.ts +0 -1
- package/src/styles/images/logo.png +0 -0
- package/src/ui/menus/dynamics.ts +0 -61
package/src/smo/data/tuplet.ts
CHANGED
|
@@ -347,6 +347,10 @@ export class SmoTuplet {
|
|
|
347
347
|
static deserialize(jsonObj: SmoTupletParamsSer): SmoTuplet {
|
|
348
348
|
const tupJson = SmoTuplet.defaults;
|
|
349
349
|
smoSerialize.serializedMerge(SmoTuplet.parameterArray, jsonObj, tupJson);
|
|
350
|
+
// Make the tuplet count === default if it is 0
|
|
351
|
+
if (tupJson.totalTicks === 0) {
|
|
352
|
+
tupJson.totalTicks = SmoTuplet.defaults.totalTicks;
|
|
353
|
+
}
|
|
350
354
|
// Legacy schema did not have notesOccupied, we need to calculate it.
|
|
351
355
|
if ((jsonObj as any).notes !== undefined) {
|
|
352
356
|
//todo: notesOccupied can probably be removed
|
|
@@ -11,7 +11,7 @@ import { SvgHelpers } from '../../render/sui/svgHelpers';
|
|
|
11
11
|
import { SmoScore } from '../data/score';
|
|
12
12
|
import { TickMap } from './tickMap';
|
|
13
13
|
import { SmoSystemStaff } from '../data/systemStaff';
|
|
14
|
-
import { getId } from '../data/common';
|
|
14
|
+
import { getId, Clef, Pitch } from '../data/common';
|
|
15
15
|
import {SmoUnmakeTupletActor} from "./tickDuration";
|
|
16
16
|
import { SmoTempoText, TimeSignature } from '../data/measureModifiers';
|
|
17
17
|
|
|
@@ -42,7 +42,8 @@ export interface ModifierPlacement {
|
|
|
42
42
|
* @category SmoTransform
|
|
43
43
|
*/
|
|
44
44
|
export class PasteBuffer {
|
|
45
|
-
notes: PasteNote[]
|
|
45
|
+
notes: Record<number, PasteNote[]>;
|
|
46
|
+
staffIndex: number;
|
|
46
47
|
noteIndex: number;
|
|
47
48
|
measures: SmoMeasure[];
|
|
48
49
|
measureIndex: number;
|
|
@@ -54,8 +55,9 @@ export class PasteBuffer {
|
|
|
54
55
|
destination: SmoSelector = SmoSelector.default;
|
|
55
56
|
staffSelectors: SmoSelector[] = [];
|
|
56
57
|
constructor() {
|
|
57
|
-
this.notes =
|
|
58
|
+
this.notes = {};
|
|
58
59
|
this.noteIndex = 0;
|
|
60
|
+
this.staffIndex = 0;
|
|
59
61
|
this.measures = [];
|
|
60
62
|
this.measureIndex = -1;
|
|
61
63
|
this.remainder = 0;
|
|
@@ -66,11 +68,18 @@ export class PasteBuffer {
|
|
|
66
68
|
this.score = score;
|
|
67
69
|
}
|
|
68
70
|
getCopyBufferTickCount() {
|
|
69
|
-
let
|
|
70
|
-
this.notes
|
|
71
|
-
|
|
71
|
+
let maxRv = 0;
|
|
72
|
+
const staves = Object.keys(this.notes);
|
|
73
|
+
staves.forEach((staffStr: string) => {
|
|
74
|
+
const staff: number = parseInt(staffStr);
|
|
75
|
+
const notes = this.notes[staff];
|
|
76
|
+
let rv = 0;
|
|
77
|
+
notes.forEach((note) => {
|
|
78
|
+
rv += note.note.tickCount;
|
|
79
|
+
});
|
|
80
|
+
maxRv = Math.max(rv, maxRv);
|
|
72
81
|
});
|
|
73
|
-
return
|
|
82
|
+
return maxRv;
|
|
74
83
|
}
|
|
75
84
|
setSelections(score: SmoScore, selections: SmoSelection[]) {
|
|
76
85
|
this.notes = [];
|
|
@@ -108,6 +117,7 @@ export class PasteBuffer {
|
|
|
108
117
|
let minSelector = selections[0].selector;
|
|
109
118
|
selections.forEach((selection) => {
|
|
110
119
|
selector = JSON.parse(JSON.stringify(selection.selector));
|
|
120
|
+
const staff = selector.staff;
|
|
111
121
|
if (SmoSelector.gt(selector, maxSelector)) {
|
|
112
122
|
maxSelector = selector;
|
|
113
123
|
}
|
|
@@ -151,17 +161,24 @@ export class PasteBuffer {
|
|
|
151
161
|
pasteNote.tupletStart = SmoTupletTree.clone(tupletTree);
|
|
152
162
|
}
|
|
153
163
|
}
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
if (!this.notes[staff]) {
|
|
165
|
+
this.notes[staff] = [];
|
|
166
|
+
}
|
|
167
|
+
this.notes[staff].push(pasteNote);
|
|
156
168
|
}
|
|
157
169
|
});
|
|
158
|
-
this.notes
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
const staves = Object.keys(this.notes);
|
|
171
|
+
staves.forEach((staffStr) => {
|
|
172
|
+
const staff = parseInt(staffStr);
|
|
173
|
+
const notes = this.notes[staff];
|
|
174
|
+
notes.sort((a, b) =>
|
|
175
|
+
SmoSelector.gt(a.selector, b.selector) ? 1 : -1
|
|
176
|
+
);
|
|
177
|
+
});
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
clearSelections() {
|
|
164
|
-
this.notes =
|
|
181
|
+
this.notes = {};
|
|
165
182
|
}
|
|
166
183
|
|
|
167
184
|
_findModifier(selector: SmoSelector) {
|
|
@@ -181,7 +198,7 @@ export class PasteBuffer {
|
|
|
181
198
|
|
|
182
199
|
// Before pasting, populate an array of existing measures from the paste destination
|
|
183
200
|
// so we know how to place the notes.
|
|
184
|
-
_populateMeasureArray(selector: SmoSelector) {
|
|
201
|
+
_populateMeasureArray(selector: SmoSelector, notes: PasteNote[]) {
|
|
185
202
|
let measureSelection = SmoSelection.measureSelection(this.score!, selector.staff, selector.measure);
|
|
186
203
|
if (!measureSelection) {
|
|
187
204
|
return;
|
|
@@ -204,16 +221,21 @@ export class PasteBuffer {
|
|
|
204
221
|
|
|
205
222
|
let currentDuration = tickmapForFirstMeasure.durationMap[selector.tick];
|
|
206
223
|
const measureTotalDuration = tickmapForFirstMeasure.totalDuration;
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
|
|
225
|
+
for (let i: number = 0; i < notes.length; i++) {
|
|
226
|
+
const selection: PasteNote = notes[i];
|
|
209
227
|
if (selection.tupletStart) {
|
|
210
228
|
// const tupletTree: SmoTupletTree | null = SmoTupletTree.getTupletTreeForNoteIndex(this.tupletNoteMap, selection.selector.voice, selection.selector.tick);
|
|
211
|
-
if (currentDuration + selection.tupletStart.totalTicks > measureTotalDuration &&
|
|
229
|
+
if (currentDuration + selection.tupletStart.totalTicks > measureTotalDuration &&
|
|
230
|
+
currentDuration + selection.note.tickCount < measureTotalDuration &&
|
|
231
|
+
measureSelection !== null) {
|
|
212
232
|
//if tuplet does not fit in a measure as a whole we cannot paste it, it is ether the whole thing or nothing
|
|
213
233
|
//reset everything that has been changed so far and return
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
if (measureSelection === null) {
|
|
235
|
+
this.measures = [];
|
|
236
|
+
this.staffSelectors = [];
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
217
239
|
}
|
|
218
240
|
}
|
|
219
241
|
if (currentDuration + selection.note.tickCount > measureTotalDuration && measureSelection !== null) {
|
|
@@ -222,7 +244,8 @@ export class PasteBuffer {
|
|
|
222
244
|
const remainder = (currentDuration + selection.note.tickCount) - measureTotalDuration;
|
|
223
245
|
currentDuration = remainder;
|
|
224
246
|
|
|
225
|
-
measureSelection = SmoSelection.measureSelection(this.score as SmoScore,
|
|
247
|
+
measureSelection = SmoSelection.measureSelection(this.score as SmoScore,
|
|
248
|
+
measureSelection.selector.staff,measureSelection.selector.measure + 1);
|
|
226
249
|
|
|
227
250
|
// If the paste buffer overlaps the end of the score, we can't paste (TODO: add a measure in this case)
|
|
228
251
|
if (measureSelection != null) {
|
|
@@ -235,7 +258,6 @@ export class PasteBuffer {
|
|
|
235
258
|
currentDuration += selection.note.tickCount;
|
|
236
259
|
}
|
|
237
260
|
}
|
|
238
|
-
|
|
239
261
|
const lastMeasure = this.measures[this.measures.length - 1];
|
|
240
262
|
|
|
241
263
|
//adjust the beginning of the paste
|
|
@@ -248,10 +270,13 @@ export class PasteBuffer {
|
|
|
248
270
|
}
|
|
249
271
|
|
|
250
272
|
if (this.measures.length > 1) {
|
|
251
|
-
this._removeOverlappingTuplets(firstMeasure,
|
|
252
|
-
|
|
273
|
+
this._removeOverlappingTuplets(firstMeasure,
|
|
274
|
+
selector.tick, firstMeasure.voices[selector.voice].notes.length - 1, selector.voice);
|
|
275
|
+
this._removeOverlappingTuplets(lastMeasure,
|
|
276
|
+
0, lastMeasure.getClosestIndexFromTickCount(selector.voice, currentDuration), selector.voice);
|
|
253
277
|
} else {
|
|
254
|
-
this._removeOverlappingTuplets(firstMeasure,
|
|
278
|
+
this._removeOverlappingTuplets(firstMeasure,
|
|
279
|
+
selector.tick, lastMeasure.getClosestIndexFromTickCount(selector.voice, currentDuration), selector.voice);
|
|
255
280
|
}
|
|
256
281
|
|
|
257
282
|
//if there are more than 2 measures remove tuplets from all but first and last measure.
|
|
@@ -284,7 +309,7 @@ export class PasteBuffer {
|
|
|
284
309
|
// ### _populateVoice
|
|
285
310
|
// ### Description:
|
|
286
311
|
// Create a new voice for a new measure in the paste destination
|
|
287
|
-
_populateVoice(): SmoVoice[] {
|
|
312
|
+
_populateVoice(notes: PasteNote[]): SmoVoice[] {
|
|
288
313
|
// this._populateMeasureArray();
|
|
289
314
|
const measures = this.measures;
|
|
290
315
|
let measure = measures[0];
|
|
@@ -301,8 +326,8 @@ export class PasteBuffer {
|
|
|
301
326
|
measure.voices.push(nvoice);
|
|
302
327
|
}
|
|
303
328
|
tickmap = measure.tickmapForVoice(this.destination.voice);
|
|
304
|
-
this._populateNew(voice, measure, tickmap, startSelector);
|
|
305
|
-
if (this.noteIndex <
|
|
329
|
+
this._populateNew(voice, measure, tickmap, startSelector, notes);
|
|
330
|
+
if (this.noteIndex < notes.length && this.measureIndex < measures.length) {
|
|
306
331
|
voice = {
|
|
307
332
|
notes: []
|
|
308
333
|
};
|
|
@@ -399,16 +424,16 @@ export class PasteBuffer {
|
|
|
399
424
|
* @param startSelector
|
|
400
425
|
* @returns
|
|
401
426
|
*/
|
|
402
|
-
_populateNew(voice: SmoVoice, measure: SmoMeasure, tickmap: TickMap, startSelector: SmoSelector) {
|
|
427
|
+
_populateNew(voice: SmoVoice, measure: SmoMeasure, tickmap: TickMap, startSelector: SmoSelector, notes: PasteNote[]) {
|
|
403
428
|
let currentDuration = tickmap.durationMap[startSelector.tick];
|
|
404
429
|
let i = 0;
|
|
405
430
|
let j = 0;
|
|
406
431
|
const totalDuration = tickmap.totalDuration;
|
|
407
|
-
while (currentDuration < totalDuration && this.noteIndex <
|
|
432
|
+
while (currentDuration < totalDuration && this.noteIndex < notes.length) {
|
|
408
433
|
if (!this.score) {
|
|
409
434
|
return;
|
|
410
435
|
}
|
|
411
|
-
const selection: PasteNote =
|
|
436
|
+
const selection: PasteNote = notes[this.noteIndex];
|
|
412
437
|
const note: SmoNote = selection.note;
|
|
413
438
|
if (note.noteType === 'n') {
|
|
414
439
|
const pitchAr: number[] = [];
|
|
@@ -416,6 +441,13 @@ export class PasteBuffer {
|
|
|
416
441
|
pitchAr.push(ix);
|
|
417
442
|
});
|
|
418
443
|
SmoNote.transpose(note, pitchAr, measure.transposeIndex, selection.originalKey, measure.keySignature);
|
|
444
|
+
} else {
|
|
445
|
+
const npitches: Pitch[] = [];
|
|
446
|
+
note.pitches.forEach((pp) => {
|
|
447
|
+
const npitch = SmoMeasure.defaultPitchForClef[measure.clef as Clef];
|
|
448
|
+
npitches.push(npitch);
|
|
449
|
+
note.pitches = npitches;
|
|
450
|
+
});
|
|
419
451
|
}
|
|
420
452
|
this._populateModifier(selection.selector, startSelector, this.score.staves[selection.selector.staff]);
|
|
421
453
|
|
|
@@ -499,7 +531,8 @@ export class PasteBuffer {
|
|
|
499
531
|
diffToAdjustRemainingTuplets += lmap.length;
|
|
500
532
|
existingIndex++;
|
|
501
533
|
}
|
|
502
|
-
SmoTupletTree.adjustTupletIndexes(
|
|
534
|
+
SmoTupletTree.adjustTupletIndexes(
|
|
535
|
+
measure.tupletTrees, voiceIndex, startIndexToAdjustRemainingTuplets, diffToAdjustRemainingTuplets);
|
|
503
536
|
|
|
504
537
|
for (let i = existingIndex + 1; i < measure.voices[voiceIndex].notes.length; i++) {
|
|
505
538
|
voice.notes.push(SmoNote.clone(measure.voices[voiceIndex].notes[i]));
|
|
@@ -523,19 +556,19 @@ export class PasteBuffer {
|
|
|
523
556
|
}
|
|
524
557
|
serializedMeasure.voices = voices;
|
|
525
558
|
}
|
|
526
|
-
|
|
527
|
-
if (
|
|
528
|
-
return;
|
|
559
|
+
pasteChordNotes(selector: SmoSelector, notes: PasteNote[]): PasteNote[] {
|
|
560
|
+
if (notes.length < 1) {
|
|
561
|
+
return [];
|
|
529
562
|
}
|
|
530
563
|
if (!this.score) {
|
|
531
|
-
return;
|
|
564
|
+
return [];
|
|
532
565
|
}
|
|
533
566
|
let srcTick = 0;
|
|
534
567
|
let destTick = 0;
|
|
535
568
|
let srcIndex = 0;
|
|
536
569
|
let selection = SmoSelection.noteSelection(this.score!, selector.staff, selector.measure, selector.voice, selector.tick);
|
|
537
|
-
while (selection && selection.note && srcIndex <
|
|
538
|
-
const srcNote =
|
|
570
|
+
while (selection && selection.note && srcIndex < notes.length) {
|
|
571
|
+
const srcNote = notes[srcIndex].note;
|
|
539
572
|
const chords = srcNote.getChords();
|
|
540
573
|
if (selection && selection.note) {
|
|
541
574
|
const destNote = selection.note;
|
|
@@ -546,7 +579,7 @@ export class PasteBuffer {
|
|
|
546
579
|
chords.forEach((chord) => {
|
|
547
580
|
if (selection) {
|
|
548
581
|
const nchord = SmoLyric.transposeChordToKey(
|
|
549
|
-
chord, selection.measure.transposeIndex,
|
|
582
|
+
chord, selection.measure.transposeIndex,notes[srcIndex].originalKey, selection.measure.keySignature);
|
|
550
583
|
destNote.addLyric(nchord);
|
|
551
584
|
}
|
|
552
585
|
});
|
|
@@ -563,19 +596,80 @@ export class PasteBuffer {
|
|
|
563
596
|
srcIndex += 1;
|
|
564
597
|
}
|
|
565
598
|
}
|
|
599
|
+
return notes;
|
|
566
600
|
}
|
|
567
|
-
|
|
568
|
-
let
|
|
569
|
-
|
|
601
|
+
pasteIntoScore(score: SmoScore, selector: SmoSelector, func:(selector: SmoSelector, notes: PasteNote[]) => PasteNote[]) {
|
|
602
|
+
let lowest: SmoSelector = SmoSelector.default;
|
|
603
|
+
const enforceRect = true;
|
|
604
|
+
let pasted = 0;
|
|
605
|
+
let startMeasure = 0;
|
|
606
|
+
let startTick = 0;
|
|
607
|
+
let startStaff = 0;
|
|
608
|
+
for (let j = 0; j < score?.staves.length; ++j ) {
|
|
609
|
+
if (!this.notes[j]) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const notes = this.notes[j];
|
|
613
|
+
if (pasted === 0) {
|
|
614
|
+
lowest.measure = selector.measure;
|
|
615
|
+
lowest.tick = selector.tick;
|
|
616
|
+
lowest.staff = selector.staff;
|
|
617
|
+
startMeasure = notes[0].selector.measure;
|
|
618
|
+
startTick = notes[0].selector.tick;
|
|
619
|
+
startStaff = notes[0].selector.staff;
|
|
620
|
+
} else {
|
|
621
|
+
if (enforceRect) {
|
|
622
|
+
if (notes[0].selector.measure !== startMeasure || notes[0].selector.tick !== startTick) {
|
|
623
|
+
// can only paste rectangles. Maybe warn?
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (notes[0].selector.staff !== startStaff + 1) {
|
|
627
|
+
// can only paste rectangles, not clear where next selection goes
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (score.staves.length <= lowest.staff + 1) {
|
|
632
|
+
// nowhere to paste this.
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
lowest.staff += 1;
|
|
636
|
+
startStaff += 1;
|
|
637
|
+
}
|
|
638
|
+
pasted += 1;
|
|
639
|
+
// Replace with fresh copy of the pasted notes
|
|
640
|
+
this.notes[j] = func(lowest, notes);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
pasteChords(selector: SmoSelector) {
|
|
644
|
+
if (!this.score) {
|
|
570
645
|
return;
|
|
571
646
|
}
|
|
647
|
+
const func = (selector: SmoSelector, notes: PasteNote[]) => {
|
|
648
|
+
return this.pasteChordNotes(selector, notes);
|
|
649
|
+
}
|
|
650
|
+
this.pasteIntoScore(this.score, selector, func);
|
|
651
|
+
}
|
|
652
|
+
pasteSelections(selector: SmoSelector) {
|
|
572
653
|
if (!this.score) {
|
|
573
654
|
return;
|
|
574
655
|
}
|
|
575
|
-
const
|
|
576
|
-
|
|
656
|
+
const func = (selector: SmoSelector, notes: PasteNote[]) => {
|
|
657
|
+
return this.pasteNoteSelections(selector, notes);
|
|
658
|
+
}
|
|
659
|
+
this.pasteIntoScore(this.score, selector, func);
|
|
660
|
+
}
|
|
661
|
+
pasteNoteSelections(selector: SmoSelector, notes: PasteNote[]): PasteNote[] {
|
|
662
|
+
let i = 0;
|
|
663
|
+
if (notes.length < 1) {
|
|
664
|
+
return notes;
|
|
665
|
+
}
|
|
666
|
+
if (!this.score) {
|
|
667
|
+
return notes;
|
|
668
|
+
}
|
|
669
|
+
const maxCutVoice = notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
|
|
670
|
+
const minCutVoice = notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
|
|
577
671
|
const backupNotes: PasteNote[] = [];
|
|
578
|
-
|
|
672
|
+
notes.forEach((bb) => {
|
|
579
673
|
const note = (SmoNote.deserialize(bb.note.serialize()));
|
|
580
674
|
const selector = JSON.parse(JSON.stringify(bb.selector));
|
|
581
675
|
let tupletStart = bb.tupletStart;
|
|
@@ -591,12 +685,12 @@ export class PasteBuffer {
|
|
|
591
685
|
this.noteIndex = 0;
|
|
592
686
|
this.measureIndex = -1;
|
|
593
687
|
this.remainder = 0;
|
|
594
|
-
this._populateMeasureArray(selector);
|
|
688
|
+
this._populateMeasureArray(selector, notes);
|
|
595
689
|
if (this.measures.length === 0) {
|
|
596
|
-
return;
|
|
690
|
+
return notes;
|
|
597
691
|
}
|
|
598
692
|
|
|
599
|
-
const voices = this._populateVoice();
|
|
693
|
+
const voices = this._populateVoice(notes);
|
|
600
694
|
const measureSel = JSON.parse(JSON.stringify(this.destination));
|
|
601
695
|
const selectors: SmoSelector[] = [];
|
|
602
696
|
for (i = 0; i < this.measures.length && i < voices.length; ++i) {
|
|
@@ -659,6 +753,6 @@ export class PasteBuffer {
|
|
|
659
753
|
selection.staff.addStaffModifier(mod.modifier);
|
|
660
754
|
}
|
|
661
755
|
});
|
|
662
|
-
|
|
756
|
+
return(backupNotes);
|
|
663
757
|
}
|
|
664
758
|
}
|
|
@@ -307,6 +307,10 @@ export class SmoStretchNoteActor extends TickIteratorBase {
|
|
|
307
307
|
|
|
308
308
|
static apply(params: SmoStretchNoteParams) {
|
|
309
309
|
const actor = new SmoStretchNoteActor(params);
|
|
310
|
+
// The duration would extend over the barline.
|
|
311
|
+
if (actor.notesToInsert.length < 1) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
310
314
|
SmoTickIterator.iterateOverTicks(actor.measure, actor, actor.voice);
|
|
311
315
|
}
|
|
312
316
|
iterateOverTick(note: SmoNote, tickmap: TickMap, index: number) {
|
package/src/smo/xform/tickMap.ts
CHANGED
|
@@ -20,6 +20,7 @@ export class TickMap {
|
|
|
20
20
|
keySignature: string;
|
|
21
21
|
voice: number;
|
|
22
22
|
notes: SmoNote[] = [];
|
|
23
|
+
priorAccidentals: TickAccidental[] = [];
|
|
23
24
|
index: number = 0;
|
|
24
25
|
startIndex: number = 0;
|
|
25
26
|
endIndex: number = 0;
|
|
@@ -107,34 +108,6 @@ export class TickMap {
|
|
|
107
108
|
this.durationAccidentalMap[this.durationMap[this.index]] = newObj;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
// ### getActiveAccidental
|
|
111
|
-
// return the active accidental for the given note
|
|
112
|
-
getActiveAccidental(pitch: Pitch, iteratorIndex: number, keySignature: string) {
|
|
113
|
-
let defaultAccidental: string = SmoMusic.getKeySignatureKey(pitch.letter, keySignature);
|
|
114
|
-
let i = 0;
|
|
115
|
-
let j = 0;
|
|
116
|
-
defaultAccidental = defaultAccidental.length > 1 ? defaultAccidental[1] : 'n';
|
|
117
|
-
if (iteratorIndex === 0) {
|
|
118
|
-
return defaultAccidental;
|
|
119
|
-
}
|
|
120
|
-
// Back up the accidental map until we have a match, or until we run out
|
|
121
|
-
for (i = iteratorIndex; i > 0; --i) {
|
|
122
|
-
const map: Record<string, TickAccidental> = this.accidentalMap[i - 1];
|
|
123
|
-
const mapKeys = Object.keys(map);
|
|
124
|
-
for (j = 0; j < mapKeys.length; ++j) {
|
|
125
|
-
const mapKey: string = mapKeys[j];
|
|
126
|
-
// The letter name + accidental in the map
|
|
127
|
-
const mapPitch: Pitch = map[mapKey].pitch;
|
|
128
|
-
const mapAcc = mapPitch.accidental ? mapPitch.accidental : 'n';
|
|
129
|
-
|
|
130
|
-
// if the letters match and the accidental...
|
|
131
|
-
if (mapPitch.letter.toLowerCase() === pitch.letter) {
|
|
132
|
-
return mapAcc;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return defaultAccidental;
|
|
137
|
-
}
|
|
138
111
|
get duration() {
|
|
139
112
|
return this.totalDuration;
|
|
140
113
|
}
|
package/src/smo/xform/undo.ts
CHANGED
|
@@ -426,7 +426,7 @@ export class UndoBuffer {
|
|
|
426
426
|
} else {
|
|
427
427
|
if (typeof(staffMap[buf.selector.staff]) === 'number') {
|
|
428
428
|
buf.selector.staff = staffMap[buf.selector.staff];
|
|
429
|
-
const staff = SmoSystemStaff.deserialize(buf.json);
|
|
429
|
+
const staff = SmoSystemStaff.deserialize(buf.json, !score.preferences.transposingScore);
|
|
430
430
|
score.replaceStaff(buf.selector.staff, staff);
|
|
431
431
|
}
|
|
432
432
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -12,19 +12,25 @@ import { PromiseHelpers } from '../../common/promiseHelpers';
|
|
|
12
12
|
export class SuiDynamicDialogAdapter extends SuiComponentAdapter {
|
|
13
13
|
modifier: SmoDynamicText;
|
|
14
14
|
backup: SmoDynamicText;
|
|
15
|
-
|
|
15
|
+
selections: SmoSelection[] = [];
|
|
16
|
+
changed: boolean = false;
|
|
16
17
|
constructor(view: SuiScoreViewOperations, modifier: SmoDynamicText) {
|
|
17
18
|
super(view);
|
|
18
19
|
this.modifier = modifier;
|
|
19
20
|
this.backup = new SmoDynamicText(this.modifier);
|
|
21
|
+
this.view.undoTrackerMeasureSelections('Dynamics');
|
|
20
22
|
if (this.view.tracker.modifierSelections.length) {
|
|
21
|
-
this.
|
|
23
|
+
this.view.tracker.modifierSelections.forEach((ms) => {
|
|
24
|
+
if (ms.selection) {
|
|
25
|
+
this.selections.push(ms.selection);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
22
28
|
} else {
|
|
23
|
-
this.
|
|
29
|
+
this.selections = this.view.tracker.selections;
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
async cancel() {
|
|
27
|
-
|
|
33
|
+
this.view.undo();
|
|
28
34
|
}
|
|
29
35
|
async commit() {
|
|
30
36
|
return PromiseHelpers.emptyPromise();
|
|
@@ -33,39 +39,48 @@ export class SuiDynamicDialogAdapter extends SuiComponentAdapter {
|
|
|
33
39
|
return this.modifier.xOffset;
|
|
34
40
|
}
|
|
35
41
|
async remove() {
|
|
36
|
-
|
|
42
|
+
for (let i = 0;i < this.selections.length; ++i) {
|
|
43
|
+
await this.view.removeDynamic(this.modifier);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
syncModifiers() {
|
|
47
|
+
for (let i = 0;i < this.selections.length; ++i) {
|
|
48
|
+
this.view.addDynamic(this.selections[i], this.modifier);
|
|
49
|
+
}
|
|
37
50
|
}
|
|
38
51
|
set xOffset(value: number) {
|
|
39
52
|
this.modifier.xOffset = value;
|
|
40
|
-
this.
|
|
53
|
+
for (let i = 0;i < this.selections.length; ++i) {
|
|
54
|
+
this.view.addDynamic(this.selections[i], this.modifier);
|
|
55
|
+
}
|
|
41
56
|
}
|
|
42
57
|
get fontSize() {
|
|
43
58
|
return this.modifier.fontSize;
|
|
44
59
|
}
|
|
45
60
|
set fontSize(value: number) {
|
|
46
61
|
this.modifier.fontSize = value;
|
|
47
|
-
this.
|
|
62
|
+
this.syncModifiers();
|
|
48
63
|
}
|
|
49
64
|
get yOffsetLine() {
|
|
50
65
|
return this.modifier.yOffsetLine;
|
|
51
66
|
}
|
|
52
67
|
set yOffsetLine(value: number) {
|
|
53
68
|
this.modifier.yOffsetLine = value;
|
|
54
|
-
this.
|
|
69
|
+
this.syncModifiers();
|
|
55
70
|
}
|
|
56
71
|
get yOffsetPixels() {
|
|
57
72
|
return this.modifier.yOffsetPixels;
|
|
58
73
|
}
|
|
59
74
|
set yOffsetPixels(value: number) {
|
|
60
75
|
this.modifier.yOffsetPixels = value;
|
|
61
|
-
this.
|
|
76
|
+
this.syncModifiers();
|
|
62
77
|
}
|
|
63
78
|
get text() {
|
|
64
79
|
return this.modifier.text;
|
|
65
80
|
}
|
|
66
81
|
set text(value: string) {
|
|
67
82
|
this.modifier.text = value;
|
|
68
|
-
this.
|
|
83
|
+
this.syncModifiers();
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
/**
|
|
@@ -79,6 +79,13 @@ export class SuiScorePreferencesAdapter extends SuiComponentAdapter {
|
|
|
79
79
|
this.preferences.transposingScore = value;
|
|
80
80
|
this.view.updateScorePreferences(this.preferences);
|
|
81
81
|
}
|
|
82
|
+
get showPartNames() {
|
|
83
|
+
return this.preferences.showPartNames;
|
|
84
|
+
}
|
|
85
|
+
set showPartNames(value: boolean) {
|
|
86
|
+
this.preferences.showPartNames = value;
|
|
87
|
+
this.view.updateScorePreferences(this.preferences);
|
|
88
|
+
}
|
|
82
89
|
async cancel() {
|
|
83
90
|
const p1 = JSON.stringify(this.preferences);
|
|
84
91
|
const p2 = JSON.stringify(this.backup);
|
|
@@ -125,6 +132,10 @@ export class SuiScorePreferencesDialog extends SuiDialogAdapterBase<SuiScorePref
|
|
|
125
132
|
smoName: 'hideEmptyLines',
|
|
126
133
|
control: 'SuiToggleComponent',
|
|
127
134
|
label: 'Hide Empty Lines'
|
|
135
|
+
}, {
|
|
136
|
+
smoName: 'showPartNames',
|
|
137
|
+
control: 'SuiToggleComponent',
|
|
138
|
+
label: 'Show Part Names in Score'
|
|
128
139
|
}, {
|
|
129
140
|
smoName: 'defaultDupleDuration',
|
|
130
141
|
control: 'SuiDropdownComponent',
|
package/src/ui/menus/manager.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { buildDom, createTopDomContainer } from '../../common/htmlHelpers';
|
|
4
4
|
import { SvgBox } from '../../smo/data/common';
|
|
5
5
|
import { UndoBuffer } from '../../smo/xform/undo';
|
|
6
|
-
import { SuiDynamicsMenu } from './dynamics';
|
|
7
6
|
import { SuiBeamMenu, SuiBeamMenuOptions } from './beams';
|
|
8
7
|
import { layoutDebug } from '../../render/sui/layoutDebug';
|
|
9
8
|
import { SuiScoreViewOperations } from '../../render/sui/scoreViewOperations';
|
|
@@ -366,8 +365,7 @@ export class SuiMenuManager {
|
|
|
366
365
|
});
|
|
367
366
|
}
|
|
368
367
|
}
|
|
369
|
-
export const menuTranslationsInit = () => {
|
|
370
|
-
MenuTranslations.push(suiMenuTranslation(SuiDynamicsMenu.defaults, 'SuiDynamicsMenu'));
|
|
368
|
+
export const menuTranslationsInit = () => {
|
|
371
369
|
MenuTranslations.push(suiConfiguredMenuTranslate(SuiBeamMenuOptions, 'Beam', 'SuiBeamMenu'));
|
|
372
370
|
}
|
|
373
371
|
|
package/src/ui/menus/text.ts
CHANGED
|
@@ -119,16 +119,18 @@ const lyricsDialogMenuOption: SuiConfiguredMenuOption = {
|
|
|
119
119
|
*/
|
|
120
120
|
const dynamicsDialogMenuOption: SuiConfiguredMenuOption = {
|
|
121
121
|
handler: async (menu: SuiMenuBase) => {
|
|
122
|
-
const sel = menu.view.tracker.selections
|
|
122
|
+
const sel = menu.view.tracker.selections;
|
|
123
123
|
let modifier = null;
|
|
124
|
-
if (sel.note) {
|
|
125
|
-
const dynamics = sel.note.getModifiers('SmoDynamicText');
|
|
124
|
+
if (sel[0].note) {
|
|
125
|
+
const dynamics = sel[0].note.getModifiers('SmoDynamicText');
|
|
126
126
|
if (dynamics.length) {
|
|
127
127
|
modifier = dynamics[0];
|
|
128
128
|
} else {
|
|
129
129
|
const params = SmoDynamicText.defaults;
|
|
130
130
|
modifier = new SmoDynamicText(params);
|
|
131
|
-
|
|
131
|
+
for (let i = 0; i < sel.length; ++i) {
|
|
132
|
+
await menu.view.addDynamic(sel[i], modifier);
|
|
133
|
+
}
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
createAndDisplayDialog(SuiDynamicModifierDialog, {
|
package/typedoc.ts
CHANGED
|
@@ -141,7 +141,6 @@ export * from './src/ui/i18n/translationEditor';
|
|
|
141
141
|
export * from './src/ui/keyBindings/default/editorKeys';
|
|
142
142
|
export * from './src/ui/keyBindings/default/trackerKeys';
|
|
143
143
|
export * from './src/ui/menus/beams';
|
|
144
|
-
export * from './src/ui/menus/dynamics';
|
|
145
144
|
export * from './src/ui/menus/file';
|
|
146
145
|
export * from './src/ui/menus/keySignature';
|
|
147
146
|
export * from './src/ui/menus/language';
|
|
Binary file
|
package/src/ui/menus/dynamics.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { SmoDynamicText } from '../../smo/data/noteModifiers';
|
|
2
|
-
import { SuiMenuBase, SuiMenuParams, MenuDefinition } from './menu';
|
|
3
|
-
declare var $: any;
|
|
4
|
-
/**
|
|
5
|
-
* @category SuiMenu
|
|
6
|
-
*/
|
|
7
|
-
export class SuiDynamicsMenu extends SuiMenuBase {
|
|
8
|
-
constructor(params: SuiMenuParams) {
|
|
9
|
-
super(params);
|
|
10
|
-
}
|
|
11
|
-
static defaults: MenuDefinition = {
|
|
12
|
-
label: 'Dynamics',
|
|
13
|
-
menuItems: [{
|
|
14
|
-
icon: 'pianissimo',
|
|
15
|
-
text: 'Pianissimo',
|
|
16
|
-
value: 'pp'
|
|
17
|
-
}, {
|
|
18
|
-
icon: 'piano',
|
|
19
|
-
text: 'Piano',
|
|
20
|
-
value: 'p'
|
|
21
|
-
}, {
|
|
22
|
-
icon: 'mezzopiano',
|
|
23
|
-
text: 'Mezzo-piano',
|
|
24
|
-
value: 'mp'
|
|
25
|
-
}, {
|
|
26
|
-
icon: 'mezzoforte',
|
|
27
|
-
text: 'Mezzo-forte',
|
|
28
|
-
value: 'mf'
|
|
29
|
-
}, {
|
|
30
|
-
icon: 'forte',
|
|
31
|
-
text: 'Forte',
|
|
32
|
-
value: 'f'
|
|
33
|
-
}, {
|
|
34
|
-
icon: 'fortissimo',
|
|
35
|
-
text: 'Fortissimo',
|
|
36
|
-
value: 'ff'
|
|
37
|
-
}, {
|
|
38
|
-
icon: 'sfz',
|
|
39
|
-
text: 'sfortzando',
|
|
40
|
-
value: 'sfz'
|
|
41
|
-
}, {
|
|
42
|
-
icon: '',
|
|
43
|
-
text: 'Cancel',
|
|
44
|
-
value: 'cancel'
|
|
45
|
-
}]
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
getDefinition() {
|
|
49
|
-
return SuiDynamicsMenu.defaults;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async selection(ev: any) {
|
|
53
|
-
const text: string = $(ev.currentTarget).attr('data-value');
|
|
54
|
-
const props = SmoDynamicText.defaults;
|
|
55
|
-
props.text = text;
|
|
56
|
-
const dynamic = new SmoDynamicText(props);
|
|
57
|
-
await this.view.addDynamic(this.tracker.selections[0], dynamic);
|
|
58
|
-
this.complete();
|
|
59
|
-
}
|
|
60
|
-
keydown() { }
|
|
61
|
-
}
|