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
@@ -9,7 +9,7 @@ import { Clef, SvgDimensions } from './common';
9
9
  import { SmoMeasure, SmoMeasureParams, ColumnMappedParams, SmoMeasureParamsSer } from './measure';
10
10
  import { SmoNoteModifierBase } from './noteModifiers';
11
11
  import {
12
- SmoTempoText, SmoMeasureFormat, SmoMeasureModifierBase, TimeSignature, TimeSignatureParameters,
12
+ SmoTempo, SmoMeasureFormat, SmoMeasureModifierBase, SmoTimeSignature, TimeSignatureParameters,
13
13
  SmoMeasureFormatParamsSer
14
14
  } from './measureModifiers';
15
15
  import { StaffModifierBase, SmoInstrument } from './staffModifiers';
@@ -153,8 +153,8 @@ export function isEmptyTextBlock(params: Partial<SmoTextGroupParamsSer>): params
153
153
  */
154
154
  export interface ColumnParamsMapType {
155
155
  keySignature: Record<number, string>,
156
- tempo: Record<number, SmoTempoText>,
157
- timeSignature: Record<number, TimeSignature>,
156
+ tempo: Record<number, SmoTempo>,
157
+ timeSignature: Record<number, SmoTimeSignature>,
158
158
  renumberingMap: Record<number, number>
159
159
  }
160
160
 
@@ -351,8 +351,8 @@ export class SmoScore {
351
351
  */
352
352
  serializeColumnMapped(func: (measure: SmoMeasure) => ColumnMappedParams) {
353
353
  const keySignature: Record<number, string> = {};
354
- const tempo: Record<number, SmoTempoText> = {};
355
- const timeSignature: Record<number, TimeSignature> = {};
354
+ const tempo: Record<number, SmoTempo> = {};
355
+ const timeSignature: Record<number, SmoTimeSignature> = {};
356
356
  const renumberingMap: Record<number, number> = {};
357
357
  let previous: ColumnMappedParams | null = null;
358
358
  this.staves[0].measures.forEach((measure) => {
@@ -381,11 +381,11 @@ export class SmoScore {
381
381
  previous!.keySignature = current.keySignature;
382
382
  keySignature[ix] = current.keySignature;
383
383
  }
384
- if (!(TimeSignature.equal(current.timeSignature, previous!.timeSignature))) {
384
+ if (!(SmoTimeSignature.equal(current.timeSignature, previous!.timeSignature))) {
385
385
  previous!.timeSignature = current.timeSignature;
386
386
  timeSignature[ix] = current.timeSignature;
387
387
  }
388
- if (!(SmoTempoText.eq(current.tempo, previous!.tempo))) {
388
+ if (!(SmoTempo.eq(current.tempo, previous!.tempo))) {
389
389
  previous!.tempo = current.tempo;
390
390
  tempo[ix] = current.tempo;
391
391
  }
@@ -393,7 +393,45 @@ export class SmoScore {
393
393
  });
394
394
  return { keySignature, tempo, timeSignature, renumberingMap };
395
395
  }
396
-
396
+ static fixLegacyColumnMappedCtor(scoreObj: any){
397
+ if (!scoreObj.columnAttributeMap) {
398
+ return;
399
+ }
400
+ if (!scoreObj.columnAttributeMap.tempo) {
401
+ return;
402
+ }
403
+ const tempoKeys = Object.keys(scoreObj.columnAttributeMap.tempo);
404
+ tempoKeys.forEach((key) => {
405
+ const t = scoreObj.columnAttributeMap.tempo[key];
406
+ t.ctor = 'SmoTempo';
407
+ });
408
+ const tsKeys = Object.keys(scoreObj.columnAttributeMap.timeSignature);
409
+ tsKeys.forEach((key) => {
410
+ const val = scoreObj.columnAttributeMap.timeSignature[key];
411
+ // Very legacy time signature format was just a string like 4/4, 3/8 etc.
412
+ if (typeof(val) === 'string') {
413
+ const nums = val.split('/');
414
+ const num = parseInt(nums[0], 10);
415
+ const denom = parseInt(nums[1], 10);
416
+ scoreObj.columnAttributeMap.timeSignature[key] = { times: [{ actualBeats: num, beatDuration: denom }], ctor: 'SmoTimeSignature' };
417
+ } else {
418
+ const ts = val;
419
+ ts.ctor = 'SmoTimeSignature';
420
+ // map legacy time signature format to compound time signature format
421
+ let beatDuration = 4;
422
+ if (ts.beatDuration && !isNaN(ts.beatDuration)) {
423
+ beatDuration = ts.beatDuration;
424
+ }
425
+ if (ts.actualBeats && !isNaN(ts.actualBeats)) {
426
+ ts.times = [{ actualBeats: ts.actualBeats, beatDuration }];
427
+ }
428
+ // Default will have nothing serialized
429
+ if (!ts.times) {
430
+ ts.times = [{ actualBeats: 4, beatDuration: 4 }];
431
+ }
432
+ }
433
+ });
434
+ }
397
435
  /**
398
436
  * Column-mapped attributes stay the same in each measure until
399
437
  * changed, like key-signatures. We don't store each measure value to
@@ -407,6 +445,7 @@ export class SmoScore {
407
445
  if (!scoreObj.columnAttributeMap) {
408
446
  return;
409
447
  }
448
+ SmoScore.fixLegacyColumnMappedCtor(scoreObj);
410
449
  const attrs = Object.keys(scoreObj.columnAttributeMap);
411
450
  scoreObj.staves.forEach((staff: any) => {
412
451
  const attrIxMap: any = {};
@@ -429,12 +468,12 @@ export class SmoScore {
429
468
  }
430
469
  // legacy timeSignature format was just a string 2/4, 3/8 etc.
431
470
  if (attr === 'timeSignature') {
432
- const ts = new TimeSignature(TimeSignature.defaults);
471
+ const ts = new SmoTimeSignature(SmoTimeSignature.defaults);
433
472
  if (typeof (curValue) === 'string') {
434
473
  ts.timeSignature = curValue;
435
474
  measure[attr] = ts;
436
475
  } else {
437
- measure[attr] = TimeSignature.createFromPartial(curValue);
476
+ measure[attr] = SmoTimeSignature.createFromPartial(curValue);
438
477
  }
439
478
  } else {
440
479
  measure[attr] = curValue;
@@ -878,7 +917,7 @@ export class SmoScore {
878
917
  if (measureIndex > 0) {
879
918
  const measure = this.staves[0].measures[measureIndex];
880
919
  const prev = this.staves[0].measures[measureIndex - 1];
881
- if (!start && !TimeSignature.equal(measure.timeSignature, prev.timeSignature)) {
920
+ if (!start && !SmoTimeSignature.equal(measure.timeSignature, prev.timeSignature)) {
882
921
  return false;
883
922
  }
884
923
  if (!start && measure.keySignature !== prev.keySignature) {
@@ -9,7 +9,7 @@ import { SmoObjectParams, SmoAttrs, MeasureNumber, getId,
9
9
  Pitch, ElementLike } from './common';
10
10
  import { SmoMusic } from './music';
11
11
  import { SmoMeasure, SmoMeasureParamsSer } from './measure';
12
- import { SmoMeasureFormat, SmoRehearsalMark, SmoRehearsalMarkParams, SmoTempoTextParams, SmoVolta, SmoBarline } from './measureModifiers';
12
+ import { SmoMeasureFormat, SmoRehearsalMark, SmoRehearsalMarkParams, SmoTempoParams, SmoVolta, SmoBarline } from './measureModifiers';
13
13
  import { SmoInstrumentParams, StaffModifierBase, SmoInstrument, SmoInstrumentMeasure, SmoInstrumentStringParams, SmoInstrumentNumParams,
14
14
  SmoTie, SmoStaffTextBracket, SmoStaffTextBracketParamsSer,
15
15
  StaffModifierBaseSer, SmoTabStave, SmoStaffHairpin, TieLine,
@@ -851,7 +851,7 @@ export class SmoSystemStaff implements SmoObjectParams {
851
851
  this.measures[index].resetTempo();
852
852
  }
853
853
 
854
- addTempo(tempo: SmoTempoTextParams, index: number) {
854
+ addTempo(tempo: SmoTempoParams, index: number) {
855
855
  this.measures[index].setTempo(tempo);
856
856
  }
857
857
 
@@ -7,7 +7,7 @@
7
7
  import { SmoToMidi } from "./smoToMidi";
8
8
  import { Clef, Pitch } from "../data/common";
9
9
  import { SmoMeasure } from "../data/measure";
10
- import { SmoTempoText, TimeSignature } from "../data/measureModifiers";
10
+ import { SmoTempo, SmoTimeSignature } from "../data/measureModifiers";
11
11
  import { SmoMusic } from "../data/music";
12
12
  import { SmoNote } from "../data/note";
13
13
  import { SmoScore } from "../data/score";
@@ -18,7 +18,7 @@ import {SmoTuplet, SmoTupletTree} from "../data/tuplet";
18
18
  import { SmoOperation } from "../xform/operations";
19
19
 
20
20
  export type MidiEventType = 'text' | 'copyrightNotice' | 'trackName' | 'instrumentName' | 'lyrics' | 'marker' |
21
- 'cuePoint' | 'channelPrefix' | 'portPrefix' | 'endOfTrack' | 'setTempo' | 'smpteOffset' | 'timeSignature' | 'keySignature' |
21
+ 'cuePoint' | 'channelPrefix' | 'portPrefix' | 'endOfTrack' | 'setTempo' | 'smpteOffset' | 'SmoTimeSignature' | 'keySignature' |
22
22
  'sequencerSpecific' | 'unknownMeta' |
23
23
  'noteOff' | 'noteOn' | 'noteAftertouch' | 'controller' | 'programChange' | 'channelAftertouch' | 'pitchBend';
24
24
 
@@ -46,8 +46,8 @@ export interface MidiTrackEvent {
46
46
  */
47
47
  export interface RunningMetadata {
48
48
  keySignature: string,
49
- timeSignature: TimeSignature,
50
- tempo: SmoTempoText
49
+ timeSignature: SmoTimeSignature,
50
+ tempo: SmoTempo
51
51
  }
52
52
  /**
53
53
  * @category serialization
@@ -75,8 +75,8 @@ export interface EventSmoData {
75
75
  tupletInfo: MidiTupletInfo | null,
76
76
  isRest: boolean,
77
77
  isTied: boolean,
78
- timeSignature: TimeSignature,
79
- tempo: SmoTempoText,
78
+ timeSignature: SmoTimeSignature,
79
+ tempo: SmoTempo,
80
80
  keySignature: string,
81
81
  measure: number,
82
82
  tick: number
@@ -100,8 +100,8 @@ export function getValueForTick<T>(arg: Record<number, T>, tick: number) {
100
100
  * @category serialization
101
101
  */
102
102
  export class MidiToSmo {
103
- timeSignatureMap: Record<number, TimeSignature> = {};
104
- tempoMap: Record<number, SmoTempoText> = {};
103
+ SmoTimeSignatureMap: Record<number, SmoTimeSignature> = {};
104
+ tempoMap: Record<number, SmoTempo> = {};
105
105
  keySignatureMap: Record<number, string> = {};
106
106
  tieMap: Record<number, number[]> = {};
107
107
  timeDivision: number = 480;
@@ -153,8 +153,8 @@ export class MidiToSmo {
153
153
  constructor(midi: any, quantizeDuration: number) {
154
154
  this.midi = midi;
155
155
  // console.log(JSON.stringify(midi, null, ''));
156
- this.timeSignatureMap[0] = new TimeSignature(TimeSignature.defaults);
157
- this.tempoMap[0] = new SmoTempoText(SmoTempoText.defaults);
156
+ this.SmoTimeSignatureMap[0] = new SmoTimeSignature(SmoTimeSignature.defaults);
157
+ this.tempoMap[0] = new SmoTempo(SmoTempo.defaults);
158
158
  this.keySignatureMap[0] = 'c';
159
159
  this.timeDivision = midi.header.ticksPerBeat;
160
160
  this.quantizeTicks = quantizeDuration;
@@ -175,11 +175,11 @@ export class MidiToSmo {
175
175
  * @param ticks
176
176
  * @returns
177
177
  */
178
- getTimeSignature(ticks: number): TimeSignature {
179
- if (this.timeSignatureMap[ticks]) {
180
- return this.timeSignatureMap[ticks];
178
+ getSmoTimeSignature(ticks: number): SmoTimeSignature {
179
+ if (this.SmoTimeSignatureMap[ticks]) {
180
+ return this.SmoTimeSignatureMap[ticks];
181
181
  }
182
- return getValueForTick(this.timeSignatureMap, ticks);
182
+ return getValueForTick(this.SmoTimeSignatureMap, ticks);
183
183
  }
184
184
  /**
185
185
  * @internal
@@ -199,7 +199,7 @@ export class MidiToSmo {
199
199
  */
200
200
  getMetadata(ticks: number) {
201
201
  return { tempo: this.getTempo(ticks),
202
- timeSignature: this.getTimeSignature(ticks), keySignature: this.getKeySignature(ticks).toLowerCase() };
202
+ timeSignature: this.getSmoTimeSignature(ticks), keySignature: this.getKeySignature(ticks).toLowerCase() };
203
203
  }
204
204
  /**
205
205
  * We process 3 types of metadata at present: time signature, tempo and keysignature.
@@ -209,22 +209,21 @@ export class MidiToSmo {
209
209
  handleMetadata(trackEvent: MidiTrackEvent, ticks: number) {
210
210
  if (trackEvent.meta) {
211
211
  const mtype = trackEvent.type;
212
- if (mtype === 'timeSignature') {
212
+ if (mtype === 'SmoTimeSignature') {
213
213
  /**
214
214
  * whenever we get a time signature event, recompute ticks per measure
215
215
  */
216
216
  const numerator = trackEvent.numerator!;
217
217
  const denominator = trackEvent.denominator!;
218
- const tsDef = TimeSignature.defaults;
219
- tsDef.actualBeats = numerator;
220
- tsDef.beatDuration = denominator;
221
- const ts = new TimeSignature(tsDef);
222
- this.timeSignatureMap[ticks] = ts;
218
+ const tsDef = SmoTimeSignature.defaults;
219
+ tsDef.times.push({actualBeats: numerator, beatDuration:denominator});
220
+ const ts = new SmoTimeSignature(tsDef);
221
+ this.SmoTimeSignatureMap[ticks] = ts;
223
222
  } else if (mtype === 'setTempo') {
224
223
  const bpm = 60 / (trackEvent.microsecondsPerBeat! / 1000000);
225
- const tempoDef = SmoTempoText.defaults;
224
+ const tempoDef = SmoTempo.defaults;
226
225
  tempoDef.bpm = bpm;
227
- this.tempoMap[ticks] = new SmoTempoText(tempoDef);
226
+ this.tempoMap[ticks] = new SmoTempo(tempoDef);
228
227
  } else if (mtype === 'keySignature') {
229
228
  const mdata = trackEvent.key!;
230
229
  if (mdata === 0) {
@@ -255,8 +254,8 @@ export class MidiToSmo {
255
254
  */
256
255
  createNewEvent(metadata: RunningMetadata): EventSmoData {
257
256
  return {
258
- pitches: [], durationTicks: 0, tupletInfo: null, isRest: false, timeSignature: new TimeSignature(metadata.timeSignature),
259
- tempo: new SmoTempoText(metadata.tempo), keySignature: metadata.keySignature, measure: 0, tick: 0, isTied: false
257
+ pitches: [], durationTicks: 0, tupletInfo: null, isRest: false, timeSignature: new SmoTimeSignature(metadata.timeSignature),
258
+ tempo: new SmoTempo(metadata.tempo), keySignature: metadata.keySignature, measure: 0, tick: 0, isTied: false
260
259
  };
261
260
  }
262
261
  /**
@@ -264,8 +263,8 @@ export class MidiToSmo {
264
263
  */
265
264
  static copyEvent(o: EventSmoData): EventSmoData {
266
265
  const pitches = JSON.parse(JSON.stringify(o.pitches));
267
- const timeSignature = new TimeSignature(o.timeSignature);
268
- const tempo = new SmoTempoText(o.tempo);
266
+ const timeSignature = new SmoTimeSignature(o.timeSignature);
267
+ const tempo = new SmoTempo(o.tempo);
269
268
  return ({
270
269
  pitches, durationTicks: o.durationTicks, tupletInfo: o.tupletInfo, isRest: o.isRest, timeSignature, tempo, keySignature: o.keySignature,
271
270
  measure: o.measure, tick: o.tick, isTied: o.isTied
@@ -298,8 +297,8 @@ export class MidiToSmo {
298
297
  if (measure === null || ev.measure > measureIndex) {
299
298
  const measureDefs = SmoMeasure.defaults;
300
299
  measureDefs.keySignature = ev.keySignature;
301
- measureDefs.timeSignature = new TimeSignature(ev.timeSignature);
302
- measureDefs.tempo = new SmoTempoText(ev.tempo);
300
+ measureDefs.timeSignature = new SmoTimeSignature(ev.timeSignature);
301
+ measureDefs.tempo = new SmoTempo(ev.tempo);
303
302
  measure = new SmoMeasure(measureDefs);
304
303
  measure.voices.push({ notes: [] });
305
304
  measureIndex = ev.measure;
@@ -5,7 +5,7 @@
5
5
  * @module /smo/midi/smoToMidi
6
6
  */
7
7
  import { SmoMusic } from '../data/music';
8
- import { TimeSignature, SmoTempoText } from '../data/measureModifiers';
8
+ import { SmoTimeSignature, SmoTempo } from '../data/measureModifiers';
9
9
  import { ScoreRoadMapBuilder } from '../xform/roadmap';
10
10
  import { SmoScore } from '../data/score';
11
11
  import { PopulateAudioData } from '../xform/updateAudio';
@@ -67,8 +67,8 @@ import Writer from 'midi-writer-js';
67
67
  export interface MidiTrackHash {
68
68
  track: any,
69
69
  lastMeasure: number,
70
- timeSignature?: TimeSignature,
71
- tempo?: SmoTempoText,
70
+ timeSignature?: SmoTimeSignature,
71
+ tempo?: SmoTempo,
72
72
  keySignature?: string
73
73
  }
74
74
  function stavesToSkip(score: SmoScore): number[] {
@@ -6,7 +6,7 @@ import { SmoMusic } from '../data/music';
6
6
  import { SmoMeasure, SmoVoice } from '../data/measure';
7
7
  import { SmoSystemStaff } from '../data/systemStaff';
8
8
  import { SmoScore } from '../data/score';
9
- import { SmoBarline, TimeSignature, SmoRehearsalMark, SmoMeasureModifierBase } from '../data/measureModifiers';
9
+ import { SmoBarline, SmoTimeSignature, SmoRehearsalMark, SmoMeasureModifierBase } from '../data/measureModifiers';
10
10
  import { SmoStaffHairpin, SmoSlur, SmoTie } from '../data/staffModifiers';
11
11
  import { SmoArticulation, SmoLyric, SmoOrnament } from '../data/noteModifiers';
12
12
  import { SmoSelector } from '../xform/selections';
@@ -14,7 +14,7 @@ import { SmoTuplet, SmoTupletTree } from '../data/tuplet';
14
14
 
15
15
  import { XmlHelpers } from './xmlHelpers';
16
16
  import { smoSerialize } from '../../common/serializationHelpers';
17
- import { SmoTempoText } from '../data/measureModifiers';
17
+ import { SmoTempo } from '../data/measureModifiers';
18
18
  import { XmlToSmo } from './xmlToSmo';
19
19
  import { SmoSystemGroup } from '../data/scoreModifiers';
20
20
  import { SuiSampleMedia } from '../../render/audio/samples';
@@ -51,8 +51,8 @@ export interface SmoState {
51
51
  note?: SmoNote,
52
52
  beamState: number,
53
53
  beamTicks: number,
54
- timeSignature?: TimeSignature,
55
- tempo?: SmoTempoText,
54
+ timeSignature?: SmoTimeSignature,
55
+ tempo?: SmoTempo,
56
56
  currentTupletLevel: number, // not sure about the name
57
57
  }
58
58
 
@@ -637,13 +637,13 @@ export class SmoToXml {
637
637
  if (smoState.tempo) {
638
638
  if (tempo.display && measure.measureNumber.measureIndex === 0 && smoState.measureTicks === 0) {
639
639
  displayTempo = true;
640
- } else if (tempo.display && !SmoTempoText.eq(smoState.tempo, tempo)) {
640
+ } else if (tempo.display && !SmoTempo.eq(smoState.tempo, tempo)) {
641
641
  displayTempo = true;
642
642
  }
643
643
  } else {
644
644
  displayTempo = true;
645
645
  }
646
- smoState.tempo = new SmoTempoText(tempo);
646
+ smoState.tempo = new SmoTempo(tempo);
647
647
  if (beforeNote === true && smoState.staffPartIx === 0 && smoState.measureTicks === 0 && smoState.partStaves[0].staffId === 0) {
648
648
  const mark: SmoMeasureModifierBase | undefined = measure.getRehearsalMark();
649
649
  if (mark) {
@@ -661,12 +661,12 @@ export class SmoToXml {
661
661
  const tempoElement = nn(directionElement, 'direction-type', null, '');
662
662
  XmlHelpers.createAttribute(directionElement, 'placement', 'above');
663
663
  let tempoText = tempo.tempoText;
664
- if (tempo.tempoMode === SmoTempoText.tempoModes.customMode) {
664
+ if (tempo.tempoMode === SmoTempo.tempoModes.customMode) {
665
665
  tempoText = tempo.customText;
666
666
  }
667
- if (tempo.tempoMode === SmoTempoText.tempoModes.textMode) {
667
+ if (tempo.tempoMode === SmoTempo.tempoModes.textMode) {
668
668
  nn(tempoElement, 'words', { words: tempoText }, 'words');
669
- } else if (tempo.tempoMode === SmoTempoText.tempoModes.customMode || tempo.tempoMode === SmoTempoText.tempoModes.durationMode) {
669
+ } else if (tempo.tempoMode === SmoTempo.tempoModes.customMode || tempo.tempoMode === SmoTempo.tempoModes.durationMode) {
670
670
  const metronomeElement = nn(tempoElement, 'metronome', null, '');
671
671
  let durationType = 'quarter';
672
672
  let dotType = false;
@@ -901,8 +901,8 @@ export class SmoToXml {
901
901
  const nn = XmlHelpers.createTextElementChild;
902
902
  const staff = smoState.partStaves[smoState.staffPartIx];
903
903
  const measure = staff.measures[smoState.measureIndex];
904
- const currentTs = (smoState.timeSignature as TimeSignature) ?? null;
905
- if (currentTs !== null && TimeSignature.equal(currentTs, measure.timeSignature)) {
904
+ const currentTs = (smoState.timeSignature as SmoTimeSignature) ?? null;
905
+ if (currentTs !== null && SmoTimeSignature.equal(currentTs, measure.timeSignature)) {
906
906
  return;
907
907
  }
908
908
  smoState.timeSignature = measure.timeSignature;
@@ -13,7 +13,7 @@ import {
13
13
  SmoTie,
14
14
  TieLine
15
15
  } from '../data/staffModifiers';
16
- import {SmoBarline, SmoMeasureModifierBase, SmoRehearsalMark, SmoTempoText} from '../data/measureModifiers';
16
+ import {SmoBarline, SmoMeasureModifierBase, SmoRehearsalMark, SmoTempo} from '../data/measureModifiers';
17
17
  import {SmoPartInfo} from '../data/partInfo';
18
18
  import {SmoMeasure} from '../data/measure';
19
19
  import {SmoNote} from '../data/note';
@@ -164,7 +164,7 @@ export interface XmlPartGroup {
164
164
  export class XmlState {
165
165
  static get defaults() {
166
166
  return {
167
- divisions: 4096, tempo: new SmoTempoText(SmoTempoText.defaults), timeSignature: '4/4', keySignature: 'c',
167
+ divisions: 4096, tempo: new SmoTempo(SmoTempo.defaults), timeSignature: '4/4', keySignature: 'c',
168
168
  clefInfo: [], staffGroups: [], smoStaves: []
169
169
  };
170
170
  }
@@ -195,7 +195,7 @@ export class XmlState {
195
195
  tupletStatesInProgress: Record<number, XmlTupletState> = {};
196
196
 
197
197
  tickCursor: number = 0;
198
- tempo: SmoTempoText = new SmoTempoText(SmoTempoText.defaults);
198
+ tempo: SmoTempo = new SmoTempo(SmoTempo.defaults);
199
199
  staffArray: XmlStaffInfo[] = [];
200
200
  staffIndex: number = 0;
201
201
  graceNotes: SmoGraceNote[] = [];
@@ -8,7 +8,7 @@ import { XmlHelpers } from './xmlHelpers';
8
8
  import { XmlVoiceInfo, XmlState, XmlWedgeInfo } from './xmlState';
9
9
  import { SmoLayoutManager, SmoPageLayout, SmoSystemGroup } from '../data/scoreModifiers';
10
10
  import { SmoTextGroup } from '../data/scoreText';
11
- import { SmoTempoText, SmoMeasureFormat, SmoMeasureModifierBase, SmoVolta, SmoBarline } from '../data/measureModifiers';
11
+ import { SmoTempo, SmoMeasureFormat, SmoMeasureModifierBase, SmoVolta, SmoBarline } from '../data/measureModifiers';
12
12
  import { SmoScore, isEngravingFont } from '../data/score';
13
13
  import { SmoMeasure, SmoMeasureParams } from '../data/measure';
14
14
  import { SmoMusic } from '../data/music';
@@ -397,11 +397,11 @@ export class XmlToSmo {
397
397
  static tempo(element: Element) {
398
398
  let tempoText = '';
399
399
  let customText = tempoText;
400
- const rv: { staffId: number, tempo: SmoTempoText }[] = [];
400
+ const rv: { staffId: number, tempo: SmoTempo }[] = [];
401
401
  const soundNodes = XmlHelpers.getChildrenFromPath(element,
402
402
  ['sound']);
403
403
  soundNodes.forEach((sound) => {
404
- let tempoMode = SmoTempoText.tempoModes.durationMode;
404
+ let tempoMode = SmoTempo.tempoModes.durationMode;
405
405
  tempoText = sound.getAttribute('tempo') as string;
406
406
  if (tempoText) {
407
407
  const bpm = parseInt(tempoText, 10);
@@ -410,20 +410,20 @@ export class XmlToSmo {
410
410
  tempoText = wordNode.length ? wordNode[0].textContent as string :
411
411
  tempoText.toString();
412
412
  if (isNaN(parseInt(tempoText, 10))) {
413
- if (SmoTempoText.tempoTexts[tempoText.toLowerCase()]) {
414
- tempoMode = SmoTempoText.tempoModes.textMode;
413
+ if (SmoTempo.tempoTexts[tempoText.toLowerCase()]) {
414
+ tempoMode = SmoTempo.tempoModes.textMode;
415
415
  } else {
416
- tempoMode = SmoTempoText.tempoModes.customMode;
416
+ tempoMode = SmoTempo.tempoModes.customMode;
417
417
  customText = tempoText;
418
418
  }
419
419
  }
420
- const params = SmoTempoText.defaults;
420
+ const params = SmoTempo.defaults;
421
421
  params.tempoMode = tempoMode;
422
422
  params.bpm = bpm;
423
423
  params.tempoText = tempoText;
424
424
  params.customText = customText;
425
425
  params.display = true;
426
- const tempo = new SmoTempoText(params);
426
+ const tempo = new SmoTempo(params);
427
427
  const staffId = XmlHelpers.getStaffId(element);
428
428
  rv.push({ staffId, tempo });
429
429
  }
@@ -567,7 +567,7 @@ export class XmlToSmo {
567
567
  // Only display tempo if changes.
568
568
  if (tempo.length) {
569
569
  // TODO: staff ID is with tempo, but tempo is per column in SMO
570
- if (!SmoTempoText.eq(xmlState.tempo, tempo[0].tempo)) {
570
+ if (!SmoTempo.eq(xmlState.tempo, tempo[0].tempo)) {
571
571
  xmlState.tempo = tempo[0].tempo;
572
572
  xmlState.tempo.display = true;
573
573
  }
@@ -13,7 +13,7 @@ import { TickMap } from './tickMap';
13
13
  import { SmoSystemStaff } from '../data/systemStaff';
14
14
  import { getId, Clef, Pitch } from '../data/common';
15
15
  import {SmoUnmakeTupletActor} from "./tickDuration";
16
- import { SmoTempoText, TimeSignature } from '../data/measureModifiers';
16
+ import { SmoTempo, SmoTimeSignature } from '../data/measureModifiers';
17
17
 
18
18
  /**
19
19
  * Used to calculate the offset and transposition of a note to be pasted
@@ -212,8 +212,8 @@ export class PasteBuffer {
212
212
  // Ordinarily, the key/tempo/time is mapped to the stave, but since we are pasting measure-by
213
213
  // measure here, we want to preserve it.
214
214
  clonedMeasure.keySignature = measureSelection.measure.keySignature;
215
- clonedMeasure.timeSignature = new TimeSignature(measureSelection.measure.timeSignature);
216
- clonedMeasure.tempo = new SmoTempoText(measureSelection.measure.tempo);
215
+ clonedMeasure.timeSignature = new SmoTimeSignature(measureSelection.measure.timeSignature);
216
+ clonedMeasure.tempo = new SmoTempo(measureSelection.measure.tempo);
217
217
  this.measures.push(clonedMeasure);
218
218
 
219
219
  const firstMeasure = this.measures[0];
@@ -10,8 +10,8 @@ import { SmoArticulation, SmoGraceNote, SmoLyric, SmoMicrotone, SmoOrnament,
10
10
  SmoDynamicText,
11
11
  SmoTabNote} from '../data/noteModifiers';
12
12
  import {
13
- SmoRehearsalMark, SmoMeasureText, SmoVolta, SmoMeasureFormat, SmoTempoText, SmoBarline,
14
- TimeSignature, SmoRepeatSymbol
13
+ SmoRehearsalMark, SmoMeasureText, SmoVolta, SmoMeasureFormat, SmoTempo, SmoBarline,
14
+ SmoTimeSignature, SmoRepeatSymbol
15
15
  } from '../data/measureModifiers';
16
16
  import { SmoStaffHairpin, SmoSlur, SmoTie, StaffModifierBase, SmoTieParams, SmoInstrument, SmoStaffHairpinParams,
17
17
  SmoSlurParams, SmoInstrumentMeasure, SmoStaffTextBracket, SmoStaffTextBracketParams,
@@ -139,7 +139,7 @@ export class SmoOperation {
139
139
  score.staves[tabStaves[0].startSelector.staff].removeTabStaves(tabStaves);
140
140
  }
141
141
  }
142
- static setTimeSignature(score: SmoScore, selections: SmoSelection[], timeSignature: TimeSignature) {
142
+ static setTimeSignature(score: SmoScore, selections: SmoSelection[], timeSignature: SmoTimeSignature) {
143
143
  const selectors: SmoSelector[] = [];
144
144
  let i = 0;
145
145
  // change the time signature for each stave in the score
@@ -151,7 +151,7 @@ export class SmoOperation {
151
151
  });
152
152
  selectors.forEach((selector: SmoSelector) => {
153
153
  const rowSelection: SmoSelection = (SmoSelection.measureSelection(score, selector.staff, selector.measure) as SmoSelection);
154
- rowSelection.measure.timeSignature = new TimeSignature(timeSignature);
154
+ rowSelection.measure.timeSignature = new SmoTimeSignature(timeSignature);
155
155
  rowSelection.measure.alignNotesWithTimeSignature();
156
156
  });
157
157
  }
@@ -744,7 +744,7 @@ export class SmoOperation {
744
744
  });
745
745
  }
746
746
 
747
- static addTempo(score: SmoScore, selection: SmoSelection, tempo: SmoTempoText) {
747
+ static addTempo(score: SmoScore, selection: SmoSelection, tempo: SmoTempo) {
748
748
  score.staves.forEach((staff) => {
749
749
  staff.addTempo(tempo, selection.selector.measure);
750
750
  });
@@ -1050,7 +1050,10 @@ export class SmoOperation {
1050
1050
  }
1051
1051
  });
1052
1052
  selections[0].staff.measureInstrumentMap = instMap;
1053
- selections[0].staff.updateInstrumentOffsets();
1053
+ // Don't update transpositions if everything is in concert key.
1054
+ if (!score.preferences.transposingScore){
1055
+ selections[0].staff.updateInstrumentOffsets();
1056
+ }
1054
1057
  score.setNoteInstrumentProperties();
1055
1058
  }
1056
1059
  static computeMultipartRest(score: SmoScore) {
@@ -277,8 +277,16 @@ export class SmoStretchNoteActor extends TickIteratorBase {
277
277
  stemTicksUsed: number,
278
278
  multiplier: number
279
279
  ) {
280
- const remainingTicks = stemTicksUsed - this.newStemTicks;
281
-
280
+ const originalTickCount = this.notes.reduce((a, b) => a + b.tickCount, 0);
281
+ const measureTicks = this.measure.timeSignature.ticksFromTimeSignature() -
282
+ (originalTickCount + (replacingNote.tickCount - originalNote.tickCount));
283
+ let remainingTicks = stemTicksUsed - this.newStemTicks;
284
+ // If this is the last note in the measure, and the measure doesn't have the full duration of notes,
285
+ // allow stretching. This isn't something that can usually happen,
286
+ // except in cases like import from xml, time signature changes, etc.
287
+ if (remainingTicks < 0 && measureTicks >= 0) {
288
+ remainingTicks = 0;
289
+ }
282
290
  if (remainingTicks >= 0) {
283
291
  this.notesToInsert.push(replacingNote);
284
292
 
@@ -1,7 +1,7 @@
1
1
  import { SuiButton, SuiButtonParams } from './button';
2
2
  import { createAndDisplayDialog } from '../dialogs/dialog';
3
3
  import { SuiKeySignatureDialog } from '../dialogs/keySignature';
4
- import { SuiTimeSignatureDialog } from '../dialogs/timeSignature';
4
+ import { SuiTimeSignatureDialogVue } from '../dialogs/timeSignature';
5
5
  import { SuiTempoDialog } from '../dialogs/tempo';
6
6
  import { SuiScoreViewDialogVue } from '../dialogs/scoreView';
7
7
  import { KeyEvent } from '../../smo/data/common';
@@ -96,7 +96,7 @@ export class DisplaySettings extends SuiButton {
96
96
  if (!this.completeNotifier) {
97
97
  return;
98
98
  }
99
- createAndDisplayDialog(SuiTimeSignatureDialog, {
99
+ SuiTimeSignatureDialogVue({
100
100
  completeNotifier: this.completeNotifier,
101
101
  view: this.view,
102
102
  eventSource: this.eventSource,
@@ -19,7 +19,7 @@ import { createApp, ref, reactive, watch } from 'vue';
19
19
  import { SuiKeySignatureDialog } from '../dialogs/keySignature';
20
20
  import { default as ribbonApp } from '../components/buttons/ribbon.vue';
21
21
  import { default as ribbonSidebarApp } from '../components/buttons/sidebar.vue';
22
- import { SuiTimeSignatureDialog } from '../dialogs/timeSignature';
22
+ import { SuiTimeSignatureDialogVue } from '../dialogs/timeSignature';
23
23
  import { SuiScoreViewDialogVue } from '../dialogs/scoreView';
24
24
 
25
25
  declare var $: any;
@@ -150,7 +150,7 @@ export class RibbonButtons {
150
150
  if (!this.controller) {
151
151
  return;
152
152
  }
153
- createAndDisplayDialog(SuiTimeSignatureDialog, {
153
+ SuiTimeSignatureDialogVue({
154
154
  completeNotifier: this.controller,
155
155
  view: this.view,
156
156
  eventSource: this.eventSource,