← Visitor lesson
Lab

Visitor

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

// 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;
}