-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbeam.ts
95 lines (81 loc) · 2.58 KB
/
beam.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import * as vexflow from 'vexflow';
import * as util from '@/util';
import * as conversions from './conversions';
import { SpannerData } from './types';
import { SpannerMap } from './spannermap';
/** The result of rendering a beam. */
export type BeamRendering = {
type: 'beam';
vexflow: {
beam: vexflow.Beam;
};
};
/** A piece of a beam. */
export type BeamFragment = {
type: BeamFragmentType;
vexflow: {
stemmableNote: vexflow.StemmableNote;
};
};
export type BeamFragmentType = 'start' | 'continue' | 'stop';
/** A container for wedges. */
export type BeamContainer = SpannerMap<null, Beam>;
/** Represents a stem connector for a group of notes within a measure. */
export class Beam {
private fragments: [BeamFragment, ...BeamFragment[]];
private constructor(opts: { fragment: BeamFragment }) {
this.fragments = [opts.fragment];
}
static process(data: SpannerData, container: BeamContainer): void {
// vexflow does the heavy lifting of figuring out the specific beams. We just need to know when a beam starts,
// continues, or stops.
const beams = util.sortBy(data.musicXML.note?.getBeams() ?? [], (beam) => beam.getNumber());
const beamValue = util.first(beams)?.getBeamValue() ?? null;
const type = conversions.fromBeamValueToBeamFragmentType(beamValue);
if (type) {
Beam.commit(
{
type,
vexflow: {
stemmableNote: data.vexflow.staveNote,
},
},
container
);
}
}
/** Conditionally commits the fragment when it can be accepted. */
private static commit(fragment: BeamFragment, container: BeamContainer): void {
const beam = container.get(null);
const last = beam?.getLastFragment();
const isAllowedType = Beam.getAllowedTypes(last?.type).includes(fragment.type);
if (fragment.type === 'start') {
container.push(null, new Beam({ fragment }));
} else if (beam && isAllowedType) {
beam.fragments.push(fragment);
}
}
private static getAllowedTypes(type: BeamFragmentType | undefined): BeamFragmentType[] {
switch (type) {
case 'start':
case 'continue':
return ['continue', 'stop'];
case 'stop':
return [];
default:
return [];
}
}
/** Renders the beam. */
render(): BeamRendering {
const vfStemmableNotes = this.fragments.map((fragment) => fragment.vexflow.stemmableNote);
const beam = new vexflow.Beam(vfStemmableNotes);
return {
type: 'beam',
vexflow: { beam },
};
}
private getLastFragment(): BeamFragment {
return util.last(this.fragments)!;
}
}