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.
@@ -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 rv = 0;
70
- this.notes.forEach((note) => {
71
- rv += note.note.tickCount;
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 rv;
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
- this.notes.push(pasteNote);
164
+ if (!this.notes[staff]) {
165
+ this.notes[staff] = [];
166
+ }
167
+ this.notes[staff].push(pasteNote);
156
168
  }
157
169
  });
158
- this.notes.sort((a, b) =>
159
- SmoSelector.gt(a.selector, b.selector) ? 1 : -1
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
- for (let i: number = 0; i < this.notes.length; i++) {
208
- const selection: PasteNote = this.notes[i];
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 && measureSelection !== null) {
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
- this.measures = [];
215
- this.staffSelectors = [];
216
- return;
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, measureSelection.selector.staff,measureSelection.selector.measure + 1);
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, selector.tick, firstMeasure.voices[selector.voice].notes.length - 1, selector.voice);
252
- this._removeOverlappingTuplets(lastMeasure, 0, lastMeasure.getClosestIndexFromTickCount(selector.voice, currentDuration), selector.voice);
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, selector.tick, lastMeasure.getClosestIndexFromTickCount(selector.voice, currentDuration), selector.voice);
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 < this.notes.length && this.measureIndex < measures.length) {
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 < this.notes.length) {
432
+ while (currentDuration < totalDuration && this.noteIndex < notes.length) {
408
433
  if (!this.score) {
409
434
  return;
410
435
  }
411
- const selection: PasteNote = this.notes[this.noteIndex];
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[] = [];
@@ -499,7 +524,8 @@ export class PasteBuffer {
499
524
  diffToAdjustRemainingTuplets += lmap.length;
500
525
  existingIndex++;
501
526
  }
502
- SmoTupletTree.adjustTupletIndexes(measure.tupletTrees, voiceIndex, startIndexToAdjustRemainingTuplets, diffToAdjustRemainingTuplets);
527
+ SmoTupletTree.adjustTupletIndexes(
528
+ measure.tupletTrees, voiceIndex, startIndexToAdjustRemainingTuplets, diffToAdjustRemainingTuplets);
503
529
 
504
530
  for (let i = existingIndex + 1; i < measure.voices[voiceIndex].notes.length; i++) {
505
531
  voice.notes.push(SmoNote.clone(measure.voices[voiceIndex].notes[i]));
@@ -523,19 +549,19 @@ export class PasteBuffer {
523
549
  }
524
550
  serializedMeasure.voices = voices;
525
551
  }
526
- pasteChords(selector: SmoSelector) {
527
- if (this.notes.length < 1) {
528
- return;
552
+ pasteChordNotes(selector: SmoSelector, notes: PasteNote[]): PasteNote[] {
553
+ if (notes.length < 1) {
554
+ return [];
529
555
  }
530
556
  if (!this.score) {
531
- return;
557
+ return [];
532
558
  }
533
559
  let srcTick = 0;
534
560
  let destTick = 0;
535
561
  let srcIndex = 0;
536
562
  let selection = SmoSelection.noteSelection(this.score!, selector.staff, selector.measure, selector.voice, selector.tick);
537
- while (selection && selection.note && srcIndex < this.notes.length) {
538
- const srcNote = this.notes[srcIndex].note;
563
+ while (selection && selection.note && srcIndex < notes.length) {
564
+ const srcNote = notes[srcIndex].note;
539
565
  const chords = srcNote.getChords();
540
566
  if (selection && selection.note) {
541
567
  const destNote = selection.note;
@@ -546,7 +572,7 @@ export class PasteBuffer {
546
572
  chords.forEach((chord) => {
547
573
  if (selection) {
548
574
  const nchord = SmoLyric.transposeChordToKey(
549
- chord, selection.measure.transposeIndex,this.notes[srcIndex].originalKey, selection.measure.keySignature);
575
+ chord, selection.measure.transposeIndex,notes[srcIndex].originalKey, selection.measure.keySignature);
550
576
  destNote.addLyric(nchord);
551
577
  }
552
578
  });
@@ -563,19 +589,80 @@ export class PasteBuffer {
563
589
  srcIndex += 1;
564
590
  }
565
591
  }
592
+ return notes;
566
593
  }
567
- pasteSelections(selector: SmoSelector) {
568
- let i = 0;
569
- if (this.notes.length < 1) {
594
+ pasteIntoScore(score: SmoScore, selector: SmoSelector, func:(selector: SmoSelector, notes: PasteNote[]) => PasteNote[]) {
595
+ let lowest: SmoSelector = SmoSelector.default;
596
+ const enforceRect = true;
597
+ let pasted = 0;
598
+ let startMeasure = 0;
599
+ let startTick = 0;
600
+ let startStaff = 0;
601
+ for (let j = 0; j < score?.staves.length; ++j ) {
602
+ if (!this.notes[j]) {
603
+ continue;
604
+ }
605
+ const notes = this.notes[j];
606
+ if (pasted === 0) {
607
+ lowest.measure = selector.measure;
608
+ lowest.tick = selector.tick;
609
+ lowest.staff = selector.staff;
610
+ startMeasure = notes[0].selector.measure;
611
+ startTick = notes[0].selector.tick;
612
+ startStaff = notes[0].selector.staff;
613
+ } else {
614
+ if (enforceRect) {
615
+ if (notes[0].selector.measure !== startMeasure || notes[0].selector.tick !== startTick) {
616
+ // can only paste rectangles. Maybe warn?
617
+ return;
618
+ }
619
+ if (notes[0].selector.staff !== startStaff + 1) {
620
+ // can only paste rectangles, not clear where next selection goes
621
+ return;
622
+ }
623
+ }
624
+ if (score.staves.length <= lowest.staff + 1) {
625
+ // nowhere to paste this.
626
+ return;
627
+ }
628
+ lowest.staff += 1;
629
+ startStaff += 1;
630
+ }
631
+ pasted += 1;
632
+ // Replace with fresh copy of the pasted notes
633
+ this.notes[j] = func(lowest, notes);
634
+ }
635
+ }
636
+ pasteChords(selector: SmoSelector) {
637
+ if (!this.score) {
570
638
  return;
571
639
  }
640
+ const func = (selector: SmoSelector, notes: PasteNote[]) => {
641
+ return this.pasteChordNotes(selector, notes);
642
+ }
643
+ this.pasteIntoScore(this.score, selector, func);
644
+ }
645
+ pasteSelections(selector: SmoSelector) {
572
646
  if (!this.score) {
573
647
  return;
574
648
  }
575
- const maxCutVoice = this.notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
576
- const minCutVoice = this.notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
649
+ const func = (selector: SmoSelector, notes: PasteNote[]) => {
650
+ return this.pasteNoteSelections(selector, notes);
651
+ }
652
+ this.pasteIntoScore(this.score, selector, func);
653
+ }
654
+ pasteNoteSelections(selector: SmoSelector, notes: PasteNote[]): PasteNote[] {
655
+ let i = 0;
656
+ if (notes.length < 1) {
657
+ return notes;
658
+ }
659
+ if (!this.score) {
660
+ return notes;
661
+ }
662
+ const maxCutVoice = notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
663
+ const minCutVoice = notes.map((n) => n.selector.voice).reduce((a, b) => a > b ? a : b);
577
664
  const backupNotes: PasteNote[] = [];
578
- this.notes.forEach((bb) => {
665
+ notes.forEach((bb) => {
579
666
  const note = (SmoNote.deserialize(bb.note.serialize()));
580
667
  const selector = JSON.parse(JSON.stringify(bb.selector));
581
668
  let tupletStart = bb.tupletStart;
@@ -591,12 +678,12 @@ export class PasteBuffer {
591
678
  this.noteIndex = 0;
592
679
  this.measureIndex = -1;
593
680
  this.remainder = 0;
594
- this._populateMeasureArray(selector);
681
+ this._populateMeasureArray(selector, notes);
595
682
  if (this.measures.length === 0) {
596
- return;
683
+ return notes;
597
684
  }
598
685
 
599
- const voices = this._populateVoice();
686
+ const voices = this._populateVoice(notes);
600
687
  const measureSel = JSON.parse(JSON.stringify(this.destination));
601
688
  const selectors: SmoSelector[] = [];
602
689
  for (i = 0; i < this.measures.length && i < voices.length; ++i) {
@@ -659,6 +746,6 @@ export class PasteBuffer {
659
746
  selection.staff.addStaffModifier(mod.modifier);
660
747
  }
661
748
  });
662
- this.notes = backupNotes;
749
+ return(backupNotes);
663
750
  }
664
751
  }
@@ -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) {
@@ -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
  }
@@ -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
  }
@@ -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',