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.
- package/README.md +6 -3
- package/build/smoosic.js +89 -29
- package/changes.md +9 -1
- package/package.json +1 -1
- package/release/html/libmode.html +36 -0
- package/release/html/smoosic.html +4 -4
- package/release/smoosic.js +89 -29
- package/release/styles/dialogs.css +25 -18
- package/release/styles/general.css +16 -9
- package/release/styles/media.css +13 -5
- package/release/styles/ribbon.css +3 -0
- package/src/application/exports.ts +4 -4
- package/src/render/audio/musicCursor.ts +1 -1
- package/src/render/sui/NoteEntryCaret.ts +4 -1
- package/src/render/sui/formatter.ts +3 -3
- package/src/render/sui/mapper.ts +1 -1
- package/src/render/sui/scoreView.ts +4 -4
- package/src/render/sui/scoreViewOperations.ts +10 -10
- package/src/render/sui/textEdit.ts +3 -1
- package/src/render/vex/vxMeasure.ts +12 -6
- package/src/smo/data/measure.ts +37 -37
- package/src/smo/data/measureModifiers.ts +106 -71
- package/src/smo/data/note.ts +4 -1
- package/src/smo/data/score.ts +50 -11
- package/src/smo/data/systemStaff.ts +2 -2
- package/src/smo/midi/midiToSmo.ts +28 -29
- package/src/smo/midi/smoToMidi.ts +3 -3
- package/src/smo/mxml/smoToXml.ts +11 -11
- package/src/smo/mxml/xmlState.ts +3 -3
- package/src/smo/mxml/xmlToSmo.ts +9 -9
- package/src/smo/xform/copypaste.ts +3 -3
- package/src/smo/xform/operations.ts +9 -6
- package/src/smo/xform/tickDuration.ts +10 -2
- package/src/ui/buttons/display.ts +2 -2
- package/src/ui/buttons/ribbon.ts +2 -2
- package/src/ui/components/dialogs/timeSignature.vue +223 -0
- package/src/ui/dialogs/fileDialogs.ts +1 -1
- package/src/ui/dialogs/keySignature.ts +1 -1
- package/src/ui/dialogs/tempo.ts +38 -38
- package/src/ui/dialogs/timeSignature.ts +45 -116
- package/src/ui/menus/timeSignature.ts +2 -2
- package/tools/smoosic-schema.json +4 -4
package/src/smo/data/score.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
157
|
-
timeSignature: Record<number,
|
|
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,
|
|
355
|
-
const timeSignature: Record<number,
|
|
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 (!(
|
|
384
|
+
if (!(SmoTimeSignature.equal(current.timeSignature, previous!.timeSignature))) {
|
|
385
385
|
previous!.timeSignature = current.timeSignature;
|
|
386
386
|
timeSignature[ix] = current.timeSignature;
|
|
387
387
|
}
|
|
388
|
-
if (!(
|
|
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
|
|
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] =
|
|
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 && !
|
|
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,
|
|
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:
|
|
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 {
|
|
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' | '
|
|
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:
|
|
50
|
-
tempo:
|
|
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:
|
|
79
|
-
tempo:
|
|
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
|
-
|
|
104
|
-
tempoMap: Record<number,
|
|
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.
|
|
157
|
-
this.tempoMap[0] = new
|
|
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
|
-
|
|
179
|
-
if (this.
|
|
180
|
-
return this.
|
|
178
|
+
getSmoTimeSignature(ticks: number): SmoTimeSignature {
|
|
179
|
+
if (this.SmoTimeSignatureMap[ticks]) {
|
|
180
|
+
return this.SmoTimeSignatureMap[ticks];
|
|
181
181
|
}
|
|
182
|
-
return getValueForTick(this.
|
|
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.
|
|
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 === '
|
|
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 =
|
|
219
|
-
tsDef.actualBeats
|
|
220
|
-
|
|
221
|
-
|
|
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 =
|
|
224
|
+
const tempoDef = SmoTempo.defaults;
|
|
226
225
|
tempoDef.bpm = bpm;
|
|
227
|
-
this.tempoMap[ticks] = new
|
|
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
|
|
259
|
-
tempo: new
|
|
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
|
|
268
|
-
const tempo = new
|
|
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
|
|
302
|
-
measureDefs.tempo = new
|
|
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 {
|
|
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?:
|
|
71
|
-
tempo?:
|
|
70
|
+
timeSignature?: SmoTimeSignature,
|
|
71
|
+
tempo?: SmoTempo,
|
|
72
72
|
keySignature?: string
|
|
73
73
|
}
|
|
74
74
|
function stavesToSkip(score: SmoScore): number[] {
|
package/src/smo/mxml/smoToXml.ts
CHANGED
|
@@ -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,
|
|
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 {
|
|
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?:
|
|
55
|
-
tempo?:
|
|
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 && !
|
|
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
|
|
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 ===
|
|
664
|
+
if (tempo.tempoMode === SmoTempo.tempoModes.customMode) {
|
|
665
665
|
tempoText = tempo.customText;
|
|
666
666
|
}
|
|
667
|
-
if (tempo.tempoMode ===
|
|
667
|
+
if (tempo.tempoMode === SmoTempo.tempoModes.textMode) {
|
|
668
668
|
nn(tempoElement, 'words', { words: tempoText }, 'words');
|
|
669
|
-
} else if (tempo.tempoMode ===
|
|
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
|
|
905
|
-
if (currentTs !== null &&
|
|
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;
|
package/src/smo/mxml/xmlState.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
SmoTie,
|
|
14
14
|
TieLine
|
|
15
15
|
} from '../data/staffModifiers';
|
|
16
|
-
import {SmoBarline, SmoMeasureModifierBase, SmoRehearsalMark,
|
|
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
|
|
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:
|
|
198
|
+
tempo: SmoTempo = new SmoTempo(SmoTempo.defaults);
|
|
199
199
|
staffArray: XmlStaffInfo[] = [];
|
|
200
200
|
staffIndex: number = 0;
|
|
201
201
|
graceNotes: SmoGraceNote[] = [];
|
package/src/smo/mxml/xmlToSmo.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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 =
|
|
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 (
|
|
414
|
-
tempoMode =
|
|
413
|
+
if (SmoTempo.tempoTexts[tempoText.toLowerCase()]) {
|
|
414
|
+
tempoMode = SmoTempo.tempoModes.textMode;
|
|
415
415
|
} else {
|
|
416
|
-
tempoMode =
|
|
416
|
+
tempoMode = SmoTempo.tempoModes.customMode;
|
|
417
417
|
customText = tempoText;
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
-
const params =
|
|
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
|
|
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 (!
|
|
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 {
|
|
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
|
|
216
|
-
clonedMeasure.tempo = new
|
|
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,
|
|
14
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
99
|
+
SuiTimeSignatureDialogVue({
|
|
100
100
|
completeNotifier: this.completeNotifier,
|
|
101
101
|
view: this.view,
|
|
102
102
|
eventSource: this.eventSource,
|
package/src/ui/buttons/ribbon.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
153
|
+
SuiTimeSignatureDialogVue({
|
|
154
154
|
completeNotifier: this.controller,
|
|
155
155
|
view: this.view,
|
|
156
156
|
eventSource: this.eventSource,
|