74 lines
3.3 KiB
TypeScript
74 lines
3.3 KiB
TypeScript
import { test, expect } from "bun:test";
|
|
import { generateSong, TIERS } from "../../src/gen/generate.ts";
|
|
import { songToGp, truthFromGp } from "../../src/score/gp.ts";
|
|
import { BASS_STANDARD, GUITAR_STANDARD, TPQ } from "../../src/core/types.ts";
|
|
|
|
const EXPECTED_ROLES = {
|
|
t1a: ["guitar"],
|
|
t1b: ["bass", "drums", "guitar"],
|
|
t1c: ["bass", "drums", "guitar", "keys", "vocals"],
|
|
t1d: ["bass", "drums", "guitar", "guitar", "keys", "vocals"],
|
|
} as const;
|
|
|
|
test("tiers: structure, determinism, valid GP", () => {
|
|
for (const tier of ["t1a", "t1b", "t1c", "t1d"] as const) {
|
|
expect(TIERS[tier]).toBeDefined();
|
|
for (const seed of [1, 2, 3]) {
|
|
const s = generateSong(tier, seed);
|
|
expect(generateSong(tier, seed)).toEqual(s);
|
|
expect(s.bpm).toBeGreaterThanOrEqual(66);
|
|
expect(s.bpm).toBeLessThanOrEqual(96);
|
|
const guitars = s.tracks.filter((t) => t.role === "guitar");
|
|
expect(guitars.length).toBe(tier === "t1d" ? 2 : 1);
|
|
for (const g of guitars) {
|
|
expect(g.notes.length).toBeGreaterThan(0);
|
|
for (const n of g.notes) expect(GUITAR_STANDARD[n.string! - 1]! + n.fret!).toBe(n.pitch);
|
|
}
|
|
expect(s.tracks.map((t) => t.role).sort()).toEqual([...EXPECTED_ROLES[tier]]);
|
|
expect(new Set(s.tracks.map((t) => t.name)).size).toBe(s.tracks.length);
|
|
const end = s.bars * 4 * TPQ;
|
|
for (const t of s.tracks) for (const n of t.notes) {
|
|
expect(n.tick + n.dur).toBeLessThanOrEqual(end);
|
|
expect(n.tick % 240).toBe(0);
|
|
expect(n.dur % 240).toBe(0);
|
|
}
|
|
expect(truthFromGp(songToGp(s)).tracks.length).toBe(s.tracks.length);
|
|
}
|
|
}
|
|
const d = generateSong("t1d", 7);
|
|
const [g1, g2] = d.tracks.filter((t) => t.role === "guitar");
|
|
expect(Math.sign(g1!.pan)).not.toBe(Math.sign(g2!.pan));
|
|
expect(generateSong("t1a", 1)).not.toEqual(generateSong("t1a", 2));
|
|
});
|
|
|
|
test("musical sanity: bass on roots, vocals range/mono, keys range, no same-string clashes", () => {
|
|
for (const seed of [1, 2, 3, 4, 5]) {
|
|
const s = generateSong("t1d", seed);
|
|
const get = (r: string) => s.tracks.filter((t) => t.role === r);
|
|
const bass = get("bass")[0]!;
|
|
expect(bass.tuning).toEqual(BASS_STANDARD);
|
|
// bass is monophonic and each bar's first note is the same pitch class as the guitar's lowest... checked via key: all pcs in diatonic set
|
|
const ticks = bass.notes.map((n) => n.tick);
|
|
expect(new Set(ticks).size).toBe(ticks.length);
|
|
for (const n of bass.notes) expect(BASS_STANDARD[n.string! - 1]! + n.fret!).toBe(n.pitch);
|
|
const vox = get("vocals")[0]!;
|
|
expect(vox.program).toBe(53);
|
|
const sorted = [...vox.notes].sort((a, b) => a.tick - b.tick);
|
|
sorted.forEach((n, i) => {
|
|
expect(n.pitch).toBeGreaterThanOrEqual(60);
|
|
expect(n.pitch).toBeLessThanOrEqual(79);
|
|
if (i > 0) expect(sorted[i - 1]!.tick + sorted[i - 1]!.dur).toBeLessThanOrEqual(n.tick);
|
|
});
|
|
for (const n of get("keys")[0]!.notes) {
|
|
expect(n.pitch).toBeGreaterThanOrEqual(48);
|
|
expect(n.pitch).toBeLessThanOrEqual(72);
|
|
}
|
|
for (const g of get("guitar")) {
|
|
const byTick = new Map<number, number[]>();
|
|
for (const n of g.notes) (byTick.get(n.tick) ?? byTick.set(n.tick, []).get(n.tick)!).push(n.string!);
|
|
for (const strs of byTick.values()) expect(new Set(strs).size).toBe(strs.length);
|
|
}
|
|
expect(get("guitar").map((g) => g.fx).sort()).toContain("clean");
|
|
}
|
|
});
|