autoeq 0.4.36

Automatic equalization for speakers, headphones and rooms!
Documentation
[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 ./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-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       ./data_tests/roomeq/generated/$algo/small_stereo_2_0/config.json \
		    --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/small_stereo_2_1/config.json \
		    --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/small_stereo_2_2/config.json \
		    --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/medium_surround_5_1/config.json \
		    --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/small_stereo_2_0/config.json \
	      --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/small_stereo_2_1/config.json \
	      --override-config ./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 ./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       ./data_tests/roomeq/generated/$algo/small_stereo_2_2_mso/config.json \
	      --override-config ./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 ./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-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

# 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