autoeq 0.4.40

Automatic equalization for speakers, headphones and rooms!
Documentation
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
[group('download')]
download-speakers:
	cargo run --bin autoeq-download-speakers --release

[group('build')]
prod-autoeq:
	cargo build --release --bin autoeq
	cargo build --release --bin benchmark-autoeq-speaker

[group('bench')]
bench-autoeq: bench-convergence bench-autoeq-speaker

[group('bench')]
bench-convergence:
	cargo run --release --bin benchmark-convergence

[group('bench')]
bench-autoeq-speaker:
	# either jobs=1 or --no-parallel ; or a mix if you have a lot of
	# CPU cores
	cargo run --release --bin benchmark-autoeq-speaker -- --qa --jobs 1

[group('examples')]
examples-autoeq:
	cargo run --release --example headphone_loss_validation

[group('publish')]
publish-autoeq:
	cd crates/autoeq && cargo publish

[group('demo')]
demo-headphone-loss:
	cargo run --release --example headphone_loss_demo -- \
	--spl "./data_tests/headphones/asr/bowerwilkins_p7/Bowers & Wilkins P7.csv" \
	--target "./data_tests/targets/harman-over-ear-2018.csv"

# ----------------------------------------------------------------------
# QA AUTOEQ
# ----------------------------------------------------------------------

[group('qa-autoeq')]
qa-autoeq: prod-autoeq \
	qa-ascilab-6b \
	qa-jbl-m2-flat qa-jbl-m2-score \
	qa-beyerdynamic-dt1990pro \
	qa-edifierw830nb

[group('qa-autoeq')]
qa-ascilab-6b:
	./target/release/autoeq --speaker="AsciLab F6B" --version asr --measurement CEA2034 \
	--algo autoeq:de --loss speaker-score -n 7 --min-freq=30 --max-q=6 \
	--qa 0.5

[group('qa-autoeq')]
qa-jbl-m2-flat:
	./target/release/autoeq --speaker="JBL M2" --version eac --measurement CEA2034 \
	--algo autoeq:de --loss speaker-flat -n 7 --min-freq=20 --max-q=6 --peq-model hp-pk \
	--qa 0.5

[group('qa-autoeq')]
qa-jbl-m2-score:
	./target/release/autoeq --speaker="JBL M2" --version eac --measurement CEA2034 \
	--algo autoeq:de --loss speaker-score -n 7 --min-freq=20 --max-q=6 --peq-model hp-pk \
	--qa 0.5

[group('qa-autoeq')]
qa-beyerdynamic-dt1990pro: qa-beyerdynamic-dt1990pro-flat qa-beyerdynamic-dt1990pro-score	qa-beyerdynamic-dt1990pro-score2

[group('qa-autoeq')]
qa-beyerdynamic-dt1990pro-score:
	./target/release/autoeq -n 5 \
	--curve ./data_tests/headphones/asr/beyerdynamic_dt1990pro/Beyerdynamic\ DT1990\ Pro\ Headphone\ Frequency\ Response\ Measurement.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv --loss headphone-score  \
	--qa 3.0

[group('qa-autoeq')]
qa-beyerdynamic-dt1990pro-score2:
	./target/release/autoeq -n 7 \
	--curve ./data_tests/headphones/asr/beyerdynamic_dt1990pro/Beyerdynamic\ DT1990\ Pro\ Headphone\ Frequency\ Response\ Measurement.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv \
	--loss headphone-score	--max-db 6 --max-q 6 --algo mh:rga --maxeval 20000 --min-freq=20 --max-freq 10000 --peq-model hp-pk-lp --min-q 0.6 --min-db 0.25 \
	--qa 1.5

[group('qa-autoeq')]
qa-beyerdynamic-dt1990pro-flat:
	./target/release/autoeq -n 5 \
	--curve ./data_tests/headphones/asr/beyerdynamic_dt1990pro/Beyerdynamic\ DT1990\ Pro\ Headphone\ Frequency\ Response\ Measurement.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv \
	--loss headphone-flat  --max-db 6 --max-q 6 --maxeval 20000 --algo mh:pso --min-freq=20 --max-freq 10000 --peq-model pk \
	--qa 0.5

[group('qa-autoeq')]
qa-edifierw830nb: qa-edifierw830nb-autoeqde qa-edifierw830nb-mhrga qa-edifierw830nb-mhfirefly

[group('qa-autoeq')]
qa-edifierw830nb-autoeqde:
	./target/release/autoeq -n 9 \
	--curve data_tests/headphones/asr/edifierw830nb/Edifier\ W830NB.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv \
	--min-freq 50 --max-freq 16000 --max-q 8 --max-db 8 \
	--loss headphone-score \
	--min-spacing-oct 0.08 \
	--algo autoeq:de --population 70 --maxeval 8000 --seed 42 \
	--qa 6.0

[group('qa-autoeq')]
qa-edifierw830nb-mhrga:
	./target/release/autoeq -n 5 \
	--curve data_tests/headphones/asr/edifierw830nb/Edifier\ W830NB.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv \
	--min-freq 50 --max-freq 16000 --max-q 8 --max-db 8 \
	--loss headphone-score \
	--min-spacing-oct 0.04 --atolerance 0.00000001 --tolerance 0.0000001 --algo mh:rga --population 100 --maxeval 30000 \
	--qa 2.5

[group('qa-autoeq')]
qa-edifierw830nb-mhfirefly:
	./target/release/autoeq -n 5 \
	--curve data_tests/headphones/asr/edifierw830nb/Edifier\ W830NB.csv \
	--target ./data_tests/targets/harman-over-ear-2018.csv \
	--min-freq 50 --max-freq 16000 --max-q 8 --max-db 8 \
	--loss headphone-score \
	--min-spacing-oct 0.04 --atolerance 0.00000001 --tolerance 0.000000001 --algo mh:rga --population 80 --maxeval 30000 \
	--qa 2.5

# ----------------------------------------------------------------------
# QA ROOMEQ
# ----------------------------------------------------------------------

# Ensure Python venv exists and has dependencies installed
[group('setup')]
ensure-venv:
	#!/usr/bin/env bash
	set -euo pipefail
	if [ ! -f ./venv/bin/python3 ]; then
		echo "Creating Python venv..."
		python3 -m venv ./venv
	fi
	if ! ./venv/bin/python3 -c "import plotly" 2>/dev/null; then
		echo "Installing Python dependencies..."
		./venv/bin/pip install -r ./crates/autoeq/scripts/requirements.txt
	fi

[group('qa-roomeq')]
qa-roomeq: qa-roomeq-small-stereo-20 \
	qa-roomeq-small-stereo-21 \
	qa-roomeq-small-stereo-22 \
	qa-roomeq-convergence \
	qa-roomeq-coverage \
	qa-roomeq-synthetic \
	qa-roomeq-gd \
	qa-roomeq-features

# Memory-capped convergence run. The `--jobs` default in
# `roomeq-qa-quality` is `num_cpus/2` so each outer test case still gets
# parallel DE evaluators without OOM'ing the machine when 70+ cases are
# scheduled. Override with `just qa-roomeq-convergence jobs=N` or run the
# binary directly.
[group('qa-roomeq')]
qa-roomeq-convergence jobs="":
	#!/usr/bin/env bash
	set -euo pipefail
	if [ -n "{{jobs}}" ]; then
	  cargo run --bin roomeq-qa-quality --release -- --jobs {{jobs}}
	else
	  cargo run --bin roomeq-qa-quality --release
	fi

[group('qa-roomeq')]
qa-roomeq-small-stereo-20: ensure-venv
	@for method in iir fir mixed; do \
	  for algo in bem fem; do \
	      mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_0; \
	      cargo run --bin roomeq --release -- \
	        --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_0/config.json \
		    --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/small_stereo_2_0/optimiser-$method.json \
		    --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_0/dsp_$method.json; \
		  ./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
		                   ./data_generated/roomeq/generated/$algo/small_stereo_2_0/dsp_$method.json; \
	  done \
	done


[group('qa-roomeq')]
qa-roomeq-small-stereo-21: ensure-venv
	@for method in iir fir mixed; do \
	  for algo in fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_1; \
	    cargo run --bin roomeq --release -- \
	        --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_1/config.json \
		    --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/small_stereo_2_1/optimiser-$method.json \
		    --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_1/dsp_$method.json; \
		./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
		                   ./data_generated/roomeq/generated/$algo/small_stereo_2_1/dsp_$method.json; \
	  done \
	done


[group('qa-roomeq')]
qa-roomeq-small-stereo-22: ensure-venv
	@for method in iir fir mixed; do \
	  for algo in fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_2; \
	    cargo run --bin roomeq --release -- \
	        --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_2/config.json \
		    --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/small_stereo_2_2/optimiser-$method.json \
		    --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_2/dsp_$method.json; \
		./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
		                   ./data_generated/roomeq/generated/$algo/small_stereo_2_2/dsp_$method.json; \
	  done \
	done

[group('qa-roomeq')]
qa-roomeq-small-stereo-51: ensure-venv
	@for method in iir fir mixed; do \
	  for algo in fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/medium_surround_5_1; \
	    cargo run --bin roomeq --release -- \
	        --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/medium_surround_5_1/config.json \
		    --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/medium_surround_5_1/optimiser-$method.json \
		    --output       ./data_generated/roomeq/generated/$algo/medium_surround_5_1/dsp_$method.json; \
		./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
		                   ./data_generated/roomeq/generated/$algo/medium_surround_5_1/dsp_$method.json; \
	  done \
	done

# ----------------------------------------------------------------------
# QA ROOMEQ — Multi-Measurement (5 listening positions per speaker)
# Tests the multi-objective optimization across all strategies.
# Requires data generated with 5 LPs (run `just generate-roomeq-data` first).
# ----------------------------------------------------------------------

[group('qa-roomeq-multi')]
qa-roomeq-multi-measurement: \
	qa-roomeq-multi-small-stereo-20 \
	qa-roomeq-multi-small-stereo-21 \
	qa-roomeq-multi-small-stereo-22-mso

[group('qa-roomeq-multi')]
qa-roomeq-multi-small-stereo-20: ensure-venv
	@for strategy in minimax weighted_sum variance_penalized; do \
	  for algo in bem fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_0; \
	    echo "=== Multi-measurement $strategy ($algo) small_stereo_2_0 ==="; \
	    cargo run --bin roomeq --release -- \
	      --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_0/config.json \
	      --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/multi_measurement/$strategy.json \
	      --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_0/dsp_iir_multi_$strategy.json; \
	    ./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
	                     ./data_generated/roomeq/generated/$algo/small_stereo_2_0/dsp_iir_multi_$strategy.json; \
	  done \
	done

[group('qa-roomeq-multi')]
qa-roomeq-multi-small-stereo-21: ensure-venv
	@for strategy in minimax weighted_sum variance_penalized; do \
	  for algo in fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_1; \
	    echo "=== Multi-measurement $strategy ($algo) small_stereo_2_1 ==="; \
	    cargo run --bin roomeq --release -- \
	      --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_1/config.json \
	      --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/multi_measurement/$strategy.json \
	      --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_1/dsp_iir_multi_$strategy.json; \
	    ./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
	                     ./data_generated/roomeq/generated/$algo/small_stereo_2_1/dsp_iir_multi_$strategy.json; \
	  done \
	done

[group('qa-roomeq-multi')]
qa-roomeq-multi-small-stereo-22-mso: ensure-venv
	@for strategy in minimax weighted_sum variance_penalized; do \
	  for algo in fem; do \
	    mkdir -p ./data_generated/roomeq/generated/$algo/small_stereo_2_2_mso; \
	    echo "=== Multi-measurement $strategy ($algo) small_stereo_2_2_mso ==="; \
	    cargo run --bin roomeq --release -- \
	      --config       ./crates/autoeq/data_tests/roomeq/generated/$algo/small_stereo_2_2_mso/config.json \
	      --override-config ./crates/autoeq/data_tests/roomeq/generated/optimiser-config/multi_measurement/$strategy.json \
	      --output       ./data_generated/roomeq/generated/$algo/small_stereo_2_2_mso/dsp_iir_multi_$strategy.json; \
	    ./venv/bin/python3 ./crates/autoeq/scripts/display-roomeq.py \
	                     ./data_generated/roomeq/generated/$algo/small_stereo_2_2_mso/dsp_iir_multi_$strategy.json; \
	  done \
	done

# New comprehensive QA using roomeq-qa-full binary
[group('qa-roomeq')]
qa-roomeq-coverage: prod-autoeq
	cargo run --bin roomeq-qa-coverage --release

[group('qa-roomeq')]
qa-roomeq-quick: prod-autoeq
	cargo run --bin roomeq-qa-coverage --release -- --quick --maxeval 200

[group('qa-roomeq')]
qa-roomeq-list:
	cargo run --bin roomeq-qa-coverage --release -- --list

[group('qa-roomeq')]
qa-roomeq-matrix:
	cargo run --bin roomeq-qa-coverage --release -- --matrix

[group('qa-roomeq')]
qa-roomeq-synthetic:
	cargo run --bin roomeq-qa-synthetic --no-default-features --release

[group('qa-roomeq')]
qa-roomeq-multiseat-guards:
	cargo run --bin roomeq-qa-synthetic --no-default-features --release -- --multiseat-guards-only

[group('qa-roomeq')]
qa-roomeq-home-cinema:
	cargo test -p autoeq home_cinema --lib -- --nocapture
	cargo test -p autoeq validate_bass_management --lib -- --nocapture
	cargo test -p autoeq derives_all_channel_multiseat_primary_weights --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_on_grid_mismatch --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_on_invalid_weight_policy --lib -- --nocapture
	cargo test -p autoeq reports_grid_mismatch_as_channel_skip --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_when_primary_seat_is_invalid --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_constraints_fail --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_broadband_level_collapses --lib -- --nocapture
	cargo test -p autoeq reports_guardrail_rejection_without_claiming_applied --lib -- --nocapture
	cargo test -p autoeq home_cinema_all_channel_multiseat_guardrail_reruns_and_reports_rejection --lib -- --nocapture
	cargo test -p autoeq reports_all_channel_multiseat_null_guard --lib -- --nocapture
	cargo test -p autoeq reports_all_channel_multiseat_by_role_group_and_excludes_subs --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-all-channel-multiseat:
	cargo test -p autoeq derives_all_channel_multiseat_primary_weights --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_on_grid_mismatch --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_on_invalid_weight_policy --lib -- --nocapture
	cargo test -p autoeq reports_grid_mismatch_as_channel_skip --lib -- --nocapture
	cargo test -p autoeq skips_all_channel_multiseat_when_primary_seat_is_invalid --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_constraints_fail --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_broadband_level_collapses --lib -- --nocapture
	cargo test -p autoeq reports_guardrail_rejection_without_claiming_applied --lib -- --nocapture
	cargo test -p autoeq home_cinema_all_channel_multiseat_guardrail_reruns_and_reports_rejection --lib -- --nocapture
	cargo test -p autoeq reports_all_channel_multiseat_null_guard --lib -- --nocapture
	cargo test -p autoeq reports_all_channel_multiseat_by_role_group_and_excludes_subs --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-bass-management:
	cargo test -p autoeq home_cinema_bass_management --lib -- --nocapture
	cargo test -p autoeq bass_headroom_uses_supplied_sample_rate_for_crossover_response --lib -- --nocapture
	cargo test -p autoeq home_cinema_bass_management_sub_curve_is_predicted_from_routes --lib -- --nocapture
	cargo test -p autoeq home_cinema_bass_bus_curve_is_predicted_across_multiple_sub_outputs --lib -- --nocapture
	cargo test -p autoeq representative_bass_route_signature_uses_emitted_route_shape --lib -- --nocapture
	cargo test -p autoeq home_cinema_bass_management_workflow_applies_configured_group_crossovers_when_optimization_disabled --lib -- --nocapture
	cargo test -p autoeq validate_bass_management --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-export-routing:
	cargo test -p autoeq external_exports_reject_routed_bass_management --lib -- --nocapture
	cargo test -p sotf-player test_build_room_eq_graph_emits_route_dsp_and_output_correction --lib -- --nocapture
	cargo test -p sotf-player test_build_room_eq_graph_applies_shared_sub_chain_to_physical_sub_routes --lib -- --nocapture
	cargo test -p sotf-player test_build_room_eq_graph_preserves_non_routing_global_plugins --lib -- --nocapture
	cargo test -p sotf-player test_requires_room_eq_graph_with_routed_bass_management --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-dsp-consistency:
	cargo test -p autoeq reported_ --lib -- --nocapture
	cargo test -p autoeq timing_diagnostics --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-phase-critical:
	cargo test -p autoeq gd_opt -- --nocapture
	cargo test -p autoeq frequency_grid --lib -- --nocapture
	cargo run --bin roomeq-qa-synthetic --no-default-features --release -- --multiseat-guards-only

[group('qa-roomeq')]
qa-roomeq-perceptual:
	cargo test -p autoeq perceptual_metrics --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_constraints_fail --lib -- --nocapture
	cargo test -p autoeq rejects_all_channel_multiseat_when_broadband_level_collapses --lib -- --nocapture
	cargo test -p autoeq reports_guardrail_rejection_without_claiming_applied --lib -- --nocapture
	cargo test -p autoeq home_cinema_all_channel_multiseat_guardrail_reruns_and_reports_rejection --lib -- --nocapture
	cargo test -p autoeq reports_all_channel_multiseat_null_guard --lib -- --nocapture

[group('qa-roomeq')]
qa-roomeq-gd:
	cargo test -p autoeq gd_opt -- --nocapture

[group('qa-roomeq')]
qa-roomeq-features:
	cargo run --bin roomeq-qa-features --no-default-features --release

# Compact CI-friendly QA run: bounded fuzzer + small coverage subset.
# Typical wall time under 3 minutes on modern hardware.
[group('qa-roomeq')]
qa-roomeq-ci:
	cargo run --bin roomeq-fuzzer --release -- -n 50
	cargo run --bin roomeq-qa-coverage --release -- --quick --maxeval 200
	cargo test -p autoeq reported_ --lib -- --nocapture
	cargo test -p autoeq timing_diagnostics --lib -- --nocapture
	cargo test -p autoeq gd_opt -- --nocapture
	cargo test -p autoeq frequency_grid --lib -- --nocapture
	cargo run --bin roomeq-qa-synthetic --no-default-features --release -- --multiseat-guards-only
	cargo test -p autoeq home_cinema --lib -- --nocapture
	cargo test -p autoeq home_cinema_bass_management --lib -- --nocapture
	cargo test -p autoeq validate_bass_management --lib -- --nocapture
	cargo test -p autoeq perceptual_metrics --lib -- --nocapture

# Memory-capped integration test runner for autoeq.
#
# The BEM multimode tests spin up one optimizer per test × `cargo test`'s
# worker count. Each optimizer internally forks rayon evaluators over all
# cores, so the effective thread count is num_cpus × num_cpus. On small-
# RAM boxes this OOMs. Cap via `RUST_TEST_THREADS` (default = 2 so BEM
# tests still interleave but memory stays bounded). Override with
# `just test-autoeq threads=N`.
[group('qa-roomeq')]
test-autoeq threads="2":
	RUST_TEST_THREADS={{threads}} cargo test -p autoeq --tests --release