smoosic 1.0.37 → 1.0.39
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/smoosic.js +89 -29
- package/release/styles/dialogs.css +4 -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/styles/dialogs.css +4 -0
- 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
|
@@ -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
|
|
671
|
+
* constructor parameters for {@link SmoTempo}
|
|
672
672
|
* @category SmoObject
|
|
673
673
|
*/
|
|
674
|
-
export interface
|
|
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
|
|
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
|
|
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():
|
|
754
|
+
static get defaults(): SmoTempoParams {
|
|
755
755
|
return JSON.parse(JSON.stringify({
|
|
756
|
-
tempoMode:
|
|
756
|
+
tempoMode: SmoTempo.tempoModes.durationMode,
|
|
757
757
|
bpm: 120,
|
|
758
758
|
beatDuration: 4096,
|
|
759
|
-
tempoText:
|
|
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 =
|
|
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:
|
|
787
|
+
static eq(t1: SmoTempo, t2: SmoTempo) {
|
|
788
788
|
if (t1.tempoMode !== t2.tempoMode) {
|
|
789
789
|
return false;
|
|
790
790
|
}
|
|
791
|
-
if (t1.tempoMode ===
|
|
791
|
+
if (t1.tempoMode === SmoTempo.tempoModes.durationMode) {
|
|
792
792
|
return t1.bpm === t2.bpm && t1.beatDuration === t2.beatDuration;
|
|
793
793
|
}
|
|
794
|
-
if (t1.tempoMode ===
|
|
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[
|
|
805
|
-
rv[
|
|
806
|
-
rv[
|
|
807
|
-
rv[
|
|
808
|
-
rv[
|
|
809
|
-
rv[
|
|
810
|
-
rv[
|
|
811
|
-
rv[
|
|
812
|
-
rv[
|
|
813
|
-
rv[
|
|
814
|
-
rv[
|
|
815
|
-
rv[
|
|
816
|
-
rv[
|
|
817
|
-
rv[
|
|
818
|
-
rv[
|
|
819
|
-
rv[
|
|
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 ===
|
|
836
|
-
this.tempoMode ===
|
|
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():
|
|
842
|
-
var params: Partial<
|
|
843
|
-
smoSerialize.serializedMergeNonDefault(
|
|
844
|
-
params.ctor = '
|
|
845
|
-
return params as
|
|
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:
|
|
848
|
-
super('
|
|
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(
|
|
854
|
-
smoSerialize.serializedMerge(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
911
|
-
|
|
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 =
|
|
915
|
-
smoSerialize.serializedMerge(
|
|
916
|
-
return new
|
|
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
|
-
|
|
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
|
-
|
|
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 ['
|
|
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
|
|
935
|
-
this.
|
|
936
|
-
|
|
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(
|
|
941
|
-
rv.ctor = '
|
|
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('
|
|
946
|
-
|
|
947
|
-
|
|
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['
|
|
961
|
-
SmoDynamicCtor['
|
|
995
|
+
SmoDynamicCtor['SmoTempo'] = (params: SmoTempoParams) => new SmoTempo(params);
|
|
996
|
+
SmoDynamicCtor['SmoTimeSignature'] = (params: TimeSignatureParameters) => new SmoTimeSignature(params);
|
|
962
997
|
}
|
package/src/smo/data/note.ts
CHANGED
|
@@ -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'
|
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[] {
|