smoosic 1.0.36 → 1.0.38

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 (42) hide show
  1. package/README.md +6 -3
  2. package/build/smoosic.js +89 -29
  3. package/changes.md +9 -1
  4. package/package.json +1 -1
  5. package/release/html/libmode.html +36 -0
  6. package/release/html/smoosic.html +4 -4
  7. package/release/smoosic.js +89 -29
  8. package/release/styles/dialogs.css +25 -18
  9. package/release/styles/general.css +16 -9
  10. package/release/styles/media.css +13 -5
  11. package/release/styles/ribbon.css +3 -0
  12. package/src/application/exports.ts +4 -4
  13. package/src/render/audio/musicCursor.ts +1 -1
  14. package/src/render/sui/NoteEntryCaret.ts +4 -1
  15. package/src/render/sui/formatter.ts +3 -3
  16. package/src/render/sui/mapper.ts +1 -1
  17. package/src/render/sui/scoreView.ts +4 -4
  18. package/src/render/sui/scoreViewOperations.ts +10 -10
  19. package/src/render/sui/textEdit.ts +3 -1
  20. package/src/render/vex/vxMeasure.ts +12 -6
  21. package/src/smo/data/measure.ts +37 -37
  22. package/src/smo/data/measureModifiers.ts +106 -71
  23. package/src/smo/data/note.ts +4 -1
  24. package/src/smo/data/score.ts +50 -11
  25. package/src/smo/data/systemStaff.ts +2 -2
  26. package/src/smo/midi/midiToSmo.ts +28 -29
  27. package/src/smo/midi/smoToMidi.ts +3 -3
  28. package/src/smo/mxml/smoToXml.ts +11 -11
  29. package/src/smo/mxml/xmlState.ts +3 -3
  30. package/src/smo/mxml/xmlToSmo.ts +9 -9
  31. package/src/smo/xform/copypaste.ts +3 -3
  32. package/src/smo/xform/operations.ts +9 -6
  33. package/src/smo/xform/tickDuration.ts +10 -2
  34. package/src/ui/buttons/display.ts +2 -2
  35. package/src/ui/buttons/ribbon.ts +2 -2
  36. package/src/ui/components/dialogs/timeSignature.vue +223 -0
  37. package/src/ui/dialogs/fileDialogs.ts +1 -1
  38. package/src/ui/dialogs/keySignature.ts +1 -1
  39. package/src/ui/dialogs/tempo.ts +38 -38
  40. package/src/ui/dialogs/timeSignature.ts +45 -116
  41. package/src/ui/menus/timeSignature.ts +2 -2
  42. package/tools/smoosic-schema.json +4 -4
@@ -12,9 +12,9 @@
12
12
  import { smoSerialize } from '../../common/serializationHelpers';
13
13
  import { SmoMusic } from './music';
14
14
  import {
15
- SmoBarline, SmoMeasureModifierBase, SmoRepeatSymbol, SmoTempoText, SmoMeasureFormat,
16
- SmoVolta, SmoRehearsalMarkParams, SmoRehearsalMark, SmoTempoTextParams, TimeSignature,
17
- TimeSignatureParametersSer, SmoMeasureFormatParamsSer, SmoTempoTextParamsSer
15
+ SmoBarline, SmoMeasureModifierBase, SmoRepeatSymbol, SmoTempo, SmoMeasureFormat,
16
+ SmoVolta, SmoRehearsalMarkParams, SmoRehearsalMark, SmoTempoParams, SmoTimeSignature,
17
+ TimeSignatureParametersSer, SmoMeasureFormatParamsSer, SmoTempoParamsSer
18
18
  } from './measureModifiers';
19
19
  import { SmoNote, NoteType, SmoNoteParamsSer } from './note';
20
20
  import { SmoTuplet, SmoTupletParamsSer, SmoTupletParams, SmoTupletTreeParamsSer, SmoTupletTree } from './tuplet';
@@ -148,7 +148,7 @@ export const SmoMeasureStringParams: SmoMeasureStringParam[] = ['keySignature'];
148
148
  * @category SmoObject
149
149
  */
150
150
  export interface SmoMeasureParams {
151
- timeSignature: TimeSignature,
151
+ timeSignature: SmoTimeSignature,
152
152
  keySignature: string,
153
153
  tupletTrees: SmoTupletTree[],
154
154
  transposeIndex: number,
@@ -158,7 +158,7 @@ export interface SmoMeasureParams {
158
158
  clef: Clef,
159
159
  voices: SmoVoice[],
160
160
  activeVoice: number,
161
- tempo: SmoTempoText,
161
+ tempo: SmoTempo,
162
162
  format: SmoMeasureFormat | null,
163
163
  modifiers: SmoMeasureModifierBase[],
164
164
  repeatSymbol: boolean,
@@ -218,7 +218,7 @@ export interface SmoMeasureParamsSer {
218
218
  /**
219
219
  * tempo at this point
220
220
  */
221
- tempo: SmoTempoTextParamsSer
221
+ tempo: SmoTempoParamsSer
222
222
 
223
223
  }
224
224
 
@@ -244,8 +244,8 @@ function isSmoMeasureParamsSer(params: Partial<SmoMeasureParamsSer>):params is S
244
244
  * @category SmoObject
245
245
  */
246
246
  export class SmoMeasure implements SmoMeasureParams, TickMappable {
247
- static get timeSignatureDefault(): TimeSignature {
248
- return new TimeSignature(TimeSignature.defaults);
247
+ static get timeSignatureDefault(): SmoTimeSignature {
248
+ return new SmoTimeSignature(SmoTimeSignature.defaults);
249
249
  }
250
250
  static defaultDupleDuration: number = 4096;
251
251
  static defaultTripleDuration: number = 2048 * 3;
@@ -268,7 +268,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
268
268
  voices: [],
269
269
  format: new SmoMeasureFormat(SmoMeasureFormat.defaults),
270
270
  activeVoice: 0,
271
- tempo: new SmoTempoText(SmoTempoText.defaults),
271
+ tempo: new SmoTempo(SmoTempo.defaults),
272
272
  repeatSymbol: false,
273
273
  repeatCount: 0
274
274
  }
@@ -281,7 +281,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
281
281
  static get defaults(): SmoMeasureParams {
282
282
  const proto: any = JSON.parse(JSON.stringify(SmoMeasure._defaults));
283
283
  proto.format = new SmoMeasureFormat(SmoMeasureFormat.defaults);
284
- proto.tempo = new SmoTempoText(SmoTempoText.defaults);
284
+ proto.tempo = new SmoTempo(SmoTempo.defaults);
285
285
  proto.modifiers.push(new SmoBarline({
286
286
  position: SmoBarline.positions.start,
287
287
  barline: SmoBarline.barlines.singleBar
@@ -294,11 +294,11 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
294
294
  }
295
295
  // @ignore
296
296
  static convertLegacyTimeSignature(ts: string) {
297
- const rv = new TimeSignature(TimeSignature.defaults);
297
+ const rv = new SmoTimeSignature(SmoTimeSignature.defaults);
298
298
  rv.timeSignature = ts;
299
299
  return rv;
300
300
  }
301
- timeSignature: TimeSignature = SmoMeasure.timeSignatureDefault;
301
+ timeSignature: SmoTimeSignature = SmoMeasure.timeSignatureDefault;
302
302
  /**
303
303
  * Overrides display of actual time signature, in the case of
304
304
  * pick-up notes where the actual and displayed durations are different
@@ -329,7 +329,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
329
329
  * the active voice in the editor, if there are multiple voices
330
330
  * */
331
331
  activeVoice: number = 0;
332
- tempo: SmoTempoText;
332
+ tempo: SmoTempo;
333
333
  beamGroups: ISmoBeamGroup[] = [];
334
334
  lines: number = 5;
335
335
  /**
@@ -351,7 +351,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
351
351
  * @param params
352
352
  */
353
353
  constructor(params: SmoMeasureParams) {
354
- this.tempo = new SmoTempoText(SmoTempoText.defaults);
354
+ this.tempo = new SmoTempo(SmoTempo.defaults);
355
355
  this.svg = {
356
356
  staffWidth: 0,
357
357
  unjustifiedWidth: 0,
@@ -392,7 +392,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
392
392
  this.repeatSymbol = params.repeatSymbol;
393
393
  this.measureNumber = JSON.parse(JSON.stringify(params.measureNumber));
394
394
  if (params.tempo) {
395
- this.tempo = new SmoTempoText(params.tempo);
395
+ this.tempo = new SmoTempo(params.tempo);
396
396
  }
397
397
  // Handle legacy time signature format
398
398
  if (params.timeSignature) {
@@ -400,7 +400,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
400
400
  if (typeof (tsAny) === 'string') {
401
401
  this.timeSignature = SmoMeasure.convertLegacyTimeSignature(tsAny);
402
402
  } else {
403
- this.timeSignature = TimeSignature.createFromPartial(tsAny);
403
+ this.timeSignature = SmoTimeSignature.createFromPartial(tsAny);
404
404
  }
405
405
  }
406
406
  this.voices = params.voices ? params.voices : [];
@@ -454,7 +454,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
454
454
  // Return true if the time signatures are the same, for display purposes (e.g. if a time sig change
455
455
  // is required)
456
456
  */
457
- static timeSigEqual(o1: TimeSignature, o2: TimeSignature) {
457
+ static timeSigEqual(o1: SmoTimeSignature, o2: SmoTimeSignature) {
458
458
  return o1.timeSignature === o2.timeSignature && o1.useSymbol === o2.useSymbol;
459
459
  }
460
460
  /**
@@ -568,7 +568,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
568
568
  } else if (modifier.ctor === 'SmoBarline' && (modifier as SmoBarline).position === SmoBarline.positions.end
569
569
  && (modifier as SmoBarline).barline === SmoBarline.barlines.singleBar) {
570
570
  ser = false;
571
- } else if (modifier.ctor === 'SmoTempoText') {
571
+ } else if (modifier.ctor === 'SmoTempo') {
572
572
  // we don't save tempo text as a modifier anymore
573
573
  ser = false;
574
574
  } else if ((modifier as SmoRepeatSymbol).ctor === 'SmoRepeatSymbol' && (modifier as SmoRepeatSymbol).position === SmoRepeatSymbol.positions.start
@@ -639,9 +639,9 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
639
639
 
640
640
  // explode column-mapped
641
641
  if (jsonObj.tempo) {
642
- params.tempo = SmoTempoText.deserialize(jsonObj.tempo);
642
+ params.tempo = SmoTempo.deserialize(jsonObj.tempo);
643
643
  } else {
644
- params.tempo = new SmoTempoText(SmoTempoText.defaults);
644
+ params.tempo = new SmoTempo(SmoTempo.defaults);
645
645
  }
646
646
 
647
647
  // timeSignatureString is now part of timeSignature. upconvert old scores
@@ -654,13 +654,14 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
654
654
  if (timeSignatureString.length) {
655
655
  jsonObj.timeSignature.displayString = timeSignatureString;
656
656
  }
657
- params.timeSignature = TimeSignature.deserialize(jsonObj.timeSignature);
657
+ jsonObj.timeSignature.ctor = 'SmoTimeSignature';
658
+ params.timeSignature = SmoTimeSignature.deserialize(jsonObj.timeSignature);
658
659
  } else {
659
- const tparams = TimeSignature.defaults;
660
+ const tparams = SmoTimeSignature.defaults;
660
661
  if (timeSignatureString.length) {
661
662
  tparams.displayString = timeSignatureString;
662
663
  }
663
- params.timeSignature = new TimeSignature(tparams);
664
+ params.timeSignature = new SmoTimeSignature(tparams);
664
665
  }
665
666
  params.keySignature = jsonObj.keySignature ?? 'C';
666
667
  params.voices = voices;
@@ -725,12 +726,12 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
725
726
  const measure = new SmoMeasure(params);
726
727
  // Handle migration for measure-mapped parameters
727
728
  measure.modifiers.forEach((mod) => {
728
- if (mod.ctor === 'SmoTempoText') {
729
- measure.tempo = (mod as SmoTempoText);
729
+ if (mod.ctor === 'SmoTempo') {
730
+ measure.tempo = (mod as SmoTempo);
730
731
  }
731
732
  });
732
733
  if (!measure.tempo) {
733
- measure.tempo = new SmoTempoText(SmoTempoText.defaults);
734
+ measure.tempo = new SmoTempo(SmoTempo.defaults);
734
735
  }
735
736
 
736
737
 
@@ -747,8 +748,8 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
747
748
  // Ordinarily, the key/tempo/time is mapped to the stave, but since we are pasting measure-by
748
749
  // measure here, we want to preserve it.
749
750
  clonedMeasure.keySignature = measure.keySignature;
750
- clonedMeasure.timeSignature = new TimeSignature(measure.timeSignature);
751
- clonedMeasure.tempo = new SmoTempoText(measure.tempo);
751
+ clonedMeasure.timeSignature = new SmoTimeSignature(measure.timeSignature);
752
+ clonedMeasure.tempo = new SmoTempo(measure.tempo);
752
753
  return clonedMeasure;
753
754
  }
754
755
  hasNonRestNotes(): boolean {
@@ -834,7 +835,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
834
835
  static get emptyMeasureNoteType(): NoteType {
835
836
  return SmoMeasure._emptyMeasureNoteType;
836
837
  }
837
- static timeSignatureNotes(timeSignature: TimeSignature, clef: Clef) {
838
+ static timeSignatureNotes(timeSignature: SmoTimeSignature, clef: Clef) {
838
839
  const pitch = SmoMeasure.defaultPitchForClef[clef];
839
840
  const maxTicks = SmoMusic.timeSignatureToTicks(timeSignature.timeSignature);
840
841
  const noteTick = 8192 / (timeSignature.beatDuration / 2);
@@ -869,7 +870,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
869
870
  * @returns
870
871
  */
871
872
  static getDefaultNotes(params: SmoMeasureParams): SmoNote[] {
872
- return SmoMeasure.timeSignatureNotes(new TimeSignature(params.timeSignature), params.clef);
873
+ return SmoMeasure.timeSignatureNotes(new SmoTimeSignature(params.timeSignature), params.clef);
873
874
  }
874
875
 
875
876
  /**
@@ -888,7 +889,7 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
888
889
  smoSerialize.serializedMerge(SmoMeasure.defaultAttributes, params, obj);
889
890
  // Don't copy column-formatting options to new measure in new column
890
891
  smoSerialize.serializedMerge(SmoMeasure.formattingOptions, SmoMeasure.defaults, obj);
891
- obj.timeSignature = new TimeSignature(params.timeSignature);
892
+ obj.timeSignature = new SmoTimeSignature(params.timeSignature);
892
893
  // The measure expects to get concert KS in constructor and adjust for instrument. So do the
893
894
  // opposite.
894
895
  obj.keySignature = SmoMusic.vexKeySigWithOffset(obj.keySignature, -1 * obj.transposeIndex);
@@ -1019,7 +1020,6 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
1019
1020
  }
1020
1021
  }
1021
1022
  const voices: SmoVoice[] = [];
1022
- const tuplets: SmoTuplet[] = [];
1023
1023
  for (var i = 0; i < this.voices.length; ++i) {
1024
1024
  const voice = this.voices[i];
1025
1025
  const newNotes: SmoNote[] = [];
@@ -1529,18 +1529,18 @@ export class SmoMeasure implements SmoMeasureParams, TickMappable {
1529
1529
  return this.modifiers.filter((mm) => type === mm.attrs.type);
1530
1530
  }
1531
1531
 
1532
- setTempo(params: SmoTempoTextParams) {
1533
- this.tempo = new SmoTempoText(params);
1532
+ setTempo(params: SmoTempoParams) {
1533
+ this.tempo = new SmoTempo(params);
1534
1534
  }
1535
1535
  /**
1536
- * Set measure tempo to the default {@link SmoTempoText}
1536
+ * Set measure tempo to the default {@link SmoTempo}
1537
1537
  */
1538
1538
  resetTempo() {
1539
- this.tempo = new SmoTempoText(SmoTempoText.defaults);
1539
+ this.tempo = new SmoTempo(SmoTempo.defaults);
1540
1540
  }
1541
1541
  getTempo() {
1542
1542
  if (typeof (this.tempo) === 'undefined') {
1543
- this.tempo = new SmoTempoText(SmoTempoText.defaults);
1543
+ this.tempo = new SmoTempo(SmoTempo.defaults);
1544
1544
  }
1545
1545
  return this.tempo;
1546
1546
  }
@@ -668,10 +668,10 @@ export type SmoTempoBooleanAttribute = 'display';
668
668
 
669
669
  export type SmoTempoMode = 'duration' | 'text' | 'custom';
670
670
  /**
671
- * constructor parameters for {@link SmoTempoText}
671
+ * constructor parameters for {@link SmoTempo}
672
672
  * @category SmoObject
673
673
  */
674
- export interface SmoTempoTextParams {
674
+ export interface SmoTempoParams {
675
675
  /**
676
676
  * text (e.g. Allegro) or bpm
677
677
  */
@@ -705,7 +705,7 @@ export interface SmoTempoTextParams {
705
705
  * serialized tempo parameters
706
706
  * @category serialization
707
707
  */
708
- export interface SmoTempoTextParamsSer extends SmoTempoTextParams {
708
+ export interface SmoTempoParamsSer extends SmoTempoParams {
709
709
  ctor: string;
710
710
  }
711
711
  /**
@@ -719,7 +719,7 @@ export interface VexTempoTextParams {
719
719
  * Information about both playback tempo and how the tempo is notated.
720
720
  * @category SmoObject
721
721
  */
722
- export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoTextParams {
722
+ export class SmoTempo extends SmoMeasureModifierBase implements SmoTempoParams {
723
723
  static get tempoModes(): Record<string, SmoTempoMode> {
724
724
  return {
725
725
  durationMode: 'duration',
@@ -751,12 +751,12 @@ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoText
751
751
  /**
752
752
  * create defaults for tempo initialization
753
753
  */
754
- static get defaults(): SmoTempoTextParams {
754
+ static get defaults(): SmoTempoParams {
755
755
  return JSON.parse(JSON.stringify({
756
- tempoMode: SmoTempoText.tempoModes.durationMode,
756
+ tempoMode: SmoTempo.tempoModes.durationMode,
757
757
  bpm: 120,
758
758
  beatDuration: 4096,
759
- tempoText: SmoTempoText.tempoTexts.allegro,
759
+ tempoText: SmoTempo.tempoTexts.allegro,
760
760
  yOffset: 0,
761
761
  display: false,
762
762
  customText: ''
@@ -765,7 +765,7 @@ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoText
765
765
  static get attributes() {
766
766
  return ['tempoMode', 'bpm', 'display', 'beatDuration', 'tempoText', 'yOffset', 'customText'];
767
767
  }
768
- tempoMode: SmoTempoMode = SmoTempoText.tempoModes.durationMode
768
+ tempoMode: SmoTempoMode = SmoTempo.tempoModes.durationMode
769
769
  bpm: number = 120;
770
770
  beatDuration: number = 4096;
771
771
  tempoText: string = 'Allegro';
@@ -784,14 +784,14 @@ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoText
784
784
  * @param t2
785
785
  * @returns
786
786
  */
787
- static eq(t1: SmoTempoText, t2: SmoTempoText) {
787
+ static eq(t1: SmoTempo, t2: SmoTempo) {
788
788
  if (t1.tempoMode !== t2.tempoMode) {
789
789
  return false;
790
790
  }
791
- if (t1.tempoMode === SmoTempoText.tempoModes.durationMode) {
791
+ if (t1.tempoMode === SmoTempo.tempoModes.durationMode) {
792
792
  return t1.bpm === t2.bpm && t1.beatDuration === t2.beatDuration;
793
793
  }
794
- if (t1.tempoMode === SmoTempoText.tempoModes.textMode) {
794
+ if (t1.tempoMode === SmoTempo.tempoModes.textMode) {
795
795
  return t1.tempoText === t2.tempoText;
796
796
  } else {
797
797
  return t1.bpm === t2.bpm && t1.beatDuration === t2.beatDuration &&
@@ -801,22 +801,22 @@ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoText
801
801
 
802
802
  static get bpmFromText(): Record<string, number> {
803
803
  const rv: any = {};
804
- rv[SmoTempoText.tempoTexts.larghissimo] = 24;
805
- rv[SmoTempoText.tempoTexts.grave] = 40;
806
- rv[SmoTempoText.tempoTexts.lento] = 45;
807
- rv[SmoTempoText.tempoTexts.largo] = 40;
808
- rv[SmoTempoText.tempoTexts.larghetto] = 60;
809
- rv[SmoTempoText.tempoTexts.adagio] = 72;
810
- rv[SmoTempoText.tempoTexts.adagietto] = 72;
811
- rv[SmoTempoText.tempoTexts.andante_moderato] = 72;
812
- rv[SmoTempoText.tempoTexts.andante] = 84;
813
- rv[SmoTempoText.tempoTexts.andantino] = 92;
814
- rv[SmoTempoText.tempoTexts.moderator] = 96;
815
- rv[SmoTempoText.tempoTexts.allegretto] = 96;
816
- rv[SmoTempoText.tempoTexts.allegro] = 120;
817
- rv[SmoTempoText.tempoTexts.vivace] = 144;
818
- rv[SmoTempoText.tempoTexts.presto] = 168;
819
- rv[SmoTempoText.tempoTexts.prestissimo] = 240;
804
+ rv[SmoTempo.tempoTexts.larghissimo] = 24;
805
+ rv[SmoTempo.tempoTexts.grave] = 40;
806
+ rv[SmoTempo.tempoTexts.lento] = 45;
807
+ rv[SmoTempo.tempoTexts.largo] = 40;
808
+ rv[SmoTempo.tempoTexts.larghetto] = 60;
809
+ rv[SmoTempo.tempoTexts.adagio] = 72;
810
+ rv[SmoTempo.tempoTexts.adagietto] = 72;
811
+ rv[SmoTempo.tempoTexts.andante_moderato] = 72;
812
+ rv[SmoTempo.tempoTexts.andante] = 84;
813
+ rv[SmoTempo.tempoTexts.andantino] = 92;
814
+ rv[SmoTempo.tempoTexts.moderator] = 96;
815
+ rv[SmoTempo.tempoTexts.allegretto] = 96;
816
+ rv[SmoTempo.tempoTexts.allegro] = 120;
817
+ rv[SmoTempo.tempoTexts.vivace] = 144;
818
+ rv[SmoTempo.tempoTexts.presto] = 168;
819
+ rv[SmoTempo.tempoTexts.prestissimo] = 240;
820
820
 
821
821
  return rv as Record<string, number>;
822
822
  }
@@ -832,29 +832,32 @@ export class SmoTempoText extends SmoMeasureModifierBase implements SmoTempoText
832
832
  return rv;
833
833
  }
834
834
  toVexTempo(): VexTempoTextParams {
835
- if (this.tempoMode === SmoTempoText.tempoModes.durationMode ||
836
- this.tempoMode === SmoTempoText.tempoModes.customMode) {
835
+ if (this.tempoMode === SmoTempo.tempoModes.durationMode ||
836
+ this.tempoMode === SmoTempo.tempoModes.customMode) {
837
837
  return this._toVexDurationTempo();
838
838
  }
839
839
  return this._toVexTextTempo();
840
840
  }
841
- serialize(): SmoTempoTextParamsSer {
842
- var params: Partial<SmoTempoTextParamsSer> = {};
843
- smoSerialize.serializedMergeNonDefault(SmoTempoText.defaults, SmoTempoText.attributes, this, params);
844
- params.ctor = 'SmoTempoText';
845
- return params as SmoTempoTextParamsSer;
841
+ serialize(): SmoTempoParamsSer {
842
+ var params: Partial<SmoTempoParamsSer> = {};
843
+ smoSerialize.serializedMergeNonDefault(SmoTempo.defaults, SmoTempo.attributes, this, params);
844
+ params.ctor = 'SmoTempo';
845
+ return params as SmoTempoParamsSer;
846
846
  }
847
- constructor(parameters: SmoTempoTextParams | null) {
848
- super('SmoTempoText');
847
+ constructor(parameters: SmoTempoParams | null) {
848
+ super('SmoTempo');
849
849
  let pobj: any = parameters;
850
850
  if (typeof (pobj) === 'undefined' || pobj === null) {
851
851
  pobj = {};
852
852
  }
853
- smoSerialize.serializedMerge(SmoTempoText.attributes, SmoTempoText.defaults, this);
854
- smoSerialize.serializedMerge(SmoTempoText.attributes, pobj, this);
853
+ smoSerialize.serializedMerge(SmoTempo.attributes, SmoTempo.defaults, this);
854
+ smoSerialize.serializedMerge(SmoTempo.attributes, pobj, this);
855
855
  }
856
856
  }
857
-
857
+ export interface TimeSignatureTime {
858
+ actualBeats: number,
859
+ beatDuration: number
860
+ }
858
861
  /**
859
862
  * Constructor parameters for a time signature
860
863
  * @category SmoObject
@@ -863,14 +866,7 @@ export interface TimeSignatureParameters {
863
866
  /**
864
867
  * numerator
865
868
  */
866
- actualBeats: number,
867
- /**
868
- * denominator, always power of 2
869
- */
870
- beatDuration: number,
871
- /**
872
- * indicates cut time/common time
873
- */
869
+ times: TimeSignatureTime[],
874
870
  useSymbol: boolean,
875
871
  /**
876
872
  * display, else just affects measure lengths.
@@ -879,7 +875,7 @@ export interface TimeSignatureParameters {
879
875
  /**
880
876
  * for pickups, display the non-pickup value
881
877
  */
882
- displayString: string
878
+ displayString: string,
883
879
  }
884
880
 
885
881
  /**
@@ -897,54 +893,93 @@ export interface TimeSignatureParametersSer extends TimeSignatureParameters {
897
893
  * about the display of the time signature.
898
894
  * @category SmoObject
899
895
  */
900
- export class TimeSignature extends SmoMeasureModifierBase {
896
+ export class SmoTimeSignature extends SmoMeasureModifierBase {
901
897
  static get defaults(): TimeSignatureParameters {
902
898
  return {
903
- actualBeats: 4,
904
- beatDuration: 4,
899
+ times: [{ actualBeats: 4, beatDuration: 4 }],
905
900
  useSymbol: false,
906
901
  display: true,
907
- displayString: ''
902
+ displayString: '',
908
903
  };
909
904
  }
910
- static equal(ts1: TimeSignature, ts2: TimeSignature): boolean {
911
- return (ts1.actualBeats === ts2.actualBeats && ts1.beatDuration === ts2.beatDuration);
905
+ ticksFromTimeSignature() {
906
+ let max = 0;
907
+ for (let i = 0; i < this.times.length; i++) {
908
+ const bt = 4096 * (4/this.times[i].beatDuration);
909
+ max = Math.max(max, bt * this.times[i].actualBeats);
910
+ }
911
+ return max;
912
+ }
913
+ static equal(ts1: SmoTimeSignature, ts2: SmoTimeSignature): boolean {
914
+ // legacy hack, this can be called before the constructor is called, since these classes used to
915
+ // just be strings. So create the objects.
916
+ const ts1obj = SmoTimeSignature.createFromPartial(ts1);
917
+ const ts2obj = SmoTimeSignature.createFromPartial(ts2);
918
+ if (ts1obj.times.length !== ts2obj.times.length) {
919
+ return false;
920
+ }
921
+ for (let i = 0; i < ts1obj.times.length; i++) {
922
+ if (ts1obj.times[i].actualBeats !== ts2obj.times[i].actualBeats ||
923
+ ts1obj.times[i].beatDuration !== ts2obj.times[i].beatDuration) {
924
+ return false;
925
+ }
926
+ }
927
+ return true;
912
928
  }
913
929
  static createFromPartial(value: Partial<TimeSignatureParameters>) {
914
- const params = TimeSignature.defaults;
915
- smoSerialize.serializedMerge(TimeSignature.parameters, value, params);
916
- return new TimeSignature(params);
930
+ const params = SmoTimeSignature.defaults;
931
+ smoSerialize.serializedMerge(SmoTimeSignature.parameters, value, params);
932
+ return new SmoTimeSignature(params);
917
933
  }
918
934
  // timeSignature: string = '4/4';
919
- actualBeats: number = 4;
920
- beatDuration: number = 4;
935
+ times: TimeSignatureTime[] = [{ actualBeats: 4, beatDuration: 4 }];
921
936
  useSymbol: boolean = false;
922
937
  display: boolean = true;
923
938
  displayString: string = '';
939
+ index: number = 0;
940
+ get beatDuration() {
941
+ return this.times[this.index].beatDuration;
942
+ }
943
+ get actualBeats() { return this.times[this.index].actualBeats;
944
+ }
924
945
  get timeSignature() {
925
- return this.actualBeats.toString() + '/' + this.beatDuration.toString();
946
+ let str: string[] = [];
947
+ this.times.forEach((time) => {
948
+ str.push(`${time.actualBeats}/${time.beatDuration}+`);
949
+ });
950
+ return str.join('+');
926
951
  }
927
952
  static get parameters() {
928
- return ['actualBeats', 'beatDuration', 'useSymbol', 'display', 'displayString'];
953
+ return ['times', 'useSymbol', 'display', 'displayString'];
929
954
  }
930
955
  static get boolParameters() {
931
956
  return [];
932
957
  }
933
958
  set timeSignature(value: string) {
934
- const ar = value.split('/');
935
- this.actualBeats = parseInt(ar[0], 10);
936
- this.beatDuration = parseInt(ar[1], 10);
959
+ const timeStrings = value.split('+');
960
+ this.times = [];
961
+ for (let i = 0; i < timeStrings.length; i++) {
962
+ const ar = timeStrings[i].split('/');
963
+ if (ar.length !== 2) {
964
+ throw new Error('invalid time signature string');
965
+ }
966
+ const actualBeats = parseInt(ar[0], 10);
967
+ const beatDuration = parseInt(ar[1], 10);
968
+ this.times.push({ actualBeats, beatDuration });
969
+ }
937
970
  }
938
971
  serialize(): TimeSignatureParametersSer {
939
972
  const rv: Partial<TimeSignatureParametersSer> = {};
940
- smoSerialize.serializedMergeNonDefault(TimeSignature.defaults, TimeSignature.parameters, this, rv);
941
- rv.ctor = 'TimeSignature';
973
+ smoSerialize.serializedMergeNonDefault(SmoTimeSignature.defaults, SmoTimeSignature.parameters, this, rv);
974
+ rv.ctor = 'SmoTimeSignature';
942
975
  return rv as TimeSignatureParametersSer;
943
976
  }
944
977
  constructor(params: TimeSignatureParameters) {
945
- super('TimeSignature');
946
- this.actualBeats = params.actualBeats;
947
- this.beatDuration = params.beatDuration;
978
+ super('SmoTimeSignature');
979
+ if (!params.times || !params.times.length) {
980
+ params.times = SmoTimeSignature.defaults.times;
981
+ }
982
+ this.times = JSON.parse(JSON.stringify(params.times));
948
983
  this.useSymbol = params.useSymbol;
949
984
  this.display = params.display;
950
985
  this.displayString = params.displayString;
@@ -957,6 +992,6 @@ export const measureModifierDynamicCtorInit = () => {
957
992
  SmoDynamicCtor['SmoVolta'] = (params: SmoVoltaParams) => new SmoVolta(params);
958
993
  SmoDynamicCtor['SmoMeasureText'] = (params: SmoMeasureTextParams) => new SmoMeasureText(params);
959
994
  SmoDynamicCtor['SmoRehearsalMark'] = (params: SmoRehearsalMarkParams) => new SmoRehearsalMark(params);
960
- SmoDynamicCtor['SmoTempoText'] = (params: SmoTempoTextParams) => new SmoTempoText(params);
961
- SmoDynamicCtor['TimeSignature'] = (params: TimeSignatureParameters) => new TimeSignature(params);
995
+ SmoDynamicCtor['SmoTempo'] = (params: SmoTempoParams) => new SmoTempo(params);
996
+ SmoDynamicCtor['SmoTimeSignature'] = (params: TimeSignatureParameters) => new SmoTimeSignature(params);
962
997
  }
@@ -312,7 +312,10 @@ export class SmoNote implements Transposable {
312
312
  if ((params as any).tuplet) {
313
313
  this.tupletId = (params as any).tuplet.id;
314
314
  }
315
-
315
+ // For imported notes, stem ticks is only different for tuplets
316
+ if (!this.isTuplet) {
317
+ this.stemTicks = this.tickCount;
318
+ }
316
319
  this.attrs = {
317
320
  id: getId().toString(),
318
321
  type: 'SmoNote'