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
name: CI
on:
push:
branches:
pull_request:
branches:
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
# "pinned" = the toolchain in rust-toolchain.toml (same compiler
# as local builds); the MSRV row overrides it via the dtolnay
# action's RUSTUP_TOOLCHAIN env var.
rust:
include:
- os: ubuntu-latest
rust: "1.85.0" # MSRV
steps:
- uses: actions/checkout@v4
- if: matrix.rust == 'pinned'
run: rustup toolchain install
- if: matrix.rust != 'pinned'
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- uses: Swatinem/rust-cache@v2
# Regression tests read fixtures from test_audio/ (gitignored, *.wav).
# The generator is deterministic, so regenerate them before testing.
- run: cargo run --example generate_test_audio
- run: cargo test --all-targets
quality-gates:
name: Quality Gates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Installs the pinned toolchain + components from rust-toolchain.toml.
- run: rustup toolchain install
- uses: Swatinem/rust-cache@v2
# The engine A/B matrix is the quality dashboard: absolute gates
# re-derived from new-engine measurements at Stage 9 (the old
# engine's quality_gates suite was deleted with the old engine).
- run: cargo test --features qa-harnesses --release --test engine_ab_matrix -- --nocapture
env:
TIMESTRETCH_QUALITY_DASHBOARD_DIR: target/quality_dashboard
# New-engine WCET gate (ROADMAP Stage 6): p99.9 per-callback ratio
# bounded per profile and callback size, priming and threshold-riding
# included. Same hardware-headroom multiplier as above (local
# measurements sit at p99.9 <= 0.21 vs the 0.5 strict bound).
- run: cargo test --features qa-harnesses --release --test engine_wcet -- --nocapture
env:
TIMESTRETCH_STRICT_CALLBACK_BUDGET: "1"
TIMESTRETCH_CALLBACK_BUDGET_MULTIPLIER: "2.0"
# Ride-quality gates (ROADMAP Stage 15 promotion): cents wobble on
# sustained rides and transient sharpness — the two harnesses most
# directly tied to how the deck sounds under the fader; previously
# local-only.
- run: cargo test --features qa-harnesses --release --test engine_keylock -- --nocapture
- run: cargo test --features qa-harnesses --release --test engine_transients -- --nocapture
# Stage 12 robustness gates: seeded adversarial input against every
# parsing/validation surface, plus the bounded deck-gesture soak
# (~1 s each in release; the full-length soak recipe stays local,
# see qa/soak.rs docs). These are feature-gated targets, so the
# plain test jobs never build them — without these lines they have
# zero CI coverage.
- run: cargo test --features qa-harnesses --release --test robustness -- --nocapture
- run: cargo test --features qa-harnesses --release --test soak -- --nocapture
- uses: actions/upload-artifact@v4
with:
name: quality-dashboard-${{ github.run_id }}
path: target/quality_dashboard/
if-no-files-found: error
public-corpus:
name: Public Corpus (BPM accuracy)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- uses: Swatinem/rust-cache@v2
# The corpus is CC-licensed netlabel audio fetched from the Internet
# Archive with pinned SHA-256 (ROADMAP Stage 7). Cached on the fetch
# script's hash so CI only downloads when the corpus changes.
- uses: actions/cache@v4
with:
path: benchmarks/audio/public-corpus
key: public-corpus-${{ hashFiles('scripts/fetch_public_corpus.sh') }}
# Seed from the previous corpus on a key miss: the fetch script
# sha-verifies and skips files already present, so a script
# change only downloads the DELTA — which keeps CI alive through
# archive.org's outages (2026-08-17: a nine-file refetch flapped
# for hours on their degraded CDN; the delta was two files).
restore-keys: |
public-corpus-
- run: bash scripts/fetch_public_corpus.sh
# Regression tests read fixtures from test_audio/ (gitignored, *.wav).
# The generator is deterministic, so regenerate them before testing.
- run: cargo run --example generate_test_audio
# Accuracy floors on the public corpus (ROADMAP Stage 10 exit
# criterion, enabled once the DnB metrical-level fix landed and
# the corpus returned to 100% acc1 — the two DnB rows are the
# non-EDM gate).
- run: cargo test --features qa-harnesses --release --test bpm_accuracy -- --nocapture
env:
TIMESTRETCH_BPM_MIN_ACC1: "90"
TIMESTRETCH_BPM_MIN_ACC2: "90"
# External-reference gate (ROADMAP Stage 7, REQUIRED): the pull
# engine's keylock render vs a Rubber Band CLI render of the same
# public-corpus track, gated on spectral similarity and level.
- run: sudo apt-get update && sudo apt-get install -y rubberband-cli
- run: cargo test --features qa-harnesses --release --test rubberband_reference_gate -- --nocapture
env:
TIMESTRETCH_REQUIRE_RUBBERBAND: "1"
- uses: actions/upload-artifact@v4
with:
name: bpm-accuracy-report-${{ github.run_id }}
path: target/bpm_accuracy_report.json
if-no-files-found: error
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets -- -D warnings
desktop:
name: Desktop
# The desktop crate is excluded from the workspace, so the root jobs
# never build it. macOS is the app's target platform (wgpu -> native
# Metal) and the only runner that needs no extra system packages for
# eframe/cpal/rfd.
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
desktop
tools/ab-tui
- run: cargo test --all-targets
working-directory: desktop
- run: cargo clippy --all-targets -- -D warnings
working-directory: desktop
# The blind A/B TUI is likewise a standalone excluded crate
# (cpal/ratatui); excluded targets have zero CI coverage unless a
# workflow names them (Stage 12 lesson), and macOS builds cpal
# without extra system packages.
- run: cargo test --all-targets
working-directory: tools/ab-tui
- run: cargo clippy --all-targets -- -D warnings
working-directory: tools/ab-tui
fmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- run: cargo fmt --all --check
doc:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- uses: Swatinem/rust-cache@v2
- run: cargo doc --no-deps
env:
RUSTDOCFLAGS: -D warnings