Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested tuplets #92

Merged
merged 17 commits into from
Sep 15, 2024
Binary file removed .DS_Store
Binary file not shown.
337 changes: 187 additions & 150 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/application/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ import { SuiSampleMedia } from '../render/audio/samples';
import { SmoScore, engravingFontTypes, isEngravingFont } from '../smo/data/score';
import { UndoBuffer } from '../smo/xform/undo';
import { SmoNote } from '../smo/data/note';
import { SmoDuration } from '../smo/xform/tickDuration';
// import { SmoDuration } from '../smo/xform/tickDuration';
import { createLoadTests } from '../../tests/file-load';
import { SmoStaffHairpin, StaffModifierBase, SmoInstrument, SmoSlur, SmoTie, SmoStaffTextBracket,
SmoTabStave
Expand Down Expand Up @@ -243,7 +243,7 @@ export const Smo = {
SmoOrnament,
SmoArticulation, SmoDynamicText, SmoGraceNote, SmoMicrotone, SmoLyric, SmoArpeggio, SmoClefChange,
// Smo Transformers
SmoSelection, SmoSelector, SmoDuration, UndoBuffer, SmoToVex, SmoOperation,
SmoSelection, SmoSelector, /*SmoDuration,*/ UndoBuffer, SmoToVex, SmoOperation,
// new score bootstrap
// help strings
cardKeysHtmlEn, cardNotesLetterHtmlEn, cardNotesChromaticHtmlEn, cardNotesChordsHtmlEn,
Expand Down
17 changes: 8 additions & 9 deletions src/common/vex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Font as VexFont, FontInfo as VexFontInfo, FontStyle as VexFontStyle, FontWeight
TupletOptions as VexTupletOptions, Curve as VexCurve, StaveTie as VexStaveTie,
ClefNote as VexClefNote,
Music as VexMusic, ChordSymbol as VexChordSymbol, ChordSymbolBlock as VexChordSymbolBlock,
TabStave as VexTabStave, TabNote as VexTabNote, TabSlide as VexTabSlide, TabNotePosition as VexTabNotePosition,
TabStave as VexTabStave, TabNote as VexTabNote, TabSlide as VexTabSlide, TabNotePosition as VexTabNotePosition,
TabNoteStruct as VexTabNoteStruct
} from "vexflow_smoosic";

Expand All @@ -18,6 +18,7 @@ TabNoteStruct as VexTabNoteStruct
* Most of the differences are trivial - e.g. different naming conventions for variables.
*/
import { smoSerialize } from "./serializationHelpers";
import { SmoMusic } from "../smo/data/music";
import { SvgBox } from "../smo/data/common";
// export type Vex = SmoVex;
export const VexFlow = SmoVex.Flow;
Expand Down Expand Up @@ -63,8 +64,11 @@ export interface GlyphInfo {

// DI interfaces to create vexflow objects
export interface CreateVexNoteParams {
isTuplet: boolean, measureIndex: number, clef: string,
closestTicks: string, exactTicks: string, keys: string[],
isTuplet: boolean,
measureIndex: number,
clef: string,
stemTicks: string,
keys: string[],
noteType: string
};

Expand Down Expand Up @@ -200,12 +204,7 @@ export function getVexTuplets(params: SmoVexTupletParams) {
return vexTuplet;
}
export function getVexNoteParameters(params: CreateVexNoteParams): { noteParams: StaveNoteStruct, duration: string } {
// If this is a tuplet, we only get the duration so the appropriate stem
// can be rendered. Vex calculates the actual ticks later when the tuplet is made
var duration =
params.isTuplet ?
params.closestTicks :
params.exactTicks;
var duration: any = params.stemTicks;
if (typeof (duration) === 'undefined') {
console.warn('bad duration in measure ' + params.measureIndex);
duration = '8';
Expand Down
64 changes: 39 additions & 25 deletions src/render/vex/toVex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SmoNote } from '../../smo/data/note';
import { SmoMeasure, SmoVoice, MeasureTickmaps } from '../../smo/data/measure';
import { SmoScore } from '../../smo/data/score';
import { SmoArticulation, SmoLyric, SmoOrnament } from '../../smo/data/noteModifiers';
import { VexFlow, StaveNoteStruct, TupletOptions, vexOrnaments } from '../../common/vex';
import {VexFlow, StaveNoteStruct, TupletOptions, vexOrnaments, getVexTuplets} from '../../common/vex';
import { SmoBarline, SmoRehearsalMark } from '../../smo/data/measureModifiers';
import { SmoSelection, SmoSelector } from '../../smo/xform/selections';
import { SmoSystemStaff } from '../../smo/data/systemStaff';
Expand All @@ -14,6 +14,7 @@ import { SmoSystemGroup } from '../../smo/data/scoreModifiers';
import { StaffModifierBase, SmoStaffHairpin, SmoSlur, SmoTie, SmoStaffTextBracket } from '../../smo/data/staffModifiers';
import { toVexBarlineType, vexBarlineType, vexBarlinePosition, toVexBarlinePosition, leftConnectorVx, rightConnectorVx,
toVexVolta, getVexChordBlocks } from '../../render/vex/smoAdapter';
import {SmoTuplet} from "../../smo/data/tuplet";



Expand Down Expand Up @@ -78,10 +79,7 @@ function smoNoteToGraceNotes(smoNote: SmoNote, strs: string[]) {
}
}
function smoNoteToStaveNote(smoNote: SmoNote) {
const duration =
smoNote.isTuplet ?
SmoMusic.closestVexDuration(smoNote.tickCount) :
SmoMusic.ticksToDuration[smoNote.tickCount];
const duration = SmoMusic.ticksToDuration[smoNote.stemTicks];
const sn: StaveNoteStruct = {
clef: smoNote.clef,
duration,
Expand Down Expand Up @@ -427,27 +425,36 @@ function createBeamGroups(smoMeasure: SmoMeasure, strs: string[]) {
}
function createTuplets(smoMeasure: SmoMeasure, strs: string[]) {
smoMeasure.voices.forEach((voice, voiceIx) => {
const tps = smoMeasure.tuplets.filter((tp) => tp.voice === voiceIx);
for (var i = 0; i < tps.length; ++i) {
const tp = tps[i];
const nar: string[] = [];
for (var j = 0; j < tp.notes.length; ++j) {
const note = tp.notes[j];
const vexNote = `${note.attrs.id}`;
nar.push(vexNote);
for (let i = 0; i < smoMeasure.tupletTrees.length; ++i) {
const tupletTree = smoMeasure.tupletTrees[i];
if (tupletTree.voice !== voiceIx) {
continue;
}
const direction = tp.getStemDirection(smoMeasure.clef) === SmoNote.flagStates.up ?
VF.Tuplet.LOCATION_TOP : VF.Tuplet.LOCATION_BOTTOM;
const tpParams: TupletOptions = {
num_notes: tp.num_notes,
notes_occupied: tp.notes_occupied,
const traverseTupletTree = ( parentTuplet: SmoTuplet): void => {
const vexNotes = [];
for (let smoNote of smoMeasure.tupletNotes(parentTuplet)) {
const vexNote = `${smoNote.attrs.id}`;
vexNotes.push(vexNote);
}
const direction = smoMeasure.getStemDirectionForTuplet(parentTuplet) === SmoNote.flagStates.up ?
VF.Tuplet.LOCATION_TOP : VF.Tuplet.LOCATION_BOTTOM;
const tpParams: TupletOptions = {
num_notes: parentTuplet.numNotes,
notes_occupied: parentTuplet.notesOccupied,
ratioed: false,
bracketed: true,
location: direction
};
const tpParamString = JSON.stringify(tpParams);
const narString = '[' + nar.join(',') + ']';
strs.push(`const ${tp.id} = new VF.Tuplet(${narString}, JSON.parse('${tpParamString}'));`);
};
const tpParamString = JSON.stringify(tpParams);
const vexNotesString = '[' + vexNotes.join(',') + ']';
strs.push(`const ${parentTuplet.attrs.id} = new VF.Tuplet(${vexNotesString}, JSON.parse('${tpParamString}'));`);

for (let i = 0; i < parentTuplet.childrenTuplets.length; i++) {
const tuplet = parentTuplet.childrenTuplets[i];
traverseTupletTree(tuplet);
}
}
traverseTupletTree(tupletTree.tuplet);
}
});
}
Expand Down Expand Up @@ -493,9 +500,16 @@ function createMeasure(smoMeasure: SmoMeasure, heightOffset: number, strs: strin
strs.push(`${bg.attrs.id}.setContext(context);`);
strs.push(`${bg.attrs.id}.draw();`)
});
smoMeasure.tuplets.forEach((tp) => {
strs.push(`${tp.id}.setContext(context).draw();`)
})
smoMeasure.tupletTrees.forEach((tp) => {
const traverseTupletTree = ( parentTuplet: SmoTuplet): void => {
strs.push(`${parentTuplet.attrs.id}.setContext(context).draw();`)
for (let i = 0; i < parentTuplet.childrenTuplets.length; i++) {
const tuplet = parentTuplet.childrenTuplets[i];
traverseTupletTree(tuplet);
}
}
traverseTupletTree(tp.tuplet);
});
}
// ## SmoToVex
// Simple serialize class that produced VEX note and voice objects
Expand Down
69 changes: 36 additions & 33 deletions src/render/vex/vxMeasure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { VexFlow, Stave,StemmableNote, Note, Beam, Tuplet, Voice,
} from '../../common/vex';

import { VxMeasureIf, VexNoteModifierIf, VxNote } from './vxNote';
import { SmoTuplet } from '../../smo/data/tuplet';
import { vexGlyph } from './glyphDimensions';
const VF = VexFlow;

Expand Down Expand Up @@ -155,16 +156,18 @@ export class VxMeasure implements VxMeasureIf {
let vexNote: Note | null = null;
let smoTabNote: SmoTabNote | null = null;
let timestamp = new Date().valueOf();
const stemTicks = SmoMusic.ticksToDuration[smoNote.stemTicks];
let tabNote: StemmableNote | null = null;
const closestTicks = SmoMusic.closestVexDuration(smoNote.tickCount);
const exactTicks = SmoMusic.ticksToDuration[smoNote.tickCount];
const noteHead = smoNote.isRest() ? 'r' : smoNote.noteHead;
const keys = SmoMusic.smoPitchesToVexKeys(smoNote.pitches, 0, noteHead);
const smoNoteParams: CreateVexNoteParams = {
isTuplet: smoNote.isTuplet, measureIndex: this.smoMeasure.measureNumber.measureIndex,
const smoNoteParams = {
isTuplet: smoNote.isTuplet,
measureIndex: this.smoMeasure.measureNumber.measureIndex,
clef: smoNote.clef,
closestTicks, exactTicks, keys,
noteType: smoNote.noteType };
stemTicks,
keys,
noteType: smoNote.noteType
};
const { noteParams, duration } = getVexNoteParameters(smoNoteParams);
if (this.tabStave && this.smoTabStave) {
smoTabNote = this.smoTabStave.getTabNoteFromNote(smoNote, this.smoMeasure.transposeIndex);
Expand All @@ -182,7 +185,7 @@ export class VxMeasure implements VxMeasureIf {
tabNote = new VF.StaveNote(noteParams);
}
}
}
}
}
if (smoNote.noteType === '/') {
// vexNote = new VF.GlyphNote('\uE504', { duration });
Expand Down Expand Up @@ -294,7 +297,7 @@ export class VxMeasure implements VxMeasureIf {
this.voiceNotes = [];
const voice = this.smoMeasure.voices[voiceIx];
let clefNoteAdded = false;

for (i = 0;
i < voice.notes.length; ++i) {
const smoNote = voice.notes[i];
Expand Down Expand Up @@ -366,37 +369,37 @@ export class VxMeasure implements VxMeasureIf {
}
}

/**
* Create the VF tuplet objects based on the smo tuplet objects
* @param vix
*/
//
createVexTuplets(vix: number) {
var j = 0;
var i = 0;
this.vexTuplets = [];
this.tupletToVexMap = {};
for (i = 0; i < this.smoMeasure.tuplets.length; ++i) {
const tp = this.smoMeasure.tuplets[i];
if (tp.voice !== vix) {
for (let i = 0; i < this.smoMeasure.tupletTrees.length; ++i) {
const tupletTree = this.smoMeasure.tupletTrees[i];
if (tupletTree.voice !== vix) {
continue;
}
const vexNotes: Note[] = [];
for (j = 0; j < tp.notes.length; ++j) {
const smoNote = tp.notes[j];
vexNotes.push(this.noteToVexMap[smoNote.attrs.id]);
}
const location = tp.getStemDirection(this.smoMeasure.clef) === SmoNote.flagStates.up ?
VF.Tuplet.LOCATION_TOP : VF.Tuplet.LOCATION_BOTTOM;
const smoTupletParams = {
vexNotes,
numNotes: tp.numNotes,
notesOccupied: tp.note_ticks_occupied,
location
const traverseTupletTree = ( parentTuplet: SmoTuplet): void => {
const vexNotes = [];
for (let smoNote of this.smoMeasure.tupletNotes(parentTuplet)) {
vexNotes.push(this.noteToVexMap[smoNote.attrs.id]);
}
const location = this.smoMeasure.getStemDirectionForTuplet(parentTuplet) === SmoNote.flagStates.up ?
VF.Tuplet.LOCATION_TOP : VF.Tuplet.LOCATION_BOTTOM;
const smoTupletParams = {
vexNotes,
numNotes: parentTuplet.numNotes,
notesOccupied: parentTuplet.notesOccupied,
location
}
const vexTuplet = getVexTuplets(smoTupletParams);

this.tupletToVexMap[parentTuplet.attrs.id] = vexTuplet;
this.vexTuplets.push(vexTuplet);
for (let i = 0; i < parentTuplet.childrenTuplets.length; i++) {
const tuplet = parentTuplet.childrenTuplets[i];
traverseTupletTree(tuplet);
}
}
const vexTuplet = getVexTuplets(smoTupletParams);
this.tupletToVexMap[tp.id] = vexTuplet;
this.vexTuplets.push(vexTuplet);
traverseTupletTree(tupletTree.tuplet);
}
}

Expand Down
Loading
Loading