Lab
Visitor
// One method for each kind of node a score is made of.
interface Visitor {
visitNote(note: Note): void;
visitRest(rest: Rest): void;
visitSection(section: Section): void;
}
interface ScoreNode {
accept(visitor: Visitor): void;
}
// A pitch is a MIDI number: 60 is middle C, 72 the C above it.
class Note implements ScoreNode {
readonly id: string;
readonly pitch: number;
readonly beats: number;
constructor(id: string, pitch: number, beats: number) {
this.id = id;
this.pitch = pitch;
this.beats = beats;
}
accept(visitor: Visitor): void {
visitor.visitNote(this);
}
}
class Rest implements ScoreNode {
readonly beats: number;
constructor(beats: number) {
this.beats = beats;
}
accept(visitor: Visitor): void {
visitor.visitRest(this);
}
}
// A section plays its children in order; a repeated one plays them twice.
class Section implements ScoreNode {
readonly repeat: boolean;
readonly children: ScoreNode[];
constructor(repeat: boolean, children: ScoreNode[]) {
this.repeat = repeat;
this.children = children;
}
accept(visitor: Visitor): void {
visitor.visitSection(this);
}
}
// How long the part plays, in beats: a repeated section counts twice.
class PlayingTime implements Visitor {
beats = 0;
visitNote(note: Note): void {
this.beats += note.beats;
}
visitRest(rest: Rest): void {
this.beats += rest.beats;
}
visitSection(section: Section): void {
const times = section.repeat ? 2 : 1;
for (let time = 0; time < times; time++) {
for (const child of section.children) child.accept(this);
}
}
}
// The notes a voice cannot sing: each one once, in score order.
class RangeCheck implements Visitor {
readonly low: number;
readonly high: number;
readonly flagged: string[] = [];
constructor(low: number, high: number) {
this.low = low;
this.high = high;
}
visitNote(_note: Note): void {
// TODO: flag a note outside low..high.
}
visitRest(_rest: Rest): void {}
visitSection(_section: Section): void {
// TODO: decide how this operation walks a section's children.
}
}
// The editor's callers: a fresh visitor for each run.
function totalBeats(root: ScoreNode): number {
const visitor = new PlayingTime();
root.accept(visitor);
return visitor.beats;
}
function outOfRange(root: ScoreNode, low: number, high: number): string[] {
const visitor = new RangeCheck(low, high);
root.accept(visitor);
return visitor.flagged;
}