Skip to content

Commit

Permalink
Merge pull request #92 from strangarnenad/nested_tuplets
Browse files Browse the repository at this point in the history
Nested tuplets
  • Loading branch information
AaronDavidNewman authored Sep 15, 2024
2 parents 4d216ec + 625642e commit 6ca9e64
Show file tree
Hide file tree
Showing 21 changed files with 1,534 additions and 1,342 deletions.
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

0 comments on commit 6ca9e64

Please sign in to comment.