# Vocal Check — guide for an AI operator

## What you are operating

Vocal Check screens recordings for voice-like audio. Give it HTTP(S) MP3 URLs or a playlist; it returns one uncalibrated voice score per recording, sampled timestamps, attribution and explicit errors. It analyzes audio, not titles or uploader tags.

- Website: https://rokko-vocal-check.vercel.app/
- Saved full batch: https://rokko-vocal-check.vercel.app/data/musefi-results.json
- CSV: https://rokko-vocal-check.vercel.app/data/musefi-results.csv
- Four-track reference demo: https://rokko-vocal-check.vercel.app/?demo=1
- Local project: `C:\Users\pierr\rokko-works-desk\vocal-detector`
- Source archive: unzip `vocal-check-deliverable.zip`, then enter `vocal-check/`.

Start with the saved report when the task is to review the existing Musefi audit. It opens immediately and needs no model inference. The delivered audit covers 217 URLs: 203 successfully analyzed and 14 download errors after a retry. At the default threshold it contains 12 voice-likely, 8 review and 183 low-evidence results. These are model decisions, not listener-confirmed labels. Read `BATCH_REPORT.md` for the date and timing.

## Install and run

Use Node.js 22+ and install `ffmpeg` and `ffprobe` on PATH. Run commands from the project root.

```sh
npm ci
node --version
ffmpeg -version
ffprobe -version
npm test
npm run dev
```

On Windows PowerShell, use `npm.cmd` / `npx.cmd` if script execution policy prevents `npm` / `npx`. The web app is Vite; `npm run build` creates `dist/`, and `npm run preview` serves that build. No `.env`, AI API key, account or database is required to run it.

## Run a batch

For the entire current Musefi playlist, with four concurrent workers and resumable JSON/CSV in the website's public data folder:

```sh
npm run scan:musefi
```

For another playlist or URL list:

```sh
node cli/scan.mjs --input urls.txt --workers 4 --threads 1 --output results.json --csv results.csv --resume
node cli/scan.mjs --playlist https://musefi.lol/playlist.json --workers 4 --output results.json --csv results.csv --resume
```

Input may be newline-separated URLs, a JSON array of URLs/objects, or a JSON object with a `tracks` array. Duplicates are removed by normalized URL. For example:

```json
{
  "tracks": [
    {
      "url": "https://example.org/recording.mp3",
      "title": "Recording title",
      "artist": "Artist name",
      "licence": "CC BY 4.0",
      "licence_url": "https://creativecommons.org/licenses/by/4.0/",
      "source": "https://example.org/release"
    }
  ]
}
```

This example URL is illustrative; replace it with actual audio. File URLs and URLs containing credentials are rejected. Audio can be at most 48 MiB per file. Network timeout is 60 seconds; a connection can fail sooner. Direct audio URLs must return a decodable file. The CLI supports URL inputs; the website also accepts local audio files.

Useful flags:

| Flag | Meaning |
| --- | --- |
| `--workers 4` | Process four tracks concurrently; valid range 1–8. Reduce on a busy computer. |
| `--threads 1` | One native inference CPU thread per worker; valid range 1–8. |
| `--seconds 30` | Three spread-out 10-second windows. `60` checks six 10-second chunks. Short recordings are checked in full. |
| `--threshold 0.20` | Voice-likely cutoff; valid range 0.10–0.80. |
| `--limit 10` | Run a small smoke test before a large new input. |
| `--resume` | Reuse compatible completed entries from `--output`, retry failures and process new URLs. |
| `--no-cache` | Refresh audio and analysis, even when `--resume` is supplied. |
| `--input -` | Read input from stdin. |

Do not launch overlapping processes that write the same output or cache files. Each batch already has its own worker pool. JSON/CSV checkpoints are written atomically after each track. Ctrl+C preserves completed results. Re-run with `--resume` to continue. A terminated track remains queued and is not given a fabricated score.

Resume matches URL, sample budget, sampling version, model revision and scoring version. Changing only the threshold recalculates decisions without running compatible audio again. It assumes a URL still serves the same recording; use `--no-cache` if the publisher replaced the file. Audio and model caches live in `.cache/`, which is excluded from the deliverable.

Exit codes: **0** = complete without per-track errors; **2** = completed with one or more track errors; **1** = setup/input failure; **130** = interrupted. The final stdout is JSON when no `--output` is given; progress is on stderr.

## Interpret the report correctly

Check `progress`, `complete` and every track's `status` before reporting success. `complete: true` means every URL was attempted; it does **not** mean every file could be analyzed. `progress.done` is the number actually scanned. `progress.errors` is the number that failed. A `queued` entry has no result yet.

For `status: "done"`:

| Field | Meaning |
| --- | --- |
| `confidence` | Strongest independent sigmoid voice-class score, 0–1. **Not a calibrated probability.** |
| `decision` | `voice`, `review` or `low`, calculated at `threshold`. |
| `evidence.start`, `evidence.end` | Seconds into the original recording for the strongest sampled window. |
| `evidence.label` | Voice class that produced the strongest score, such as Singing or Speech. |
| `frames` | Individual model windows, scores, timestamps and top audio classes. |
| `sampledSeconds`, `duration`, `windows` | Actual sampled coverage, recording length and planned windows. |
| `analyzedAt` | When this recording was analyzed. Resume preserves this timestamp. |
| `model`, `modelRevision`, `scoringVersion` | Reproducibility identifiers. |
| `url`, `artist`, `licence`, `licence_url`, `source` | Original URL and attribution. Preserve these when exporting. |

At the default threshold: `confidence >= 0.20` → voice; `0.08 <= confidence < 0.20` → review; lower scores → low voice evidence. If the threshold changes, the review floor is `min(0.08, threshold * 0.4)`.

For `status: "error"`, report the error separately. Do not substitute zero confidence, call the track instrumental or count it as scanned. The Error filter on the website exposes these entries.

Sort successful results by descending confidence and listen at `evidence.start`. Use the website's track play button and **Play this moment**. Keep the original recording and licence links alongside your findings.

Scores cannot prove a human source or absence of vocals. Synthetic speech/singing can trigger the detector. Quiet vocals, brief vocals and vocals outside the sampled windows may be missed; instruments can cause false positives. Do not present the 12 flagged tracks as 12 confirmed metadata mistakes. The four tracks originally reported by listeners were not identified to this project, so this delivery does not demonstrate recall on those specific examples.

## Network calls and costs

There is **no paid AI inference API**, secret key, wallet transaction or per-scan AI charge.

- The website is a static Vercel deployment. Viewing a saved audit downloads the app/report and web fonts. Playing a recording downloads audio from its original host.
- Fresh browser scans download the pinned Transformers.js runtime from jsDelivr and approximately 90 MB of AST ONNX model files from Hugging Face, then run inference in the browser. Public URL audio is fetched from its host. Local file contents are processed on the device.
- Fresh CLI scans use the installed JavaScript package and native ONNX Runtime. Model files are downloaded from Hugging Face on first use; audio and playlist data come from their supplied public URLs. Inference runs on the computer/server running the command.
- `/playlist.json` is a fixed Vercel rewrite to Musefi's public metadata feed. There is no open audio proxy or always-running inference backend on Vercel.
- Vercel bandwidth/hosting and any existing server's compute/bandwidth remain subject to the owner's normal plan. Do not promise that hosting is free or that the app makes no network requests.

## Refresh and publish the website

```sh
npm run scan:musefi
npm test
npm run build
```

Inspect `public/data/musefi-results.json` and its error count before publishing. The website serves the report bundled with the build; it does not scan automatically on a schedule. Browser **Import fresh playlist** creates a new queue and does not launch a server batch. **Rescan in browser** really runs the model again and will be slower for 217 tracks.

The project is linked locally to `rokko-vocal-check` on Vercel. With owner authorization and Vercel authentication, `npx vercel deploy --prod --yes` updates the production alias. Deploy only this project folder. Exclude `.cache/`, `.vercel/`, credentials, test artifacts and unrelated Works Desk files. No secrets are required by the site.

## Tests and handoff files

```sh
npm test
node tests/batch-integration.mjs
npm run build
# Keep the preview running in another terminal:
node node_modules/vite/bin/vite.js preview --host 127.0.0.1 --port 5178
node tests/batch-website.mjs
```

The integration test uses real licensed narration and music clips, an unavailable URL, parallel inference, checkpoints and resume. Website tests use isolated Puppeteer Chrome; set `CHROME_PATH` if needed. To check a deployed site, set `TEST_URL` before `node tests/batch-website.mjs`. The older full four-track inference suite is `tests/browser.mjs` and takes longer.

Read `TEST_REPORT.md` for evidence and dates. `deliverables/` contains the source ZIP, static website bundle, recorded demo, JSON/CSV report, this guide and SHA-256 checksums. `README.md` is the human setup guide. `THIRD_PARTY.md` and `public/demo/CREDITS.md` cover dependencies and audio attribution.
