Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1d35323
update vexflow to 5.0.0
jaredjj3 Mar 16, 2025
e78e007
add playback test suite
jaredjj3 Mar 17, 2025
4a2ba19
rename SequenceFactory LegacySequenceFactory
jaredjj3 Mar 17, 2025
600f852
create SequenceFactory
jaredjj3 Mar 17, 2025
c511990
add SequenceFactory test
jaredjj3 Mar 17, 2025
ab9e4f6
update test suite and impl something that pacifies the tests
jaredjj3 Mar 18, 2025
35fe1e7
rename sequence to LegacySequence
jaredjj3 Mar 24, 2025
c63d077
fold SequenceFactory into Sequence and extend tests
jaredjj3 Mar 24, 2025
1d11f54
remove custom matcher
jaredjj3 Mar 24, 2025
ab5b6bf
replace sequence with timeline concept
jaredjj3 Mar 30, 2025
cedc449
sort transitions to handle stop before start
jaredjj3 Mar 30, 2025
7173a84
tweak timeline implementation and finish unit tests
jaredjj3 Mar 31, 2025
c227c3f
create moment type
jaredjj3 Mar 31, 2025
ffb09bb
group clusters of events as moments
jaredjj3 Apr 1, 2025
681bcfc
rename playback.Moment to playback.TimelineMoment
jaredjj3 Apr 1, 2025
83870ad
create CursorFrame class
jaredjj3 Apr 1, 2025
b1f23e1
add hints to CursorFrame class
jaredjj3 Apr 2, 2025
c7a6cfc
rename Cursor to LegacyCursor
jaredjj3 Apr 2, 2025
14baeda
move CursorFrame creation to cursorframe.ts
jaredjj3 Apr 3, 2025
d643258
add measure and system data to TimelineMomentEvent types
jaredjj3 Apr 3, 2025
faafbdf
flesh out CursorFrameFactory
jaredjj3 Apr 3, 2025
13d9f62
scaffold cursor frame tests
jaredjj3 Apr 3, 2025
3d54a7b
finish rough cursorframe impl and unit test
jaredjj3 Apr 4, 2025
48703d5
test CursorFrame creates for: single measure, single stave, same notes
jaredjj3 Apr 4, 2025
09885e2
test CursorFrame creates for: single measure, multiple staves, differ…
jaredjj3 Apr 4, 2025
318bb23
test CursorFrame creates for: single measure, multiple staves, multip…
jaredjj3 Apr 4, 2025
ad220d5
test CursorFrame creates for: multiple measures, single stave, differ…
jaredjj3 Apr 4, 2025
42ed010
test CursorFrame creates for: single measure, single stave, repeat
jaredjj3 Apr 4, 2025
f363c8b
test CursorFrame creates for: multiple measures, single stave, repeat…
jaredjj3 Apr 4, 2025
69acf5f
test CursorFrame creates for: multiple measures, single stave, multip…
jaredjj3 Apr 4, 2025
387b7a8
create CursorFrameLocators
jaredjj3 Apr 4, 2025
63ae633
implement new cursor API
jaredjj3 Apr 4, 2025
92a0323
update README with new cursor API
jaredjj3 Apr 4, 2025
376acb3
improve clarity on cursor internal indexes
jaredjj3 Apr 4, 2025
3060097
migrate to new cursor
jaredjj3 Apr 4, 2025
2e9991c
delete legacy cursor classes
jaredjj3 Apr 4, 2025
842a988
fix issue with multiple repeat endings not excluding previous endings
jaredjj3 Apr 5, 2025
50a3575
simplify CursorFrame and add final test
jaredjj3 Apr 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ const cursorComponent = vexml.SimpleCursor.render(score.getOverlayElement());
cursorModel.addEventListener(
'change',
(e) => {
cursorComponent.update(e.cursorRect);
// The model infers its visibility via the cursorRect. It assumes you've updated appropriately.
cursorComponent.update(e.rect);
// The model infers its visibility via the rect. It assumes you've updated appropriately.
if (!cursorModel.isFullyVisible()) {
cursorModel.scrollIntoView(scrollBehavior);
}
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"dependencies": {
"jszip": "3.10.1",
"vexflow": "5.0.0-beta.1"
"vexflow": "5.0.0"
},
"devDependencies": {
"@babel/core": "^7.17.8",
Expand Down
6 changes: 3 additions & 3 deletions site/src/components/Vexml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const Vexml = ({ musicXML, config, onResult, onClick, onLongpress, onEnte
if (!cursor.isFullyVisible()) {
cursor.scrollIntoView(scrollBehavior);
}
const currentTimeMs = cursor.getState().sequenceEntry.durationRange.start.ms;
const currentTimeMs = cursor.getCurrentState().frame.tRange.start.ms;
player.seek(currentTimeMs, false);
setProgress(currentTimeMs / durationMs);
};
Expand All @@ -79,7 +79,7 @@ export const Vexml = ({ musicXML, config, onResult, onClick, onLongpress, onEnte
if (!cursor.isFullyVisible()) {
cursor.scrollIntoView(scrollBehavior);
}
const currentTimeMs = cursor.getState().sequenceEntry.durationRange.start.ms;
const currentTimeMs = cursor.getCurrentState().frame.tRange.start.ms;
player.seek(currentTimeMs, false);
setProgress(currentTimeMs / durationMs);
};
Expand Down Expand Up @@ -147,7 +147,7 @@ export const Vexml = ({ musicXML, config, onResult, onClick, onLongpress, onEnte
cursor.addEventListener(
'change',
(e) => {
simpleCursor.update(e.cursorRect);
simpleCursor.update(e.rect);
if (!cursor.isFullyVisible()) {
cursor.scrollIntoView(scrollBehavior);
}
Expand Down
14 changes: 14 additions & 0 deletions src/elements/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Config } from '@/config';
import { Logger } from '@/debug';
import { Rect } from '@/spatial';
import { Fraction } from '@/util';
import { Pitch } from './types';

export class Note {
private constructor(
Expand All @@ -24,6 +25,19 @@ export class Note {
return this.noteRender.rect;
}

getPitch(): Pitch {
const note = this.document.getNote(this.noteRender.key);
return {
step: note.pitch.step,
octave: note.pitch.octave,
accidentalCode: note.accidental?.code ?? null,
};
}

sharesACurveWith(note: Note): boolean {
return this.noteRender.curveIds.some((curveId) => note.noteRender.curveIds.includes(curveId));
}

/** Returns the measure beat that this note starts on. */
getStartMeasureBeat(): Fraction {
return Fraction.fromFractionLike(this.document.getVoiceEntry(this.noteRender.key).measureBeat);
Expand Down
5 changes: 5 additions & 0 deletions src/elements/part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export class Part {
return this.partRender.key.partIndex;
}

/** Returns the system index. */
getSystemIndex(): number {
return this.partRender.key.systemIndex;
}

/** Returns the start measure beat for the part. */
getStartMeasureBeat(): Fraction {
return (
Expand Down
31 changes: 23 additions & 8 deletions src/elements/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ export class Score {
util.assert(0 <= span.toPartIndex && span.toPartIndex < partCount, 'toPartIndex out of bounds');
util.assert(span.fromPartIndex <= span.toPartIndex, 'fromPartIndex must be less than or equal to toPartIndex');

const sequence = this.getSequences().find((sequence) => sequence.getPartIndex() === partIndex);
util.assertDefined(sequence);
const timeline = this.getTimelines().find((timeline) => timeline.getPartIndex() === partIndex);
util.assertDefined(timeline);

const frames = playback.CursorFrame.create(this.log, this, timeline, span);
const path = new playback.CursorPath(partIndex, frames);
const cursor = playback.Cursor.create(path, this.getScrollContainer());

const cursor = playback.Cursor.create(this.root.getScrollContainer(), this, sequence, span);
this.cursors.push(cursor);

return cursor;
}

Expand All @@ -100,7 +104,7 @@ export class Score {

/** Returns the duration of the score in milliseconds. */
getDurationMs(): number {
return Math.max(0, ...this.getSequences().map((sequence) => sequence.getDuration().ms));
return Math.max(0, ...this.getTimelines().map((timeline) => timeline.getDuration().ms));
}

/** Returns the max number of parts in this score. */
Expand Down Expand Up @@ -347,9 +351,8 @@ export class Score {
}

@util.memoize()
private getSequences(): playback.Sequence[] {
const sequences = new playback.SequenceFactory(this.log, this).create();
return sequences;
private getTimelines(): playback.Timeline[] {
return playback.Timeline.create(this.log, this);
}

@util.memoize()
Expand All @@ -359,6 +362,18 @@ export class Score {

@util.memoize()
private getTimestampLocator(): playback.TimestampLocator {
return playback.TimestampLocator.create(this, this.getSequences());
const paths = new Array<playback.CursorPath>();
const timelines = this.getTimelines();

for (let partIndex = 0; partIndex < this.getPartCount(); partIndex++) {
const timeline = timelines.find((timeline) => timeline.getPartIndex() === partIndex);
util.assertDefined(timeline);
const span = { fromPartIndex: partIndex, toPartIndex: partIndex };
const frames = playback.CursorFrame.create(this.log, this, timeline, span);
const path = new playback.CursorPath(partIndex, frames);
paths.push(path);
}

return playback.TimestampLocator.create(this, paths);
}
}
10 changes: 10 additions & 0 deletions src/elements/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Score } from './score';
import { Stave } from './stave';
import { System } from './system';
import { Voice } from './voice';
import { Enum, EnumValues } from '@/util';

/**
* Represents a rendered musical element.
Expand All @@ -21,6 +22,15 @@ export type VexmlElement = Score | System | Measure | Fragment | Part | Stave |
*/
export type VoiceEntry = Note | Rest;

export type AccidentalCode = EnumValues<typeof ACCIDENTAL_CODES>;
export const ACCIDENTAL_CODES = new Enum(['#', '##', 'b', 'bb', 'n', 'd', '_', 'db', '+', '++'] as const);

export type Pitch = {
step: string;
octave: number;
accidentalCode: AccidentalCode | null;
};

export type EventMap = {
click: ClickEvent;
enter: EnterEvent;
Expand Down
37 changes: 37 additions & 0 deletions src/playback/bsearchcursorframelocator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as util from '@/util';
import { CursorFrameLocator } from './types';
import { Duration } from './duration';
import { CursorPath } from './cursorpath';

/**
* A CursorFrameLocator that uses binary search to locate the frame at a given time.
*/
export class BSearchCursorFrameLocator implements CursorFrameLocator {
constructor(private path: CursorPath) {}

locate(time: Duration): number | null {
const frames = this.path.getFrames();

let left = 0;
let right = frames.length - 1;

while (left <= right) {
const mid = Math.floor((left + right) / 2);
const entry = frames.at(mid);

util.assertDefined(entry);

if (entry.tRange.includes(time)) {
return mid;
}

if (entry.tRange.end.isGreaterThanOrEqual(time)) {
right = mid - 1;
} else {
left = mid + 1;
}
}

return null;
}
}
42 changes: 0 additions & 42 deletions src/playback/cheaplocator.ts

This file was deleted.

Loading