File size: 18,944 Bytes
eb1a122
 
b8fa9bf
 
eb1a122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b8fa9bf
eb1a122
 
 
 
 
b8fa9bf
 
 
 
fa35534
 
 
b8fa9bf
fa35534
b8fa9bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb1a122
 
 
 
ce84147
 
eb1a122
 
 
 
 
 
b8fa9bf
 
eb1a122
 
 
 
 
fa35534
eb1a122
 
 
 
 
ce84147
 
 
 
 
 
eb1a122
 
 
 
 
b8fa9bf
eb1a122
 
 
 
 
 
 
 
 
b8fa9bf
eb1a122
 
 
 
 
b8fa9bf
 
 
 
eb1a122
 
 
 
 
b8fa9bf
 
 
 
 
 
 
3703c4e
 
 
0b5f0f0
3703c4e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb1a122
0b5f0f0
 
 
 
 
 
 
 
 
 
 
 
eb1a122
 
 
 
 
 
 
 
 
0b5f0f0
 
eb1a122
3703c4e
eb1a122
 
3703c4e
eb1a122
 
 
b8fa9bf
eb1a122
 
 
 
 
b8fa9bf
 
 
 
 
 
eb1a122
 
 
 
 
 
fa35534
 
 
 
b8fa9bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa35534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03d531b
 
 
 
 
 
 
 
 
e07820e
03d531b
 
 
 
 
 
 
 
 
e07820e
03d531b
 
 
 
 
e07820e
03d531b
 
 
 
 
e07820e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b5f0f0
e07820e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03d531b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f1f066
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa35534
7f1f066
 
 
fa35534
7f1f066
 
 
 
 
 
 
 
 
fa35534
7f1f066
 
ab6f318
 
 
 
 
 
 
 
 
 
5a90820
 
 
 
 
 
 
 
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# API documentation

Last updated: 2026-05-12

The active app is `app.py`, a FastAPI application.

## Start server

```bash
uvicorn app:app --host 0.0.0.0 --port 7860
```

## `GET /api/health`

Returns backend health.

```json
{"status":"ok"}
```

## `GET /api/config`

Returns supported models, stems, default pipeline params, stage definitions, and clustering mode labels.

```bash
curl http://127.0.0.1:7860/api/config
```

Important response keys:

| Key | Meaning |
|---|---|
| `separation_backends` | Supported separation engines: `spleeter`, `demucs`, and `none`. |
| `spleeter_models` | Supported Spleeter model profiles. |
| `spleeter_stems` | Valid stems per Spleeter model, plus `all`. |
| `demucs_models` | Supported Demucs model names. |
| `demucs_stems` | Valid stems per Demucs model, plus `all`. |
| `defaults` | Default `PipelineParams`. |
| `stages` | Pipeline stage definitions. |
| `clustering_modes` | Human-readable labels for batch and online clustering modes. |

## `GET /api/jobs`

Lists active in-memory jobs and completed run manifests found under `.runs/`.

```bash
curl http://127.0.0.1:7860/api/jobs?limit=50
```

Response:

```json
{
  "active": [],
  "history": [
    {
      "id": "58ca0db4ac74",
      "status": "complete",
      "filename": "song.wav",
      "created_at": 1778540000.0,
      "duration_sec": 2.4,
      "audio_duration_sec": 8.0,
      "realtime_factor": 0.3,
      "bpm": 120.0,
      "hit_count": 32,
      "cluster_count": 8,
      "clustering_mode": "online_preview",
      "stem": "all",
      "error": null
    }
  ]
}
```

`created_at` is the manifest file modification time as a Unix timestamp.

## `POST /api/jobs`

Creates an extraction job.

The endpoint accepts browser-form-style JSON values for numeric and boolean params. For example, `{ "subdivision": "16" }` is coerced to integer `16` before validation. Invalid values return `400` with an actionable `detail` message suitable for direct UI display.

Content type: `multipart/form-data`

Fields:

| Field | Type | Required | Description |
|---|---|---:|---|
| `file` | file | yes | Audio source. |
| `params` | JSON string | no | Partial or full pipeline params. |

Example:

```bash
curl -F 'file=@song.wav' \
  -F 'params={"separation_backend":"spleeter","spleeter_model":"spleeter:4stems","stem":"drums","clustering_mode":"online_preview","target_min":4,"target_max":12,"synthesize":true}' \
  http://127.0.0.1:7860/api/jobs
```

Response status: `202 Accepted`

Invalid params response example:

```json
{"detail":"Invalid extraction parameter: subdivision must be one of 4, 8, 16, 32, 64"}
```

```json
{
  "id": "58ca0db4ac74",
  "status": "pending",
  "filename": "song.wav",
  "params": {"stem": "all", "clustering_mode": "online_preview"},
  "stages": [],
  "logs": [],
  "result": null,
  "error": null
}
```

## `GET /api/jobs/{job_id}`

Poll job status and retrieve results. This works for active in-memory jobs and completed historical jobs whose manifest is still present in `.runs/`.

Statuses:

| Status | Meaning |
|---|---|
| `pending` | Job is queued. |
| `running` | Job is executing. |
| `complete` | Result and artifacts are ready. |
| `error` | Pipeline failed; `error` and `traceback` are populated. |

Completed jobs contain:

| Key | Meaning |
|---|---|
| `duration_sec` | Total wall time. |
| `audio_duration_sec` | Duration of processed stem/source. |
| `realtime_factor` | `duration_sec / audio_duration_sec`. |
| `bpm` | Detected tempo. |
| `hit_count` | Number of accepted onsets/hits. |
| `cluster_count` | Number of sample clusters. |
| `stages` | Per-stage timing/status/detail list. |
| `samples` | Representative sample rows with score, duration, first onset, and playback/download URL. |
| `hits` | Per-detected-hit review rows with onset, duration, label, cluster, representative flag, and playback/download URL. |
| `overview` | Decimated envelope and clickable onset markers for waveform display. |
| `files` | Relative artifact paths. Includes `source`, `stem`, `context_bed`, `target_reconstruction`, `reconstruction`, `midi`, and `archive` when available. |
| `file_urls` | Direct API URLs for top-level artifacts. |

## `GET /api/jobs/{job_id}/events`

Streams job snapshots as server-sent events. This is the preferred progress channel for the frontend; polling remains supported via `GET /api/jobs/{job_id}`.

```bash
curl -N http://127.0.0.1:7860/api/jobs/58ca0db4ac74/events
```

Event shape:

```text
event: job
data: {"id":"58ca0db4ac74","status":"running","stages":[...]}
```

The stream closes after `complete` or `error`. Completed historical jobs emit one final `job` event and close.

## Top-level artifact meanings

| Key | Path | Meaning |
|---|---|---|
| `source` | `source.wav` | Normalized source mix used for source preview. |
| `stem` | `stem.wav` | Target stem being sampled. |
| `context_bed` | `context_bed.wav` | Non-target stems/context bed; silent for `stem=all`. |
| `target_reconstruction` | `target_reconstruction.wav` | Sample-triggered reconstruction of only the target stem. |
| `reconstruction` | `reconstruction.wav` | Full-context reproduced mix: context bed plus target reconstruction. |
| `midi` | `reconstruction.mid` | MIDI trigger reconstruction. |
| `archive` | `sample-pack.zip` | Complete sample pack and reproduction artifacts. |

## `GET /api/jobs/{job_id}/files/{relative_path}`

Downloads an artifact from a completed job.

Examples:

```bash
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/sample-pack.zip
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/reconstruction.mid
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/reconstruction.wav
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/target_reconstruction.wav
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/samples/hihat_open_0.wav
curl -O http://127.0.0.1:7860/api/jobs/58ca0db4ac74/files/review/hits/hit_00000_kick.wav
```

The endpoint prevents path traversal by resolving downloads under `.runs/<job-id>/output/` and requiring the final path to remain relative to that output root.

## `POST /api/cache/clear`

Clears the in-memory DSP cache and disk stem/source cache.

```bash
curl -X POST http://127.0.0.1:7860/api/cache/clear
```

Response:

```json
{"status":"cleared","scope":"memory+disk"}
```

## Pipeline parameters

Defined in `pipeline_runner.PipelineParams`.

| Parameter | Default | Meaning |
|---|---:|---|
| `stem` | `drums` | Source/stem to extract, or `all` to bypass source separation. Valid values depend on the selected backend/model. |
| `separation_backend` | `spleeter` | Source-separation engine: `spleeter`, `demucs`, or `none`. |
| `spleeter_model` | `spleeter:4stems` | Spleeter model profile used by the default backend. |
| `demucs_model` | `htdemucs_ft` | Demucs model used when `separation_backend=demucs` or fallback is needed. |
| `demucs_shifts` | `1` | Test-time shifts for Demucs quality/speed tradeoff. |
| `demucs_overlap` | `0.25` | Demucs chunk overlap. |
| `onset_mode` | `auto` | `auto`, `percussive`, `harmonic`, or `broadband`. |
| `onset_delta` | `0.12` | Peak-pick threshold. |
| `energy_threshold_db` | `-35` | RMS gate for accepting hits. |
| `pre_pad` | `0.003` | Seconds of audio before onset. |
| `min_dur` | `0.02` | Minimum hit duration. |
| `max_dur` | `1.5` | Maximum hit duration. |
| `min_gap` | `0.03` | Minimum time between onsets. |
| `ncc_threshold` | `0.80` | Similarity threshold. Also used by online clustering assignment. |
| `attack_ms` | `25` | Transient window used for NCC/prototypes. |
| `mel_threshold` | `0.75` | Candidate prefilter threshold. For online mode, lower values such as `0.62` are useful. |
| `linkage` | `average` | Agglomerative linkage for `batch_quality`. |
| `clustering_mode` | `batch_quality` | `batch_quality` or `online_preview`. |
| `target_min` | `5` | Lower cluster target; `0` disables target mode in batch mode. |
| `target_max` | `20` | Upper cluster target; `0` disables target/cap mode. |
| `synthesize` | `true` | Write synthesized alternates for clusters with multiple hits. |
| `quantize_midi` | `true` | Snap MIDI notes to grid. |
| `subdivision` | `16` | MIDI grid subdivision. |
| `device` | `cpu` | Torch device for Demucs. |
| `use_disk_cache` | `true` | Cache decoded full mix/stems by source digest and extraction settings. |
| `allow_backend_fallback` | `true` | If Spleeter is selected but unavailable/fails, fall back to Demucs instead of failing the job. |


## Sample-card action API

These endpoints back the simplified card workflow in the reference-style UI. They mutate `supervision_state.json` and preserve the original batch manifest.

### `POST /api/jobs/{job_id}/export-selected`

Exports only the currently selected representative sample labels into `selected/` artifacts.

Body:

```json
{"labels":["kick_0","snare_0"],"synthesize":true}
```

Response shape:

```json
{
  "export": {
    "kind": "selected-sample-export",
    "files": {"archive": "selected/sample-pack.zip", "midi": "selected/reconstruction.mid"},
    "file_urls": {}
  },
  "state": {}
}
```

Rules:

- `labels` must contain at least one visible sample label.
- Only selected semantic clusters are rendered.
- Suppressed hits remain excluded.
- Pinned/drawn representatives are honored.
- The export is written under `.runs/<job-id>/output/selected/` and does not mutate the original pack.

### `POST /api/jobs/{job_id}/samples/{sample_label}/draw`

Cycles a card to the next active representative hit in that semantic cluster. The chosen hit is persisted as a representative override, so later selected/all edited exports use the same choice.

Response:

```json
{"sample": {"label": "kick_0", "url": "..."}, "state": {}}
```

### `POST /api/jobs/{job_id}/samples/{sample_label}/edit`

Applies a timing edit to the current representative and rewrites its preview WAV immediately.

Body:

```json
{"start_offset_ms":-8,"tail_offset_ms":24}
```

The backend slices from `stem.wav`, writes `overrides/hits/*_edited.wav`, updates the representative hit in semantic state, and returns a refreshed card row.

## Interactive supervision API

The interactive supervision API is backed by `supervised_state.py` and persists state as:

```text
.runs/<job_id>/output/supervision_state.json
```

The batch `manifest.json` remains immutable. Supervised edits update semantic state and can be rendered into a separate edited export under `supervised/` without mutating original artifacts.

### `GET /api/jobs/{job_id}/state`

Returns the supervised state for a completed job. If the state file does not exist yet, it is created from the batch manifest.

Response keys:

| Key | Meaning |
|---|---|
| `summary` | Counts for hits, clusters, constraints, events, suggestions, suppressed/forced hits, locked clusters, latest export, undo availability. |
| `hits` | Semantic hit rows with confidence, suppression/favorite/review flags, file URLs, and current cluster assignment. |
| `clusters` | Semantic clusters with hit IDs, representative hit, confidence, locked state, and suppressed count. |
| `review_queue` | Low-confidence/high-priority hits sorted for review. |
| `constraints` | Recent replayable constraints. |
| `events` | Recent state mutation events. |
| `suggestions` | Open move/split/suppress suggestions, including exact `diff` previews. |

```bash
curl http://127.0.0.1:7860/api/jobs/<job-id>/state
```


### `POST /api/jobs/{job_id}/hits/force-onset`

Creates a user-forced hit slice from `stem.wav` and adds it to semantic state.

Body:

```json
{
  "onset_sec": 0.123,
  "duration_ms": 160,
  "target_cluster_id": "cluster:0",
  "label": "snare"
}
```

Required fields:

| Field | Required | Meaning |
|---|---:|---|
| `onset_sec` | yes | Onset location in seconds. |
| `duration_ms` | no | Slice length. If omitted, the system slices until the next active onset or a bounded default. |
| `target_cluster_id` | no | Existing cluster to place the hit into. If omitted, a new user cluster is created. |
| `label` | no | Override label. If omitted, the rule-based classifier labels the forced slice. |

Effects:

- writes `review/hits/hit_NNNNN_<label>_forced.wav`,
- creates a semantic hit with `source=forced`,
- creates `force-onset` and `force-cluster` constraints,
- appends `hit.force_onset`,
- recomputes confidence and review queue.

### `POST /api/jobs/{job_id}/hits/{hit_id}/restore`

Restores a suppressed hit.

Effects:

- sets `suppressed=false`,
- clears `review_status=suppressed` back to `unreviewed`,
- creates a `restore-hit` constraint,
- appends `hit.restored`,
- recomputes confidence and review queue.

### `POST /api/jobs/{job_id}/export`

Renders the current semantic state into edited artifacts under `supervised/`. This does not modify the original `manifest.json`, original samples, or original ZIP.

Body:

```json
{
  "synthesize": true,
  "quantize": true,
  "subdivision": 16
}
```

Response shape:

```json
{
  "export": {
    "kind": "supervised-export",
    "hit_count": 17,
    "cluster_count": 10,
    "files": {
      "archive": "supervised/sample-pack.zip",
      "midi": "supervised/reconstruction.mid",
      "target_reconstruction": "supervised/target_reconstruction.wav",
      "reconstruction": "supervised/reconstruction.wav"
    },
    "file_urls": {}
  },
  "state": {}
}
```

Export rules:

- suppressed hits are excluded,
- forced hits are included,
- moved/pulled hits use current semantic cluster membership,
- favorite/pinned representatives are honored before quality scoring,
- cluster labels are sanitized for filenames,
- `supervision_state.json` receives `latest_export` and a `supervised.exported` event.

### `POST /api/jobs/{job_id}/hits/{hit_id}/move`

Moves a hit into an existing target cluster.

Body:

```json
{"target_cluster_id":"cluster:0"}
```

Effects:

- updates hit membership in `supervision_state.json`,
- creates `force-cluster`,
- creates `must-link` to the target representative when possible,
- appends events,
- recomputes confidence/review queue,
- may create similar-hit move suggestions,
- pushes an undo snapshot.

Example:

```bash
curl -X POST http://127.0.0.1:7860/api/jobs/<job-id>/hits/hit%3A00003/move \
  -H 'Content-Type: application/json' \
  -d '{"target_cluster_id":"cluster:0"}'
```

### `POST /api/jobs/{job_id}/hits/{hit_id}/pull-out`

Pulls a hit into a new user cluster.

Optional body:

```json
{"label":"snare_user_1"}
```

Effects:

- creates a new `cluster:user:*` cluster,
- creates `cannot-link` from the source representative when possible,
- creates `force-cluster`,
- may create split suggestions,
- pushes an undo snapshot.

### `POST /api/jobs/{job_id}/hits/{hit_id}/suppress`

Marks a hit as bleed/noise/non-sample material.

Body:

```json
{"reason":"bleed"}
```

Effects:

- marks the hit `suppressed`,
- creates `suppress-pattern`,
- may create similar suppression suggestions,
- recomputes confidence and review priority.

### `POST /api/jobs/{job_id}/hits/{hit_id}/review`

Stores a review decision for a hit.

Body:

```json
{"status":"accepted"}
```

Supported statuses:

| Status | Meaning |
|---|---|
| `unreviewed` | Clear explicit review status. |
| `accepted` | Mark the hit as reviewed/accepted. |
| `favorite` | Mark as favorite and pin as semantic representative for its cluster. |

### `POST /api/jobs/{job_id}/clusters/{cluster_id}/lock`

Locks or unlocks a cluster.

Body:

```json
{"locked":true}
```

Lock state is persisted and shown in the cluster board. It does not yet alter future full pipeline reruns.

### `GET /api/jobs/{job_id}/suggestions`

Returns open suggestions and the state summary.

```bash
curl http://127.0.0.1:7860/api/jobs/<job-id>/suggestions
```

### `POST /api/jobs/{job_id}/suggestions/{suggestion_id}/accept`

Applies a suggestion and records accepted constraints/examples.

Supported suggestion types:

- `move-hits`,
- `split-hits`,
- `suppress-hits`.

### `POST /api/jobs/{job_id}/suggestions/{suggestion_id}/reject`

Marks a suggestion rejected and records an event.

### `GET /api/jobs/{job_id}/explain/cluster/{cluster_id}`

Returns explanation data for one cluster:

- label,
- locked state,
- confidence and reasons,
- representative hit,
- hit counts,
- label distribution,
- lowest-confidence outliers,
- relevant constraints,
- summary string.

### `POST /api/jobs/{job_id}/undo`

Restores the previous semantic state snapshot if available.

```bash
curl -X POST http://127.0.0.1:7860/api/jobs/<job-id>/undo
```

## Job progress contract

Every serialized job now includes a top-level `progress` object. The same object is sent through `GET /api/jobs/{job_id}`, `GET /api/jobs`, and `GET /api/jobs/{job_id}/events` job events.

Example:

```json
{
  "fraction": 0.1875,
  "completed_steps": 1,
  "total_steps": 8,
  "completed_units": 12.0,
  "total_units": 64.0,
  "stage_key": "stem",
  "stage_label": "Stem separation / source load",
  "stage_fraction": 0.5,
  "stage_work_done": 4,
  "stage_work_total": 8,
  "basis": "exact completed work units: Demucs chunks when available; Spleeter and non-instrumented stages advance only at real stage boundaries; no time-based estimates"
}
```

Semantics:

- `fraction` is the completed work-unit fraction used by the UI waveform progress tint.
- `stage_fraction` is the current stage-local progress when known.
- `stage_work_done` and `stage_work_total` are exact work-unit counters when a stage exposes work units.
- Demucs separated-stem extraction exposes exact completed split chunks.
- Spleeter reports coarse start/complete boundaries because the backend does not expose reliable chunk callbacks here.
- Non-instrumented stages update at exact stage boundaries only.
- The API does not provide guessed ETA or interpolated time progress.

## Progressive `partial_samples`

Active/running jobs may include `partial_samples` before `result` is available. These rows are emitted only after the corresponding sample WAV has been written, and they include the same `url` decoration used by final samples.

Final results remain authoritative under `result.samples`.

## `PipelineParams.auto_tune`

`auto_tune` defaults to `true`. When enabled, the pipeline tunes onset sensitivity and group bounds from the loaded target stem before final onset detection. The effective tuned values are returned in the job params/result manifest.

## Upload/runtime fallback update (2026-05-12)

- Added a visible top-bar `Choose audio` affordance in addition to whole-app drag/drop.
- Fixed the default hidden state of the error banner so placeholder errors are not shown on page load.
- API errors now surface request path/status/detail in the visible banner and pipeline logs.
- `/api/config` now includes runtime diagnostics for optional separation backends.
- If Spleeter is unavailable, the simple UI keeps the app usable by switching to full-mix mode; backend fallback also uses full-mix rather than silently launching Demucs.