tests/tracks.test.js
- import { describe, it, expect, vi, beforeEach } from "vitest";
- import { FIXTURES, MOCKS, STUBS } from "./api.js";
- const { midiPart, midiStep3 } = FIXTURES;
- const { default: MidiWriter } = await MOCKS.midiWriter;
- const { generateMidi } = await import("../src/controller/tracks.js");
- describe("generateMidi", () => {
- const midiNoteArgs = () => MidiWriter.NoteEvent.mock.calls.map((c) => c[0]);
- const midiFirstTrack = () => MidiWriter.Track.mock.results[0].value;
- beforeEach(() => {
- vi.clearAllMocks();
- STUBS.tracksMidiWriter(MidiWriter);
- });
-
- it("emits per-voice tracks with header meta and one NoteEvent per sub-beat for a 2-voice 2-sub-beat input", () => {
- const out = generateMidi(
- [
- midiPart({
- solution: { note_0_0: 60, note_0_1: 62, note_1_0: 55, note_1_1: 57 },
- numSubBeats: 2,
- numerator: 2,
- }),
- ],
- midiStep3(2, [0, 42]),
- 120,
- );
- expect(out).toEqual({
- combinedB64: "TESTB64",
- voicesB64: ["TESTB64", "TESTB64"],
- });
- expect(MidiWriter.Track).toHaveBeenCalledTimes(4);
- expect(MidiWriter.NoteEvent).toHaveBeenCalledTimes(8);
- const t = midiFirstTrack();
- expect(t.setTempo).toHaveBeenCalledWith(120);
- expect(t.setTimeSignature).toHaveBeenCalledWith(2, 4);
- expect(t.setKeySignature).toHaveBeenCalledWith("C");
- const pcArgs = MidiWriter.ProgramChangeEvent.mock.calls.map((c) => c[0]);
- expect(pcArgs).toContainEqual({ instrument: 0, channel: 0 });
- expect(pcArgs).toContainEqual({ instrument: 42, channel: 1 });
- expect(midiNoteArgs().map((a) => a.channel)).toEqual(
- expect.arrayContaining([1, 2]),
- );
- expect(midiNoteArgs()[0]).not.toHaveProperty("wait");
- });
-
- it("extends preceding note via ties as a single T384 event and emits Cm key signature in minor mode; accumulates rests and orphan-ties as wait T256", () => {
- generateMidi(
- [
- midiPart({
- solution: { note_0_0: 60, note_0_1: -2, note_0_2: -2 },
- numSubBeats: 3,
- numerator: 3,
- mode: "minor",
- }),
- ],
- midiStep3(),
- 120,
- );
- expect(MidiWriter.NoteEvent).toHaveBeenCalledTimes(2);
- expect(midiNoteArgs()[0]).toMatchObject({ duration: "T384" });
- expect(midiFirstTrack().setKeySignature).toHaveBeenCalledWith("Cm");
- vi.clearAllMocks();
- generateMidi(
- [
- midiPart({
- solution: { note_0_0: -1, note_0_1: -2, note_0_2: 60 },
- numSubBeats: 3,
- numerator: 3,
- }),
- ],
- midiStep3(),
- 120,
- );
- expect(midiNoteArgs()[0]).toMatchObject({ wait: "T256" });
- });
-
- it("returns null on empty parts input such as when all step-3 parts are UNSAT", () => {
- expect(generateMidi([], midiStep3(), 120)).toBeNull();
- });
-
- it("emits TimeSig and KeySig at every multi-part boundary; scans ties across boundaries so held notes extend and meta events flush after", () => {
- generateMidi(
- [
- midiPart({
- numSubBeats: 2,
- solution: { note_0_0: 60, note_0_1: -2 },
- numerator: 2,
- }),
- midiPart({
- numSubBeats: 2,
- solution: { note_0_0: -2, note_0_1: 64 },
- numerator: 3,
- key: 9,
- mode: "minor",
- }),
- midiPart({
- numSubBeats: 1,
- solution: { note_0_0: 67 },
- numerator: 6,
- denominator: 8,
- key: 7,
- }),
- ],
- midiStep3(),
- 120,
- );
- expect(midiNoteArgs().slice(0, 3)).toMatchObject([
- { pitch: ["C4"], duration: "T384" },
- { pitch: ["E4"], duration: "T128" },
- { pitch: ["G4"], duration: "T64" },
- ]);
- expect(
- MidiWriter.TimeSignatureEvent.mock.calls.map((c) => c.join("/")),
- ).toEqual(["3/4", "6/8", "3/4", "6/8"]);
- expect(MidiWriter.KeySignatureEvent.mock.calls.map((c) => c[0])).toEqual([
- "Am",
- "G",
- "Am",
- "G",
- ]);
- const t = midiFirstTrack();
- expect(t.setTimeSignature).toHaveBeenCalledWith(2, 4);
- expect(t.setKeySignature).toHaveBeenCalledWith("C");
- expect(t.addEvent.mock.calls.map((c) => c[0]._kind)).toEqual([
- "program",
- "note",
- "timesig",
- "keysig",
- "note",
- "timesig",
- "keysig",
- "note",
- ]);
- });
- });