File size: 3,290 Bytes
3703c4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Hit review and progress streaming

Last updated: 2026-05-12

## Purpose

This pass moves the app closer to a review workstation by making detected hits individually inspectable and by replacing frontend-only polling with a server-sent-events progress channel.

## Implemented behavior

| Area | Implementation | Files |
|---|---|---|
| Review hit artifacts | Every accepted detected hit is written as an individual WAV under `review/hits/`. | `pipeline_runner.py` |
| Manifest hit rows | `manifest.json` now includes a top-level `hits` array with onset, duration, label, cluster, representative flag, and relative file path. | `pipeline_runner.py` |
| Hit URLs | API serialization adds direct download/playback URLs to every hit row. | `app.py` |
| Waveform selection | Clicking the waveform selects the nearest detected onset marker. | `web/app.js` |
| Hit audition | Clicking a hit row or waveform marker loads that hit into the selected-hit audio player. | `web/index.html`, `web/app.js` |
| Sample audition | Representative sample rows now have explicit Audition buttons and a dedicated selected-sample player. | `web/index.html`, `web/app.js` |
| SSE progress | `GET /api/jobs/{job_id}/events` streams job snapshots whenever state changes. | `app.py`, `web/app.js` |
| Poll fallback | The frontend falls back to polling if `EventSource` is unavailable or errors. | `web/app.js` |
| Artifact serving hardening | File downloads now use `Path.relative_to()` against the resolved run output directory. | `app.py` |

## Manifest shape additions

Completed results now include:

```json
{
  "hits": [
    {
      "index": 0,
      "label": "kick",
      "cluster_id": 3,
      "cluster_label": "kick_0",
      "is_representative": true,
      "onset_sec": 0.002993,
      "duration_ms": 255.0,
      "rms_energy": 0.141768,
      "spectral_centroid_hz": 773.4,
      "file": "review/hits/hit_00000_kick.wav"
    }
  ]
}
```

API responses add `url` to each hit row, for example:

```json
{
  "file": "review/hits/hit_00000_kick.wav",
  "url": "/api/jobs/<job-id>/files/review/hits/hit_00000_kick.wav"
}
```

The `overview.onsets` entries now also carry `index` and `duration_sec`, allowing the waveform to map markers back to review hit rows.

## Streaming endpoint

`GET /api/jobs/{job_id}/events` returns `text/event-stream`.

Each emitted event has type `job` and contains the same serialized shape as `GET /api/jobs/{job_id}`:

```text
event: job
data: {"id":"...","status":"running",...}
```

The stream ends after `complete` or `error`. Completed historical jobs stream one final event and then close.

## Current limitations

- Hit review is read-only. It does not yet support delete/shift/relabel actions.
- Every accepted hit is exported as a WAV. This is correct for review UX, but large files with thousands of hits may produce many small artifacts.
- SSE streams job snapshots, not fine-grained internal Demucs progress.
- The waveform is an overview canvas, not an editable detailed waveform yet.

## Next editor step

Add an edit state layer on top of the hit manifest:

1. Mark hit deleted/restored.
2. Shift onset and duration bounds.
3. Reassign cluster label.
4. Merge/split clusters.
5. Re-render/repack from edited manifest without rerunning Demucs or onset detection.