File size: 9,313 Bytes
b2f2f57
2115e2f
b2f2f57
 
2115e2f
b2f2f57
 
6b4a4b8
2115e2f
 
b2f2f57
2115e2f
b2f2f57
 
2115e2f
 
 
 
 
 
 
 
 
 
 
dc33e11
b2f2f57
2115e2f
b2f2f57
2115e2f
b2f2f57
 
 
 
 
 
2115e2f
dc33e11
b2f2f57
6b4a4b8
b2f2f57
 
2115e2f
 
 
 
 
b2f2f57
2115e2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2f2f57
2115e2f
dc33e11
b2f2f57
 
 
 
2115e2f
b2f2f57
2115e2f
 
 
b2f2f57
 
 
 
 
2115e2f
b2f2f57
 
 
 
 
 
2115e2f
b2f2f57
2115e2f
 
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env node
// Check examples, real audio gains, live CPU inference, and responsive layouts.
import assert from "node:assert/strict";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import { chromium } from "../demo/web/node_modules/playwright/index.mjs";

const base = process.env.HENDRIX_SPACE_URL || "https://jackboyla-hendrix.static.hf.space";
const resultPath = process.env.HENDRIX_SPACE_RESULT || "progress/evaluations/huggingface_space_redesign_check.json";
const screenshotPath = process.env.HENDRIX_SPACE_SCREENSHOT || "progress/evaluations/huggingface_space_redesign.png";
const browser = await chromium.launch({ headless: true, args: ["--disable-gpu"] });
const errors = [], failedResponses = [], audioUploads = [], results = [];

async function check(page, embedded) {
  await page.addInitScript(() => {
    window.__blendTargets = [];
    const original = AudioParam.prototype.setTargetAtTime;
    AudioParam.prototype.setTargetAtTime = function (value, time, constant) {
      window.__blendTargets.push(value);
      return original.call(this, value, time, constant);
    };
  });
  const modelRequests = [];
  page.on("pageerror", error => errors.push(String(error)));
  page.on("request", request => {
    if (request.method() === "POST" && request.url().startsWith(base)) audioUploads.push(request.url());
    if (request.url().includes("/transcribe")) audioUploads.push(request.url());
    if (request.url().includes("/models/")) modelRequests.push(request.url());
  });
  page.on("response", response => {
    if (response.status() >= 400 && response.url().startsWith(base)) failedResponses.push({ url: response.url(), status: response.status() });
  });
  let app;
  if (embedded) {
    const repoId = process.env.HENDRIX_SPACE_REPO || "jackboyla/hendrix";
    await page.goto(`https://huggingface.co/spaces/${repoId}`, { waitUntil: "domcontentloaded", timeout: 60_000 });
    await page.waitForFunction(origin => [...document.querySelectorAll("iframe")].some(frame => frame.src.startsWith(origin)), base, { timeout: 60_000 });
    app = page.frameLocator(`iframe[src^="${base}"]`).first();
  } else {
    await page.goto(`${base}/local.html?backend=wasm`, { waitUntil: "domcontentloaded", timeout: 60_000 });
    app = page.mainFrame();
  }
  const download = app.getByRole("link", { name: "Download MIDI" });
  await download.waitFor({ timeout: 60_000 });
  assert.equal(await app.locator(".example-card").count(), 14);
  assert.doesNotMatch(await app.locator("body").textContent(), /D26|research checkpoint/);
  const isolation = await app.locator("html").evaluate(() => ({ isolated: crossOriginIsolated, secure: isSecureContext, sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined" }));
  assert.equal(isolation.secure, true);
  if (!embedded) assert.equal(isolation.isolated, true);
  assert.deepEqual(modelRequests, [], "prepared examples must work without downloading the inference model");

  // Verify every catalog asset, not just the featured example.
  const catalogResult = await app.locator("html").evaluate(async () => {
    const catalog = await (await fetch("/examples/catalog.json")).json();
    const bad = [];
    for (const example of catalog.examples) {
      for (const url of [example.audio_url, example.notes_url]) {
        const response = await fetch(url);
        if (!response.ok) bad.push({ url, status: response.status });
      }
    }
    return { count: catalog.examples.length, categories: [...new Set(catalog.examples.map(example => example.category))], bad };
  });
  assert.equal(catalogResult.count, 14);
  assert.deepEqual(catalogResult.bad, []);
  const blend = app.getByRole("slider", { name: "Original and MIDI blend" });
  await blend.waitFor();
  // MIDI endpoints stay disabled until the actual sampled-instrument synth is ready.
  await app.getByRole("button", { name: "MIDI", exact: true }).waitFor();
  const synthStarted = Date.now();
  while (await blend.isDisabled()) {
    if (Date.now() - synthStarted > 60_000) throw new Error("MIDI instrument sounds did not become ready");
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  await app.getByRole("button", { name: "Play recording" }).click();
  await app.getByRole("button", { name: "Pause playback" }).waitFor();
  await blend.focus();
  await page.keyboard.press("End");
  await app.locator(".blend-readout").filter({ hasText: "100% MIDI" }).waitFor();
  const midiGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
  assert.deepEqual(midiGains, [0, 1, 0, 0], "MIDI endpoint must mute original gain and open MIDI gain");
  assert.equal(await app.getByRole("button", { name: "Pause playback" }).count(), 1);
  await page.keyboard.press("Home");
  await app.locator(".blend-readout").filter({ hasText: "100% original" }).waitFor();
  const originalGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
  assert.deepEqual(originalGains, [1, 0, 0, 0], "Original endpoint must open original gain and mute MIDI gain");
  await page.keyboard.press("ArrowRight");
  await app.locator(".blend-readout").filter({ hasText: "99% original" }).waitFor();
  const mixedGains = await app.locator("html").evaluate(() => window.__blendTargets.slice(-4));
  assert.deepEqual(mixedGains, [0.99, 0.01, 0, 0], "slider must drive both actual audio gains");
  await app.getByRole("button", { name: "Pause playback" }).click();
  const preparedMidi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href"));
  assert.equal(Buffer.from(preparedMidi).subarray(0, 4).toString(), "MThd");

  // Browse and load a second source; then return to the featured mix.
  await app.getByRole("button", { name: "Guitar", exact: true }).click();
  assert.equal(await app.locator(".example-card").count(), 4);
  await app.getByRole("button", { name: "Load Funk guitar", exact: true }).click();
  await app.getByRole("heading", { name: "Funk guitar", exact: true }).waitFor();
  await download.waitFor();
  await app.getByRole("button", { name: "Pause playback" }).waitFor();
  await app.getByRole("button", { name: "Pause playback" }).click();
  await app.getByRole("button", { name: "All", exact: true }).click();
  await app.getByRole("button", { name: "Load Full-band groove", exact: true }).click();
  await app.getByRole("heading", { name: "Full-band groove", exact: true }).waitFor();
  await app.getByRole("button", { name: "Pause playback" }).waitFor();
  await app.getByRole("button", { name: "Pause playback" }).click();
  assert.deepEqual(modelRequests, [], "all prepared listening checks must stay independent of inference download");

  if (!embedded) {
    await mkdir(dirname(screenshotPath), { recursive: true });
    await page.screenshot({ path: screenshotPath, fullPage: true });
    await page.setViewportSize({ width: 390, height: 844 });
    const layout = await app.locator("html").evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth }));
    assert.ok(layout.scrollWidth <= layout.width, `mobile layout overflow: ${JSON.stringify(layout)}`);
    await page.screenshot({ path: screenshotPath.replace(/\.png$/, "_mobile.png"), fullPage: true });
    await page.setViewportSize({ width: 1280, height: 1000 });
  }
  // Check the live path too; use a short excerpt to keep this a bounded test.
  await app.locator(".limit-label select").selectOption("2.05");
  const started = Date.now();
  await app.getByRole("button", { name: "Transcribe audio" }).click();
  await app.locator('.run-status:has-text("Audio never left this device"), .run-status.error').waitFor({ timeout: 180_000 });
  const status = await app.locator(".run-status").textContent();
  const diagnostic = await app.locator(".diagnostic-output").textContent().catch(() => "");
  assert.match(status, /^[1-9]\d* notes from /, `${status}; ${diagnostic}`);
  assert.match(diagnostic, /backend=wasm cache=cpu/);
  const midi = await app.locator("html").evaluate(async (_html, url) => Array.from(new Uint8Array(await (await fetch(url)).arrayBuffer())), await download.getAttribute("href"));
  assert.equal(Buffer.from(midi).subarray(0, 4).toString(), "MThd");
  const result = { embedded, ...isolation, catalog: catalogResult, midiGains, originalGains, mixedGains,
    preparedMidiBytes: preparedMidi.length, status, diagnostic, liveMidiBytes: midi.length,
    liveWallSeconds: (Date.now() - started) / 1000, modelRequests };
  results.push(result);
  console.log(JSON.stringify(result));
}

try {
  await check(await browser.newPage({ viewport: { width: 1280, height: 1000 } }), false);
  if (process.env.HENDRIX_SPACE_SKIP_EMBED !== "1") {
    await check(await browser.newPage({ viewport: { width: 1440, height: 1000 } }), true);
  }
  assert.deepEqual(errors, []);
  assert.deepEqual(failedResponses, []);
  assert.deepEqual(audioUploads, []);
  await mkdir(dirname(resultPath), { recursive: true });
  await writeFile(resultPath, JSON.stringify({ base, results, errors, failedResponses, audioUploads }, null, 2) + "\n");
  console.log(`Example, audio blend, MIDI, layout, and live transcription checks passed: ${resultPath}`);
} finally { await browser.close(); }