smoosic 1.0.12 → 1.0.15

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.
Files changed (133) hide show
  1. package/README.md +1 -0
  2. package/build/smoosic.js +34 -23
  3. package/package.json +1 -1
  4. package/release/smoosic.js +34 -23
  5. package/src/application/configuration.ts +1 -1
  6. package/src/application/exports.ts +4 -3
  7. package/src/common/vex.ts +11 -0
  8. package/src/render/audio/musicCursor.ts +3 -1
  9. package/src/render/audio/samples.ts +2 -2
  10. package/src/render/sui/mapper.ts +10 -4
  11. package/src/render/sui/scoreRender.ts +5 -1
  12. package/src/render/sui/scoreViewOperations.ts +26 -0
  13. package/src/render/vex/vxMeasure.ts +1 -0
  14. package/src/smo/data/measure.ts +1 -0
  15. package/src/smo/data/noteModifiers.ts +85 -1
  16. package/src/smo/data/score.ts +10 -0
  17. package/src/smo/data/scoreModifiers.ts +11 -3
  18. package/src/smo/data/staffModifiers.ts +14 -5
  19. package/src/smo/data/systemStaff.ts +11 -0
  20. package/src/smo/xform/copypaste.ts +50 -1
  21. package/src/smo/xform/operations.ts +15 -0
  22. package/src/ui/dialogs/endings.ts +1 -1
  23. package/src/ui/dialogs/instrument.ts +11 -1
  24. package/src/ui/dialogs/preferences.ts +11 -0
  25. package/src/ui/keyBindings/default/trackerKeys.ts +1 -1
  26. package/src/ui/menus/edit.ts +79 -0
  27. package/src/ui/menus/manager.ts +3 -0
  28. package/src/ui/ribbonLayout/default/defaultRibbon.ts +12 -2
  29. package/release/library/BachCantataBMV68.json +0 -1
  30. package/release/library/BachCantataBMV682.json +0 -1
  31. package/release/library/Beethoven_AnDieFerneGeliebte.xml +0 -4271
  32. package/release/library/Dichterliebe01.xml +0 -7613
  33. package/release/library/MozartTrio.xml +0 -3498
  34. package/release/library/Mozart_AnChloe.xml +0 -3986
  35. package/release/library/Postillionlied.json +0 -1
  36. package/release/library/ScottJoplin_The_Entertainer.xml +0 -20741
  37. package/release/library/Yama2.json +0 -1
  38. package/release/library/bigband/Bilongo-Mandinga.json +0 -1
  39. package/release/library/bigband/Plena.json +0 -1
  40. package/release/library/bigband/Poinciana.json +0 -1
  41. package/release/library/bigband/child who drums.json +0 -1
  42. package/release/library/cp/Another Hairdo.mxl +0 -0
  43. package/release/library/cp/Anthropology Eb.mxl +0 -0
  44. package/release/library/cp/Au Privave No 1 Eb.mxl +0 -0
  45. package/release/library/cp/Au Privave No 2 Eb.mxl +0 -0
  46. package/release/library/cp/Back Home Blues.mxl +0 -0
  47. package/release/library/cp/Ballade.mxl +0 -0
  48. package/release/library/cp/Billies Bounce.mxl +0 -0
  49. package/release/library/cp/Bloomdido.mxl +0 -0
  50. package/release/library/cp/Blues for Alice.mxl +0 -0
  51. package/release/library/cp/Celerity Alto.mxl +0 -0
  52. package/release/library/cp/Celerity.mxl +0 -0
  53. package/release/library/cp/Cheryl F07.mxl +0 -0
  54. package/release/library/cp/Cheryl.mxl +0 -0
  55. package/release/library/cp/Confirmation Eb 09.mxl +0 -0
  56. package/release/library/cp/Cool Blues Alto1.mxl +0 -0
  57. package/release/library/cp/Dewey Square.mxl +0 -0
  58. package/release/library/cp/Donna Lee Eb.musicxml +0 -9134
  59. package/release/library/cp/Donna Lee Eb.mxl +0 -0
  60. package/release/library/cp/Ko Ko Eb.mxl +0 -0
  61. package/release/library/cp/Little Benny.mxl +0 -0
  62. package/release/library/cp/Moose the Mooche Eb.mxl +0 -0
  63. package/release/library/cp/Night in Tunisia solo.mxl +0 -0
  64. package/release/library/cp/Nows the Time No 1.mxl +0 -0
  65. package/release/library/cp/Nows the Time No 2.mxl +0 -0
  66. package/release/library/cp/Ornithology Eb.mxl +0 -0
  67. package/release/library/cp/Parkers Mood.mxl +0 -0
  68. package/release/library/cp/Passport.mxl +0 -0
  69. package/release/library/cp/Red Cross.mxl +0 -0
  70. package/release/library/cp/Scrapple from the Apple Eb.mxl +0 -0
  71. package/release/library/cp/Shaw Nuff Eb.mxl +0 -0
  72. package/release/library/cp/Shaw Nuff.mxl +0 -0
  73. package/release/library/cp/She Rote No1.mxl +0 -0
  74. package/release/library/cp/She Rote No2.mxl +0 -0
  75. package/release/library/cp/The Bird 07.mxl +0 -0
  76. package/release/library/cp/The Bird.mxl +0 -0
  77. package/release/library/cp/Warming Up A Riff.mxl +0 -0
  78. package/release/library/cp/Yardbird Suite (alto solo).mxl +0 -0
  79. package/release/library/cp/Yardbird Suite Eb Alto+Bass+Chords.mxl +0 -0
  80. package/release/library/cp/Yardbird Suite Eb.mxl +0 -0
  81. package/release/library/cp/cplist.txt +0 -40
  82. package/release/library/hymns/AdesteFideles.json +0 -1
  83. package/release/library/hymns/Heshallfeed.json +0 -1
  84. package/release/library/hymns/Hyfrydol.json +0 -1
  85. package/release/library/hymns/Lakeshore.json +0 -1
  86. package/release/library/hymns/Precious Lord.json +0 -1
  87. package/release/library/hymns/Shade.json +0 -1
  88. package/release/library/hymns/SpiritofLivingGod.json +0 -1
  89. package/release/library/hymns/SweetSpirit.json +0 -1
  90. package/release/library/hymns/We3kings.json +0 -1
  91. package/release/library/hymns/Weisse-Flaggen.json +0 -1
  92. package/release/library/hymns/WorldPeacePrayer.json +0 -1
  93. package/release/library/hymns/balm.json +0 -1
  94. package/release/library/hymns/cplist.txt +0 -9
  95. package/release/library/hymns/nearerMyGod.json +0 -1
  96. package/release/library/links/bigband.json +0 -47
  97. package/release/library/links/cptranscriptions.json +0 -365
  98. package/release/library/links/hymns.json +0 -78
  99. package/release/library/links/messiah.json +0 -33
  100. package/release/library/links/musicxml.json +0 -62
  101. package/release/library/links/piano.json +0 -59
  102. package/release/library/links/samples.json +0 -75
  103. package/release/library/links/smoLibrary.json +0 -68
  104. package/release/library/links/soprano.json +0 -51
  105. package/release/library/links/transcriptions.json +0 -32
  106. package/release/library/links/trumpet.json +0 -76
  107. package/release/library/messiah/Messiah-I-1.json +0 -1
  108. package/release/library/messiah/Messiah-I-2.json +0 -1
  109. package/release/library/miditest/keytime.mid +0 -0
  110. package/release/library/miditest/ties.mid +0 -0
  111. package/release/library/miditest/triplet.mid +0 -0
  112. package/release/library/piano/20seconds.json +0 -1
  113. package/release/library/piano/BachInvention.json +0 -1
  114. package/release/library/piano/BachWTC-3.json +0 -1
  115. package/release/library/piano/Gnossienne3-3.json +0 -1
  116. package/release/library/piano/Gymnopedie No 1 (8).json +0 -1
  117. package/release/library/piano/Joplin-Entertainer.json +0 -1
  118. package/release/library/soprano/Gesu Bambino.json +0 -1
  119. package/release/library/soprano/LaudamusTrumpetSoprano.json +0 -1
  120. package/release/library/soprano/Postillionlied.json +0 -1
  121. package/release/library/soprano/Solovey.json +0 -1
  122. package/release/library/soprano/ThereIsABalm.json +0 -1
  123. package/release/library/trumpet/2min.json +0 -1
  124. package/release/library/trumpet/BachAirOrganTpt.json +0 -1
  125. package/release/library/trumpet/BachCantataBMV68.json +0 -1
  126. package/release/library/trumpet/BeethovenOp21-old.json +0 -1
  127. package/release/library/trumpet/BeethovenOp21.json +0 -1
  128. package/release/library/trumpet/Fireworks - trumpets.json +0 -1
  129. package/release/library/trumpet/HymnFreedom.json +0 -1
  130. package/release/library/trumpet/TelemannSonata3rd.json +0 -1
  131. package/release/library/xml/Gnossienne_No._3.mxl +0 -0
  132. package/release/library/xml/M_Book_1_Part_1_The_Open_Well_Tempered_Clavier.mxl +0 -0
  133. package/release/library/xml/lg-177775783.xml +0 -8674
@@ -86,7 +86,7 @@ export interface SmoConfigurationParams {
86
86
  leftControls: 'controls-left',
87
87
  topControls: 'controls-top',
88
88
  scoreDomContainer: 'smo-scroll-region',
89
- libraryUrl: 'https://smoosic.github.io/Smoosic/release/library/links/smoLibrary.json',
89
+ libraryUrl: 'https://smoosic.github.io/SmoScores/links/smoLibrary.json',
90
90
  demonPollTime: 50, // how often we poll the score to see if it changed
91
91
  idleRedrawTime: 1000, // maximum time between score modification and render
92
92
  ribbonLayout: defaultRibbonLayout.ribbons,
@@ -40,7 +40,7 @@ import { SuiInstrumentDialog } from '../ui/dialogs/instrument';
40
40
  import { SuiTimeSignatureDialog } from '../ui/dialogs/timeSignature';
41
41
  import { SuiTempoDialog } from '../ui/dialogs/tempo';
42
42
  import { SuiNoteHeadAdapter, SuiNoteHeadDialog, SuiNoteHeadButtonComponent, SuiStemButtonComponent } from '../ui/dialogs/noteHead';
43
- import { SuiEndingsAdapter, SuiEndingsDialog,
43
+ import { SuiEndingsAdapter, SuiEndingsDialog, endingsButtonFactory,
44
44
  SuiEndBarButtonComponent, SuiStartBarButtonComponent, SuiRepeatSymbolButtonComponent } from '../ui/dialogs/endings';
45
45
  import { SuiScoreIdentificationDialog } from '../ui/dialogs/scoreId';
46
46
  import { SuiScorePreferencesDialog } from '../ui/dialogs/preferences';
@@ -98,6 +98,7 @@ import { TextCheckComponent } from '../ui/dialogs/components/textCheck';
98
98
  import { SuiMenuManager} from '../ui/menus/manager';
99
99
  import { SuiMenuBase, SuiMenuCustomizer } from '../ui/menus/menu';
100
100
  import { SuiScoreMenu } from '../ui/menus/score';
101
+ import { SuiEditMenu } from '../ui/menus/edit';
101
102
  import { SuiTextMenu } from '../ui/menus/text';
102
103
  import { SuiPartMenu } from '../ui/menus/parts';
103
104
  import { SuiVoiceMenu } from '../ui/menus/voices';
@@ -346,7 +347,7 @@ export const Smo = {
346
347
  // Menus
347
348
  SuiMenuManager, SuiMenuBase, SuiMenuCustomizer, SuiScoreMenu, SuiFileMenu,
348
349
  SuiDynamicsMenu, SuiTimeSignatureMenu, SuiKeySignatureMenu, SuiStaffModifierMenu,
349
- SuiLanguageMenu, SuiMeasureMenu, SuiNoteMenu, SmoLanguage, SmoTranslator, SuiPartMenu,
350
+ SuiLanguageMenu, SuiMeasureMenu, SuiNoteMenu, SuiEditMenu, SmoLanguage, SmoTranslator, SuiPartMenu,
350
351
  SuiPartSelectionMenu, SuiTextMenu, SuiVoiceMenu, SuiBeamMenu,
351
352
  // Dialogs
352
353
  SuiGraceNoteAdapter, SuiGraceNoteDialog, SuiGraceNoteButtonsComponent,
@@ -365,7 +366,7 @@ export const Smo = {
365
366
  SuiArticulationDialog, SuiArticulationButtonComponent, SuiArticulationAdapter,
366
367
  SuiMicrotoneAdapter, SuiMicrotoneButtonComponent, SuiMicrotoneDialog,
367
368
  SuiNoteHeadAdapter, SuiNoteHeadDialog, SuiStemButtonComponent, SuiNoteHeadButtonComponent,
368
- SuiEndingsAdapter, SuiEndingsDialog,
369
+ SuiEndingsAdapter, SuiEndingsDialog, endingsButtonFactory,
369
370
  SuiEndBarButtonComponent, SuiStartBarButtonComponent, SuiRepeatSymbolButtonComponent,
370
371
  SuiPrintFileDialog, SuiSaveFileDialog, SuiSaveXmlDialog, SuiSaveVexDialog,
371
372
  SuiSaveMidiDialog, SuiDialogBase, createAndDisplayDialog,
package/src/common/vex.ts CHANGED
@@ -86,6 +86,8 @@ export type TabNotePosition = VexTabNotePosition;
86
86
  // @internal
87
87
  export type TabNoteStruct = VexTabNoteStruct;
88
88
 
89
+ const lineDefaults = [4, 2, 0, 1, 3]; // lines to turn off if there are less than 5
90
+
89
91
  /**
90
92
  * @internal
91
93
  */
@@ -163,6 +165,7 @@ export interface SmoVexStaveParams {
163
165
  canceledKey: string | null,
164
166
  startX: number,
165
167
  adjX: number,
168
+ lines: number,
166
169
  context: any
167
170
  }
168
171
  export function createTabStave(box: SvgBox, spacing: number, numLines: number): TabStave {
@@ -224,6 +227,14 @@ export function createStave(params: SmoVexStaveParams) {
224
227
  fill: 'none', 'stroke-width': 1, stroke: 'white'
225
228
  });
226
229
  }
230
+ if (params.lines < 5) {
231
+ const linesAr = [];
232
+ for (let i = 0; i < lineDefaults.length; ++i) {
233
+ const visible = lineDefaults[i] < params.lines;
234
+ linesAr.push({ visible });
235
+ }
236
+ stave.setConfigForLines(linesAr);
237
+ }
227
238
  // stave.options.spaceAboveStaffLn = 0; // don't let vex place the staff, we want to.
228
239
  stave.options.space_above_staff_ln = 0; // don't let vex place the staff, we want to.
229
240
  // Add a clef and time signature.
@@ -81,6 +81,8 @@ export const defaultClearAudioAnimationHandler = (delay: number) => {
81
81
  ctx.closeGroup();
82
82
  ctx.restore();
83
83
  layoutDebug.updatePlayDebug(selector, measure.svg.logicalBox);
84
- scroller.scrollVisibleBox(zmeasureSel.measure.svg.logicalBox);
84
+ if (view.score.preferences.autoScrollPlayback) {
85
+ scroller.scrollVisibleBox(zmeasureSel.measure.svg.logicalBox);
86
+ }
85
87
  }
86
88
  }
@@ -382,14 +382,14 @@ export class SuiSampleMedia {
382
382
  sample: 'sample-asax-a3',
383
383
  family: 'wind',
384
384
  instrument: 'tenorSax',
385
- nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 3 }, 24, null),
385
+ nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'a', accidental: 'n', octave: 3 }, 12, null),
386
386
  });
387
387
  SuiSampleMedia.insertIntoMap({
388
388
  sustain: 'sustained',
389
389
  sample: 'sample-asax-c4',
390
390
  family: 'wind',
391
391
  instrument: 'tenorSax',
392
- nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 24, null),
392
+ nativeFrequency: SmoAudioPitch.smoPitchToFrequency({ letter: 'c', accidental: 'n', octave: 4 }, 12, null),
393
393
  });
394
394
  SuiSampleMedia.insertIntoMap({
395
395
  sustain: 'sustained',
@@ -395,8 +395,10 @@ export abstract class SuiMapper {
395
395
  }
396
396
  return true;
397
397
  }
398
- // ### updateMeasure
399
- // A measure has changed. Update the music geometry for it
398
+
399
+ /**
400
+ * This is the logic that stores the screen location of music after it's rendered
401
+ */
400
402
  mapMeasure(staff: SmoSystemStaff, measure: SmoMeasure, printing: boolean) {
401
403
  let voiceIx = 0;
402
404
  let selectedTicks = 0;
@@ -433,8 +435,12 @@ export abstract class SuiMapper {
433
435
  tick,
434
436
  pitches: []
435
437
  };
436
- if (typeof(note.logicalBox) === 'undefined') {
437
- console.warn('note has no box');
438
+ if (measure.repeatSymbol) {
439
+ // Some measures have a symbol that replaces the notes. This allows us to select
440
+ // the measure
441
+ const x = measure.svg.logicalBox.x + (measure.svg.logicalBox.width / voice.notes.length) * tick;
442
+ const width = measure.svg.logicalBox.width / voice.notes.length;
443
+ note.logicalBox = { x, y: measure.svg.logicalBox.y, width, height: measure.svg.logicalBox.height };
438
444
  }
439
445
  // create a selection for the newly rendered note
440
446
  const selection = new SmoSelection({
@@ -107,7 +107,11 @@ export class SuiScoreRender {
107
107
  // If this text is attached to the measure, base the block location on the rendered measure location.
108
108
  if (newGroup.attachToSelector) {
109
109
  // If this text is attached to a staff that is not visible, don't draw it.
110
- const mappedStaff = this.score!.staves.find((staff) => staff.staffId === newGroup.selector!.staff);
110
+ let mappedStaff = this.score!.staves.find((staff) => staff.staffId === newGroup.selector!.staff);
111
+ if (this.score?.isPartExposed() && this.score.staves[0].partInfo.preserveTextGroups) {
112
+ mappedStaff = this.score!.staves.find((staff) =>
113
+ staff.getMappedStaffId() === newGroup.selector!.staff);
114
+ }
111
115
  if (!mappedStaff) {
112
116
  return;
113
117
  }
@@ -1235,6 +1235,32 @@ export class SuiScoreViewOperations extends SuiScoreView {
1235
1235
  this.replaceMeasureView(measureRange);
1236
1236
  await this.renderer.updatePromise();
1237
1237
  }
1238
+ /**
1239
+ * Paste only the chords.
1240
+ */
1241
+ async pasteChords(): Promise<void> {
1242
+ // We undo the whole score on a paste, since we don't yet know the
1243
+ // extent of the overlap
1244
+ this.renderer.preserveScroll();
1245
+ const selections: SmoSelection[] = this.getPasteMeasureList();
1246
+ const firstSelection = selections[0];
1247
+ const measureEnd = selections[selections.length - 1].selector.measure;
1248
+ const measureRange = [firstSelection.selector.measure, measureEnd];
1249
+ this.storeUndo.grouping = true;
1250
+ // Undo the paste by selecting all the affected measures
1251
+ for (let i = measureRange[0]; i <= measureRange[1]; ++i) {
1252
+ this._undoColumn('paste', i);
1253
+ this.renderer.unrenderColumn(this.score.staves[0].measures[i]);
1254
+ }
1255
+ this.storeUndo.grouping = false;
1256
+ const altSelection = this._getEquivalentSelection(firstSelection);
1257
+ const altTarget = altSelection!.selector;
1258
+ altTarget.tick = this.tracker.selections[0].selector.tick;
1259
+ this.storePaste.pasteChords(altTarget);
1260
+ // Refresh those measures.
1261
+ this.replaceMeasureView(measureRange);
1262
+ await this.renderer.updatePromise();
1263
+ }
1238
1264
  /**
1239
1265
  * specify a note head other than the default for the duration
1240
1266
  * @param head
@@ -505,6 +505,7 @@ export class VxMeasure implements VxMeasureIf {
505
505
  canceledKey,
506
506
  startX: this.smoMeasure.svg.maxColumnStartX,
507
507
  adjX: this.smoMeasure.svg.adjX,
508
+ lines: this.smoMeasure.lines,
508
509
  context: this.context.getContext()
509
510
  }
510
511
  this.stave = createStave(smoVexStaveParams);
@@ -861,6 +861,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
861
861
  // The measure expects to get concert KS in constructor and adjust for instrument. So do the
862
862
  // opposite.
863
863
  obj.keySignature = SmoMusic.vexKeySigWithOffset(obj.keySignature, -1 * obj.transposeIndex);
864
+ obj.lines = params.lines;
864
865
  // Don't redisplay tempo for a new measure
865
866
  const rv = new SmoMeasure(obj);
866
867
  if (rv.tempo && rv.tempo.display) {
@@ -6,7 +6,8 @@
6
6
  * @module /smo/data/noteModifiers
7
7
  */
8
8
  import { SmoAttrs, Ticks, Pitch, getId, SmoObjectParams, Transposable, SvgBox, SmoModifierBase,
9
- Clef, IsClef, SmoDynamicCtor } from './common';
9
+ Clef, IsClef, SmoDynamicCtor,
10
+ IsPitchLetter} from './common';
10
11
  import { smoSerialize } from '../../common/serializationHelpers';
11
12
  import { SmoMusic } from './music';
12
13
  import { defaultNoteScale, FontInfo, getChordSymbolGlyphFromCode } from '../../common/vex';
@@ -931,6 +932,46 @@ export class SmoLyric extends SmoNoteModifierBase {
931
932
  this.adjustNoteWidthChord = val;
932
933
  }
933
934
  }
935
+ static transposeChordToKey(chord: SmoLyric, offset: number, srcKey: string, destKey: string): SmoLyric {
936
+ if (chord.parser !== SmoLyric.parsers.chord || offset === 0) {
937
+ return new SmoLyric(chord);
938
+ }
939
+ const nchord = new SmoLyric(chord);
940
+ let srcIx = 0;
941
+ const maxLen = chord.text.length - 1;
942
+ let destString = '';
943
+ while (srcIx < chord.text.length) {
944
+ let symbolBlock = false;
945
+ const nchar = chord.text[srcIx];
946
+ let lk = srcIx < maxLen ? chord.text[srcIx + 1] : null;
947
+ // make sure this chord start witha VEX pitch letter (A-G upper case)
948
+ if (IsPitchLetter(nchar.toLowerCase()) && nchar == nchar.toUpperCase()) {
949
+ if (lk === '@') {
950
+ symbolBlock = true;
951
+ srcIx += 1;
952
+ lk = srcIx < maxLen ? chord.text[srcIx + 1] : null;
953
+ }
954
+ const pitch: Pitch = {letter: nchar.toLowerCase() as any, accidental: 'n', octave: 4 };
955
+ if (lk !== null && (lk === 'b' || lk === '#')) {
956
+ pitch.accidental = lk;
957
+ srcIx += 1;
958
+ }
959
+ const npitch = SmoMusic.transposePitchForKey(pitch, srcKey, destKey, offset);
960
+ destString += npitch.letter.toUpperCase();
961
+ if (symbolBlock) {
962
+ destString += '@';
963
+ }
964
+ if (npitch.accidental !== 'n') {
965
+ destString += npitch.accidental;
966
+ }
967
+ } else {
968
+ destString += nchar;
969
+ }
970
+ srcIx += 1;
971
+ }
972
+ nchord.text = destString;
973
+ return nchord;
974
+ }
934
975
 
935
976
  // ### getClassSelector
936
977
  // returns a selector used to find this text block within a note.
@@ -1022,7 +1063,49 @@ export class SmoLyric extends SmoNoteModifierBase {
1022
1063
  }
1023
1064
  }
1024
1065
  }
1066
+ /**
1067
+ * Used to create a {@link SmoBarline}
1068
+ * @category SmoObject
1069
+ */
1070
+
1071
+ export interface SmoNoteBarParams {
1072
+ barline: number
1073
+ }
1025
1074
 
1075
+ export interface SmoNoteBarParamsSer extends SmoNoteBarParams {
1076
+ ctor: string,
1077
+ barline: number
1078
+ }
1079
+
1080
+ export class SmoNoteBar extends SmoNoteModifierBase {
1081
+ barline: number = SmoNoteBar.defaults.barline;
1082
+ static get defaults(): SmoNoteBarParams {
1083
+ return { barline: SmoNoteBar.barlines.noBar }
1084
+ }
1085
+ static get parameterArray() {
1086
+ return ['barline'];
1087
+ }
1088
+ static readonly barlines: Record<string, number> = {
1089
+ singleBar: 0,
1090
+ doubleBar: 1,
1091
+ endBar: 2,
1092
+ startRepeat: 3,
1093
+ endRepeat: 4,
1094
+ noBar: 5
1095
+ }
1096
+ constructor(parameters: SmoNoteBarParams) {
1097
+ super('SmoNoteBar');
1098
+ smoSerialize.serializedMerge(SmoNoteBar.parameterArray, SmoNoteBar.defaults, this);
1099
+ smoSerialize.serializedMerge(SmoLyric.parameterArray, parameters, this);
1100
+ }
1101
+ serialize(): SmoNoteBarParamsSer {
1102
+ const parameters: Partial<SmoNoteBarParamsSer> = {};
1103
+ smoSerialize.serializedMergeNonDefault(SmoNoteBar.defaults,
1104
+ SmoNoteBar.parameterArray, this, parameters);
1105
+ parameters.ctor = 'SmoNoteBar';
1106
+ return parameters as SmoNoteBarParamsSer;
1107
+ }
1108
+ }
1026
1109
  /**
1027
1110
  * The persisted bits of {@link SmoDynamicTextParams}
1028
1111
  * @category serialization
@@ -1205,6 +1288,7 @@ export const noteModifierDynamicCtorInit = () => {
1205
1288
  SmoDynamicCtor['SmoGraceNote'] = (params: GraceNoteParams) => new SmoGraceNote(params);
1206
1289
  SmoDynamicCtor['SmoArticulation'] = (params: SmoArticulationParameters) => new SmoArticulation(params);
1207
1290
  SmoDynamicCtor['SmoLyric'] = (params: SmoLyricParams) => new SmoLyric(params);
1291
+ SmoDynamicCtor['SmoNoteBar'] = (params: SmoNoteBarParams) => new SmoNoteBar(params);
1208
1292
  SmoDynamicCtor['SmoDynamicText'] = (params: SmoDynamicTextParams) => new SmoDynamicText(params);
1209
1293
  SmoDynamicCtor['SmoTabNote'] = (params: SmoTabNoteParams) => new SmoTabNote(params);
1210
1294
  SmoDynamicCtor['SmoClefChange'] = (params: SmoClefChangeParams) => new SmoClefChange(params);
@@ -722,6 +722,12 @@ export class SmoScore {
722
722
  if (!isSmoScoreParams(params)) {
723
723
  throw 'Bad score, missing params: ' + JSON.stringify(params, null, ' ');
724
724
  }
725
+ if (params.staves.length === 1) {
726
+ const part = params.staves[0].partInfo;
727
+ if (part) {
728
+ part.expandMultimeasureRests = true;
729
+ }
730
+ }
725
731
  const score = new SmoScore(params);
726
732
  score.textGroups = textGroups;
727
733
  score.systemGroups = systemGroups;
@@ -950,8 +956,12 @@ export class SmoScore {
950
956
  // immediately preceeding or post-ceding measure if it exists.
951
957
  if (measureIndex < staff.measures.length) {
952
958
  protomeasure = staff.measures[measureIndex];
959
+ const instrument = staff.getStaffInstrument(measureIndex);
960
+ protomeasure.lines = instrument.lines;
953
961
  } else if (staff.measures.length) {
954
962
  protomeasure = staff.measures[staff.measures.length - 1];
963
+ const instrument = staff.getStaffInstrument(staff.measures.length - 1);
964
+ protomeasure.lines = instrument.lines;
955
965
  } else {
956
966
  protomeasure = SmoMeasure.defaults;
957
967
  }
@@ -124,9 +124,10 @@ export interface SmoScoreInfo {
124
124
  }
125
125
 
126
126
 
127
- export type SmoScorePreferenceBool = 'autoPlay' | 'autoAdvance' | 'showPiano' | 'hideEmptyLines' | 'transposingScore';
127
+ export type SmoScorePreferenceBool = 'autoPlay' | 'autoAdvance' | 'showPiano' | 'hideEmptyLines' | 'transposingScore' | 'autoScrollPlayback';
128
128
  export type SmoScorePreferenceNumber = 'defaultDupleDuration' | 'defaultTripleDuration';
129
- export const SmoScorePreferenceBools: SmoScorePreferenceBool[] = ['autoPlay', 'autoAdvance', 'showPiano', 'hideEmptyLines', 'transposingScore'];
129
+ export const SmoScorePreferenceBools: SmoScorePreferenceBool[] = ['autoPlay', 'autoAdvance', 'showPiano', 'hideEmptyLines',
130
+ 'transposingScore', 'autoScrollPlayback'];
130
131
  export const SmoScorePreferenceNumbers: SmoScorePreferenceNumber[] = ['defaultDupleDuration', 'defaultTripleDuration'];
131
132
  /**
132
133
  * Global score/program behavior preferences, see below for parameters
@@ -135,6 +136,7 @@ export const SmoScorePreferenceNumbers: SmoScorePreferenceNumber[] = ['defaultDu
135
136
  export interface SmoScorePreferencesParams {
136
137
  autoPlay: boolean;
137
138
  autoAdvance: boolean;
139
+ autoScrollPlayback: boolean;
138
140
  defaultDupleDuration: number;
139
141
  defaultTripleDuration: number;
140
142
  showPiano: boolean;
@@ -159,6 +161,7 @@ export class SmoScorePreferences {
159
161
  defaultTripleDuration: number = 6144;
160
162
  showPiano: boolean = false;
161
163
  hideEmptyLines: boolean = false;
164
+ autoScrollPlayback: boolean = true;
162
165
  transposingScore: boolean = false;
163
166
  static get defaults(): SmoScorePreferencesParams {
164
167
  return {
@@ -166,19 +169,24 @@ export class SmoScorePreferences {
166
169
  autoAdvance: true,
167
170
  defaultDupleDuration: 4096,
168
171
  defaultTripleDuration: 6144,
172
+ autoScrollPlayback: true,
169
173
  showPiano: false,
170
174
  hideEmptyLines: false,
171
175
  transposingScore: false
172
176
  };
173
177
  }
174
178
  constructor(params: SmoScorePreferencesParams) {
175
- if (params) {
179
+ if (params) {
176
180
  SmoScorePreferenceBools.forEach((bb) => {
177
181
  this[bb] = params[bb];
178
182
  });
179
183
  SmoScorePreferenceNumbers.forEach((nn) => {
180
184
  this[nn] = params[nn];
181
185
  });
186
+ // legacy, added later
187
+ if (typeof(params.autoScrollPlayback) === 'undefined') {
188
+ this.autoScrollPlayback = true;
189
+ }
182
190
  }
183
191
  }
184
192
  serialize(): SmoScorePreferencesParams {
@@ -158,7 +158,8 @@ export interface SmoInstrumentParams {
158
158
  /**
159
159
  * future, can be used to set sample
160
160
  */
161
- mutes?: string,
161
+ mutes?: string,
162
+ lines: number
162
163
  }
163
164
 
164
165
  /**
@@ -174,8 +175,8 @@ export interface SmoInstrumentParamsSer extends SmoInstrumentParams {
174
175
  function isSmoInstrumentParamsSer(params: Partial<SmoInstrumentParamsSer>): params is SmoInstrumentParamsSer {
175
176
  return params?.ctor === 'SmoInstrument';
176
177
  }
177
- export type SmoInstrumentNumParamType = 'keyOffset' | 'midichannel' | 'midiport' | 'midiInstrument';
178
- export const SmoInstrumentNumParams: SmoInstrumentNumParamType[] = ['keyOffset', 'midichannel', 'midiport', 'midiInstrument'];
178
+ export type SmoInstrumentNumParamType = 'keyOffset' | 'midichannel' | 'midiport' | 'midiInstrument' | 'lines';
179
+ export const SmoInstrumentNumParams: SmoInstrumentNumParamType[] = ['keyOffset', 'midichannel', 'midiport', 'midiInstrument', 'lines'];
179
180
  export type SmoInstrumentStringParamType = 'instrumentName' | 'abbreviation' | 'family' | 'instrument';
180
181
  export const SmoInstrumentStringParams: SmoInstrumentStringParamType[] = ['instrumentName', 'abbreviation', 'family', 'instrument'];
181
182
  /**
@@ -187,7 +188,8 @@ export const SmoInstrumentStringParams: SmoInstrumentStringParamType[] = ['instr
187
188
  */
188
189
  export class SmoInstrument extends StaffModifierBase {
189
190
  static get attributes() {
190
- return ['startSelector', 'endSelector', 'keyOffset', 'midichannel', 'midiport', 'instrumentName', 'abbreviation', 'instrument', 'family'];
191
+ return ['startSelector', 'endSelector',
192
+ 'keyOffset', 'midichannel', 'midiport', 'instrumentName', 'abbreviation', 'instrument', 'family', 'lines'];
191
193
  }
192
194
  startSelector: SmoSelector;
193
195
  endSelector: SmoSelector;
@@ -196,6 +198,7 @@ export class SmoInstrument extends StaffModifierBase {
196
198
  keyOffset: number = 0;
197
199
  clef: Clef = 'treble';
198
200
  midiInstrument: number = 1;
201
+ lines: number = 5;
199
202
  midichannel: number;
200
203
  midiport: number;
201
204
  family: string;
@@ -214,7 +217,8 @@ export class SmoInstrument extends StaffModifierBase {
214
217
  midiInstrument: 1,
215
218
  midiport: 0,
216
219
  startSelector: SmoSelector.default,
217
- endSelector: SmoSelector.default
220
+ endSelector: SmoSelector.default,
221
+ lines: 5
218
222
  }));
219
223
  }
220
224
  static get defaultOscillatorParam(): SmoOscillatorInfo {
@@ -241,6 +245,11 @@ export class SmoInstrument extends StaffModifierBase {
241
245
  } else {
242
246
  name = (params as any).instrument;
243
247
  }
248
+ if (typeof ((params as any).lines) === 'undefined') {
249
+ this.lines = 5;
250
+ } else {
251
+ this.lines = params.lines;
252
+ }
244
253
  this.instrumentName = name;
245
254
  this.family = params.family;
246
255
  this.instrument = params.instrument;
@@ -380,6 +380,7 @@ export class SmoSystemStaff implements SmoObjectParams {
380
380
  curInstrumentIndex += 1;
381
381
  }
382
382
  measure.transposeIndex = instrumentAr[curInstrumentIndex].instrument.keyOffset;
383
+ measure.lines = instrumentAr[curInstrumentIndex].instrument.lines;
383
384
  params.measures.push(measure);
384
385
  });
385
386
  if (jsonObj.modifiers) {
@@ -416,6 +417,15 @@ export class SmoSystemStaff implements SmoObjectParams {
416
417
  }
417
418
  mod.associatedStaff = to; // this.staffId will remap to 'to' value
418
419
  });
420
+ this.textBrackets.forEach((mod: SmoStaffTextBracket) => {
421
+ if (mod.startSelector.staff === from) {
422
+ mod.startSelector.staff = to;
423
+ }
424
+ if (mod.endSelector.staff === from) {
425
+ mod.endSelector.staff = to;
426
+ }
427
+ mod.associatedStaff = to; // this.staffId will remap to 'to' value
428
+ });
419
429
  }
420
430
  updateMeasureFormatsForPart() {
421
431
  this.measures.forEach((measure, mix) => {
@@ -453,6 +463,7 @@ export class SmoSystemStaff implements SmoObjectParams {
453
463
  const tabStave: SmoTabStave | undefined = this.getTabStaveForMeasure(SmoSelector.fromMeasure(measure));
454
464
  measure.transposeToOffset(entry.instrument.keyOffset, targetKey, entry.instrument.clef);
455
465
  measure.transposeIndex = entry.instrument.keyOffset;
466
+ measure.lines = entry.instrument.lines;
456
467
  measure.keySignature = targetKey;
457
468
  measure.setClef(entry.instrument.clef);
458
469
  }
@@ -4,6 +4,7 @@ import { SmoSelection, SmoSelector } from './selections';
4
4
  import { SmoNote } from '../data/note';
5
5
  import { SmoMeasure, SmoVoice } from '../data/measure';
6
6
  import { StaffModifierBase } from '../data/staffModifiers';
7
+ import { SmoLyric } from '../data/noteModifiers';
7
8
  import {SmoTuplet, SmoTupletTree, SmoTupletTreeParams} from '../data/tuplet';
8
9
  import { SmoMusic } from '../data/music';
9
10
  import { SvgHelpers } from '../../render/sui/svgHelpers';
@@ -131,6 +132,14 @@ export class PasteBuffer {
131
132
  const keyOffset = -1 * selection.measure.transposeIndex;
132
133
  const destKey = SmoMusic.vexKeySignatureTranspose(originalKey, keyOffset).toLocaleLowerCase();
133
134
  const note = SmoNote.transpose(SmoNote.clone(selection.note),[], keyOffset, selection.measure.keySignature, destKey) as SmoNote;
135
+ const chords: SmoLyric[] = note.getChords();
136
+ chords.forEach((chord) => {
137
+ note.removeLyric(chord);
138
+ });
139
+ chords.forEach((chord) => {
140
+ const nchord = SmoLyric.transposeChordToKey(chord, keyOffset, selection.measure.keySignature, destKey);
141
+ note.addLyric(nchord);
142
+ });
134
143
  const pasteNote: PasteNote = {
135
144
  selector,
136
145
  note,
@@ -517,7 +526,47 @@ export class PasteBuffer {
517
526
  }
518
527
  serializedMeasure.voices = voices;
519
528
  }
520
-
529
+ pasteChords(selector: SmoSelector) {
530
+ if (this.notes.length < 1) {
531
+ return;
532
+ }
533
+ if (!this.score) {
534
+ return;
535
+ }
536
+ let srcTick = 0;
537
+ let destTick = 0;
538
+ let srcIndex = 0;
539
+ let selection = SmoSelection.noteSelection(this.score!, selector.staff, selector.measure, selector.voice, selector.tick);
540
+ while (selection && selection.note && srcIndex < this.notes.length) {
541
+ const srcNote = this.notes[srcIndex].note;
542
+ const chords = srcNote.getChords();
543
+ if (selection && selection.note) {
544
+ const destNote = selection.note;
545
+ if (chords.length) {
546
+ chords.forEach((chord) => {
547
+ destNote.removeLyric(chord);
548
+ });
549
+ chords.forEach((chord) => {
550
+ if (selection) {
551
+ const nchord = SmoLyric.transposeChordToKey(
552
+ chord, selection.measure.transposeIndex,this.notes[srcIndex].originalKey, selection.measure.keySignature);
553
+ destNote.addLyric(nchord);
554
+ }
555
+ });
556
+ }
557
+ srcTick += srcNote.tickCount;
558
+ while (selection && selection.note && destTick < srcTick) {
559
+ destTick += selection.note.tickCount;
560
+ if (selection && selection.note) {
561
+ const curSelector = selection.selector;
562
+ selection = SmoSelection.nextNoteSelection(this.score,
563
+ curSelector.staff, curSelector.measure, curSelector.voice, curSelector.tick);
564
+ }
565
+ }
566
+ srcIndex += 1;
567
+ }
568
+ }
569
+ }
521
570
  pasteSelections(selector: SmoSelector) {
522
571
  let i = 0;
523
572
  if (this.notes.length < 1) {
@@ -87,11 +87,26 @@ export class SmoOperation {
87
87
  });
88
88
  });
89
89
  }
90
+ /**
91
+ * Move a single stave up or down one. If last down, move to first.
92
+ * If first up, move to last
93
+ * @param score
94
+ * @param selection
95
+ * @param index
96
+ */
90
97
  static moveStaffUpDown(score: SmoScore, selection: SmoSelection, index: number) {
91
98
  const index1 = selection.selector.staff;
92
99
  const index2 = selection.selector.staff + index;
93
100
  if (index2 < score.staves.length && index2 >= 0) {
94
101
  score.swapStaves(index1, index2);
102
+ } else if (index2 === score.staves.length) {
103
+ for (let i = 0; i < (score.staves.length - 1); ++i) {
104
+ score.swapStaves((score.staves.length - 1) - i, (score.staves.length - 1) - (i + 1));
105
+ }
106
+ } else if (index2 < 0) {
107
+ for (let i = 0; i < (score.staves.length - 1); ++i) {
108
+ score.swapStaves(i, i + 1);
109
+ }
95
110
  }
96
111
  }
97
112
 
@@ -13,7 +13,7 @@ import { SuiDialogNotifier, SuiBaseComponentParams } from './components/baseComp
13
13
  * Buttons with ending or repeat functions
14
14
  * @returns SuiButtonArrayParameters
15
15
  */
16
- const endingsButtonFactory: getButtonsFcn = () => {
16
+ export const endingsButtonFactory: getButtonsFcn = () => {
17
17
  const params: SuiButtonArrayParameters = {
18
18
  label: 'Measure Endings',
19
19
  rows: [{
@@ -38,7 +38,12 @@ export class SuiInstrumentAdapter extends SuiComponentAdapter {
38
38
  this.view.changeInstrument(this.instrument, this.selections);
39
39
  this.instrument = new SmoInstrument(this.instrument);
40
40
  }
41
-
41
+ get lines() {
42
+ return this.instrument.lines;
43
+ }
44
+ set lines(value: number) {
45
+ this.writeNumParam('lines', value);
46
+ }
42
47
  get transposeIndex() {
43
48
  return this.instrument.keyOffset;
44
49
  }
@@ -108,6 +113,11 @@ export class SuiInstrumentDialog extends SuiDialogAdapterBase<SuiInstrumentAdapt
108
113
  label: 'Instrument Properties',
109
114
  elements:
110
115
  [{
116
+ smoName: 'lines',
117
+ defaultValue: 5,
118
+ control: 'SuiRockerComponent',
119
+ label: 'Staff lines (1-5)'
120
+ }, {
111
121
  smoName: 'transposeIndex',
112
122
  defaultValue: 0,
113
123
  control: 'SuiRockerComponent',
@@ -44,6 +44,13 @@ export class SuiScorePreferencesAdapter extends SuiComponentAdapter {
44
44
  this.preferences.showPiano = value;
45
45
  this.view.updateScorePreferences(this.preferences);
46
46
  }
47
+ get autoScrollPlayback(): boolean {
48
+ return this.preferences.autoScrollPlayback;
49
+ }
50
+ set autoScrollPlayback(value: boolean) {
51
+ this.preferences.autoScrollPlayback = value;
52
+ this.view.updateScorePreferences(this.preferences);
53
+ }
47
54
  get hideEmptyLines(): boolean {
48
55
  return this.preferences.hideEmptyLines;
49
56
  }
@@ -102,6 +109,10 @@ export class SuiScorePreferencesDialog extends SuiDialogAdapterBase<SuiScorePref
102
109
  smoName: 'autoPlay',
103
110
  control: 'SuiToggleComponent',
104
111
  label: 'Auto-play sounds for note entry'
112
+ }, {
113
+ smoName: 'autoScrollPlayback',
114
+ control: 'SuiToggleComponent',
115
+ label: 'Auto-Scroll Playback'
105
116
  }, {
106
117
  smoName: 'showPiano',
107
118
  control: 'SuiToggleComponent',
@@ -45,7 +45,7 @@ export class defaultTrackerKeys {
45
45
  ctrlKey: false,
46
46
  altKey: false,
47
47
  shiftKey: true,
48
- action: "moveHome"
48
+ action: "moveEnd"
49
49
  }, {
50
50
  event: "keydown",
51
51
  key: "End",