beqforge 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beqforge-0.2.4/.gitattributes +1 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/workflows/build-executable.yml +8 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/PKG-INFO +43 -4
- {beqforge-0.2.2 → beqforge-0.2.4}/README.md +42 -3
- {beqforge-0.2.2 → beqforge-0.2.4}/TODO.md +26 -23
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/__init__.py +1 -1
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/AGENTS.md +6 -2
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/pipeline.py +26 -3
- beqforge-0.2.4/packaging/designer/beqforge-designer.cmd +33 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/done-design-and-pipeline.md +31 -0
- beqforge-0.2.4/plans/parameter-space-optimisation.md +75 -0
- beqforge-0.2.4/plans/texture-yardstick-sub-band.md +215 -0
- beqforge-0.2.4/plans/texture-yardstick-sub-band.patch +154 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_designer_server.py +61 -5
- beqforge-0.2.4/tests/test_design_ripple_band.py +56 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_designer_server_startup.py +19 -2
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/designer_server.py +93 -14
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/smoke_test_exe.py +21 -10
- {beqforge-0.2.2 → beqforge-0.2.4}/.dockerignore +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/dependabot.yml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/workflows/build-designer-image.yml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/workflows/build-device-check.yml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.github/workflows/tests.yml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.gitignore +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/.gitignore +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/beqforge.iml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/editor.xml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/misc.xml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/modules.xml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/.idea/vcs.xml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/AGENTS.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/CLAUDE.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/LICENCE.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/AGENTS.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/biquad.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/provenance.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/publication.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beq_common/types.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/__init__.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/accept.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/beqd.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/biquad.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/charts.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/cli.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/design.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/designer.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/diagnose.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/explain.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/extraction.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/filters.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/harness.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/identify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/material.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/record.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/reference.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/rolloff.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge/verify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge-device-check.spec +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge.spec +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/report.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/AGENTS.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/__init__.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/cli.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/core.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/device-check.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/docs/optimiser.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/evidence_validation.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/negative_corpus.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/packaging/designer/Dockerfile +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/packaging/designer/smoke.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/research-design-decisions.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/pyproject.toml +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/__init__.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/conftest.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_cli.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_accept.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_channel_evidence.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_charts.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_corpus.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_design.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_designer.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_diagnose.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_evidence.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_explain.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_extraction.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_filters.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_guard.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_harness.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_headroom.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_hum.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_identify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_parallel.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_pipeline.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_playback.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_publication.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_record.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_reference.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_references.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_strategy_config.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_timings.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_design_verify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_designer_image.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_extract_layout.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_optimiser.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/__init__.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/design_beq.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/probe.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/extract.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/ledger_template.html +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/negative_corpus.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/render_ledger.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/replay.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/smoke_test_install.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/summarise.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/tools/validate_evidence.py +0 -0
- {beqforge-0.2.2 → beqforge-0.2.4}/uv.lock +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.cmd text eol=crlf
|
|
@@ -226,6 +226,14 @@ jobs:
|
|
|
226
226
|
env:
|
|
227
227
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
228
228
|
|
|
229
|
+
- name: Attach the Windows designer launcher
|
|
230
|
+
if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/')
|
|
231
|
+
uses: softprops/action-gh-release@v3
|
|
232
|
+
with:
|
|
233
|
+
files: packaging/designer/beqforge-designer.cmd
|
|
234
|
+
env:
|
|
235
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
236
|
+
|
|
229
237
|
publish:
|
|
230
238
|
name: Publish Python package to PyPI
|
|
231
239
|
needs: [validated_tests, package, build, create_release]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: beqforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Audio-derived BEQ design, published filter optimisation and DSP validation
|
|
5
5
|
License-File: LICENCE.md
|
|
6
6
|
Requires-Python: <3.15,>=3.13
|
|
@@ -187,6 +187,12 @@ naming the array, rather than a design decline. Inline requests remain supported
|
|
|
187
187
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
188
188
|
measures cold and warm requests.
|
|
189
189
|
|
|
190
|
+
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
191
|
+
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
192
|
+
caller can send each server as many designs as it takes rather than be configured with that
|
|
193
|
+
number. Each design's fit pool gets an equal share of the machine's cores (`BEQ_FIT_WORKERS`,
|
|
194
|
+
if set, stays each design's own). The share changes how long a fit takes, never its answer.
|
|
195
|
+
|
|
190
196
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
191
197
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
192
198
|
environment list. `--help` lists the variable beside each option. Other subcommands retain
|
|
@@ -207,6 +213,7 @@ their existing CLI configuration.
|
|
|
207
213
|
| `--record-dir` | `BEQFORGE_RECORD_DIR` | path |
|
|
208
214
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
209
215
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
216
|
+
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
210
217
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
211
218
|
|
|
212
219
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -270,9 +277,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
270
277
|
|
|
271
278
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
272
279
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
273
|
-
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in
|
|
274
|
-
worker
|
|
275
|
-
caller's timeout for that. `docker stop` stops a design in progress.
|
|
280
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in separate
|
|
281
|
+
worker processes, `BEQFORGE_WORKERS` (default 1) at a time, so a caller sending more at once
|
|
282
|
+
queues them; size the caller's timeout for that. `docker stop` stops a design in progress.
|
|
276
283
|
|
|
277
284
|
Build and check it locally from the repository root:
|
|
278
285
|
|
|
@@ -284,6 +291,38 @@ python3 packaging/designer/smoke.py beqforge-designer
|
|
|
284
291
|
The smoke script checks `/health` and one design inline and one by reference;
|
|
285
292
|
`tests/test_designer_image.py` runs the same check against an in-process server.
|
|
286
293
|
|
|
294
|
+
### Running the designer on Windows
|
|
295
|
+
|
|
296
|
+
The release's `beqforge-windows-x86_64.exe` runs the same server natively, for a beqdesigner
|
|
297
|
+
container (or install) that calls out to a Windows machine. Put `beqforge-designer.cmd`, which
|
|
298
|
+
is attached to the same release (source: `packaging/designer/beqforge-designer.cmd`), beside the
|
|
299
|
+
exe and double-click it, or run the exe directly:
|
|
300
|
+
|
|
301
|
+
```bat
|
|
302
|
+
beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
303
|
+
--shared-root D:\beq\work --cache-dir %LOCALAPPDATA%\beqforge\cache
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The launcher sets the image's defaults through the environment variables above, each only if
|
|
307
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
308
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache`. Arguments pass through to `serve-designer`.
|
|
309
|
+
|
|
310
|
+
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
311
|
+
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
312
|
+
networks.
|
|
313
|
+
* **Shared root.** For audio by reference, set `BEQFORGE_SHARED_ROOT` (or edit the commented
|
|
314
|
+
line in the launcher) to the Windows folder bind-mounted into beqdesigner's container as its
|
|
315
|
+
work directory. Without it, beqdesigner must send audio inline.
|
|
316
|
+
* **beqdesigner's side.** Under Docker Desktop on the same machine, register
|
|
317
|
+
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
318
|
+
Check `http://<address>:8420/health` from there first.
|
|
319
|
+
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
320
|
+
design more than one title at a time; `/health` reports it as `parallel`.
|
|
321
|
+
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
322
|
+
press Ctrl+C, to stop.
|
|
323
|
+
|
|
324
|
+
### Building and releasing
|
|
325
|
+
|
|
287
326
|
For a machine without Python/`uv`, a standalone `beqforge` executable (every subcommand,
|
|
288
327
|
including `serve-designer`) is built for Linux/macOS/Windows by
|
|
289
328
|
`.github/workflows/build-executable.yml` on every `vX.Y.Z` tag, and attached to the matching
|
|
@@ -161,6 +161,12 @@ naming the array, rather than a design decline. Inline requests remain supported
|
|
|
161
161
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
162
162
|
measures cold and warm requests.
|
|
163
163
|
|
|
164
|
+
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
165
|
+
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
166
|
+
caller can send each server as many designs as it takes rather than be configured with that
|
|
167
|
+
number. Each design's fit pool gets an equal share of the machine's cores (`BEQ_FIT_WORKERS`,
|
|
168
|
+
if set, stays each design's own). The share changes how long a fit takes, never its answer.
|
|
169
|
+
|
|
164
170
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
165
171
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
166
172
|
environment list. `--help` lists the variable beside each option. Other subcommands retain
|
|
@@ -181,6 +187,7 @@ their existing CLI configuration.
|
|
|
181
187
|
| `--record-dir` | `BEQFORGE_RECORD_DIR` | path |
|
|
182
188
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
183
189
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
190
|
+
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
184
191
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
185
192
|
|
|
186
193
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -244,9 +251,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
244
251
|
|
|
245
252
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
246
253
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
247
|
-
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in
|
|
248
|
-
worker
|
|
249
|
-
caller's timeout for that. `docker stop` stops a design in progress.
|
|
254
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in separate
|
|
255
|
+
worker processes, `BEQFORGE_WORKERS` (default 1) at a time, so a caller sending more at once
|
|
256
|
+
queues them; size the caller's timeout for that. `docker stop` stops a design in progress.
|
|
250
257
|
|
|
251
258
|
Build and check it locally from the repository root:
|
|
252
259
|
|
|
@@ -258,6 +265,38 @@ python3 packaging/designer/smoke.py beqforge-designer
|
|
|
258
265
|
The smoke script checks `/health` and one design inline and one by reference;
|
|
259
266
|
`tests/test_designer_image.py` runs the same check against an in-process server.
|
|
260
267
|
|
|
268
|
+
### Running the designer on Windows
|
|
269
|
+
|
|
270
|
+
The release's `beqforge-windows-x86_64.exe` runs the same server natively, for a beqdesigner
|
|
271
|
+
container (or install) that calls out to a Windows machine. Put `beqforge-designer.cmd`, which
|
|
272
|
+
is attached to the same release (source: `packaging/designer/beqforge-designer.cmd`), beside the
|
|
273
|
+
exe and double-click it, or run the exe directly:
|
|
274
|
+
|
|
275
|
+
```bat
|
|
276
|
+
beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
277
|
+
--shared-root D:\beq\work --cache-dir %LOCALAPPDATA%\beqforge\cache
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The launcher sets the image's defaults through the environment variables above, each only if
|
|
281
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
282
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache`. Arguments pass through to `serve-designer`.
|
|
283
|
+
|
|
284
|
+
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
285
|
+
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
286
|
+
networks.
|
|
287
|
+
* **Shared root.** For audio by reference, set `BEQFORGE_SHARED_ROOT` (or edit the commented
|
|
288
|
+
line in the launcher) to the Windows folder bind-mounted into beqdesigner's container as its
|
|
289
|
+
work directory. Without it, beqdesigner must send audio inline.
|
|
290
|
+
* **beqdesigner's side.** Under Docker Desktop on the same machine, register
|
|
291
|
+
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
292
|
+
Check `http://<address>:8420/health` from there first.
|
|
293
|
+
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
294
|
+
design more than one title at a time; `/health` reports it as `parallel`.
|
|
295
|
+
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
296
|
+
press Ctrl+C, to stop.
|
|
297
|
+
|
|
298
|
+
### Building and releasing
|
|
299
|
+
|
|
261
300
|
For a machine without Python/`uv`, a standalone `beqforge` executable (every subcommand,
|
|
262
301
|
including `serve-designer`) is built for Linux/macOS/Windows by
|
|
263
302
|
`.github/workflows/build-executable.yml` on every `vX.Y.Z` tag, and attached to the matching
|
|
@@ -18,29 +18,32 @@ are retained so records and code comments remain traceable. Completed items are
|
|
|
18
18
|
| Priority | ID | Status | Value, work and blocker or reason for parking |
|
|
19
19
|
| --- | --- | --- | --- |
|
|
20
20
|
| 1 | Playback contract | open | Reconcile the supplied bass-management model with the model actually used for targets, verification and clipping. Only `lpf_fs` is consumed today; check the ignored fields before claiming the listener's clipping cost. Caller propagation is an external subtask. |
|
|
21
|
-
| 2 |
|
|
22
|
-
| 3 |
|
|
23
|
-
| 4 |
|
|
24
|
-
| 5 | Steep-filter
|
|
25
|
-
| 6 |
|
|
26
|
-
| 7 |
|
|
27
|
-
| 8 |
|
|
28
|
-
| 9 |
|
|
29
|
-
| 10 |
|
|
30
|
-
| 11 |
|
|
31
|
-
| 12 |
|
|
32
|
-
| 13 |
|
|
33
|
-
| 14 |
|
|
34
|
-
| 15 |
|
|
35
|
-
| 16 |
|
|
36
|
-
| 17 |
|
|
37
|
-
| 18 |
|
|
38
|
-
| 19 |
|
|
39
|
-
| 20 |
|
|
40
|
-
| 21 |
|
|
41
|
-
| 22 |
|
|
42
|
-
| 23 |
|
|
43
|
-
| 24 |
|
|
21
|
+
| 2 | Texture yardstick band | open (E10 only) | Implemented: the mix's ripple is measured about a cubic over the unchanged band, so the LFE knee is trend. 9 (2009), Beauty and the Beast, Crazy, Stupid, Love and Despicable Me 3 now accept; Carrie stays declined. Probe and protocol unchanged; corpus 0/45 gate pass. Neither width-normalised form separated `broadband/5`. The degree was chosen on corpus and library data, so it needs the fresh E10 validation before it counts as settled; see [the outcome](plans/texture-yardstick-sub-band.md#outcome-17-october-2026). |
|
|
22
|
+
| 3 | E10 | open | Predeclare fresh content-goal validation before further tuning or a default change. Existing seeds and corpus have repeatedly informed development; writing the protocol needs no new audio. |
|
|
23
|
+
| 4 | E1 | open | Add record-only scene-held-out verification: evidence independent of the material that generated the target. A gate needs a separate demonstrated benefit. |
|
|
24
|
+
| 5 | Steep-filter scoring | open | Implement coloured/codec-like floors and recipe-specific truth bands under E10; inspect shape failures and published-ceiling excess. Existing titles can supply programme texture. |
|
|
25
|
+
| 6 | Steep-filter default | blocked | Recovery improves substantially with `--content-edge`, but changing the default needs E10, the scoring above, listening judgements and more independent real steep titles. Safe House already works with the default; keep the option off meanwhile. |
|
|
26
|
+
| 7 | F2 / device behaviour | in progress; first hardware pilot measured | Coefficient quantisation does not establish hardware accuracy. Limit changes need measured internal arithmetic/noise/limit cycles on a named device and a capture setup; the [measurement plan](plans/F2-device-precision-validation.md) specifies Python sweep/capture, optional REW cross-checks, software controls, single-entry/full-catalogue analysis and a standalone cross-platform executable. The `beqforge-device-check` preview includes frozen manifests, capture/recovery, CLI-only miniDSP controls (minidsp-rs 0.1.12, batched through `-f`) with float32 transport attribution, staged qualification/convergence, uncertainty-aware filter checks, entry/unique-cascade frequency distributions and matched-device deltas. First verified pilot on a miniDSP 2x4 HD (USB loopback, 96 kHz): the device plays its float32 coefficients as predicted (worst 0.0074 dB, 2-200 Hz), while float32 coefficients themselves put 18.8 dB of error into a 5 Hz Q 6 peak; see the plan, section 12. First catalogue sample (50 cascades): 49 play as predicted on every bin (worst 0.0082 dB); the 50th was clipped by the device and is to be remeasured (F2a, plan section 13). Next: the `grid` and `boundary` suites on hardware to map where float32 coefficients stop representing low, high-Q filters. No Q-limit change yet. |
|
|
27
|
+
| 8 | Validation coverage (former TODO 10) | blocked | Broad real-programme generalisation needs the missing shapes and independently characterised paired releases listed below. Sparse titles exist; their absolute truth and wider sparse coverage remain missing. |
|
|
28
|
+
| 9 | Identification | blocked | Generalising `N(f)` and its fit band needs independent, varied source material, especially paired releases. Sensitivity experiments can proceed; programme-only uncertainty cannot identify the cause of a droop. |
|
|
29
|
+
| 10 | E4 | parked | No measured failure has been attributed to isolated ceiling holes. Reopen if unevenness or E9 excess is traced to such bins; smoothing may only reduce the raw licence. |
|
|
30
|
+
| 11 | F4 follow-up | parked | Both fitter-objective selection spikes lost passes/true positives. Judge several fits only with a demonstrated usable alternative discarded before `_judge`; a smaller residual is insufficient evidence. |
|
|
31
|
+
| 12 | F3 follow-up | parked | A fifth section moved no verdict when tried and costs more fitting. Reopen if an evidence-supported target has a demonstrated usable fit beyond four sections and no usable fit within four. |
|
|
32
|
+
| 13 | T2 | parked | The steep-variant audit found no swept cap binding a weak winner. Derive caps only when a binding cap demonstrably prevents a better supported result. |
|
|
33
|
+
| 14 | Residual (former TODO 5) | open | Correct the reported target error and its actual measurement band without changing fit selection. Primarily audit accuracy; any escalation/objective change is a separate, higher-impact decision. Non-parametric reporting is already allowed upstream. |
|
|
34
|
+
| 15 | Authored features | parked | Manual `--exclude` works; no validated automatic detector exists. Reopen with real labelled features and negatives showing that detection preserves legitimate bass. |
|
|
35
|
+
| 16 | Dynamic processing (former TODO 11) | parked | No suspected real case has scoped a useful tripwire. Reopen when compression/limiting demonstrably compromises this correction; non-LTI processing alone does not prove every BEQ is invalid. |
|
|
36
|
+
| 17 | Tilted passband reference (goal/corpus follow-up) | parked | No real in-sub-band case needs a sloping reference yet; Dossier's abstention was decided. Reopen with one. Goal-aware corpus labels belong to the open E10 protocol work. Nearest so far: 13 Going on 30's mix tilts about −7 dB/oct above 50 Hz (bass is coherent across channels, ~200 Hz is not) and finds its plateau only through the one-sided fallback. That reference reads a 36 dB deficit below 25 Hz; each channel's own plateau reads 30-34 dB. A reference built from the channels would be 2-5 dB less generous there (see plans/done-design-and-pipeline.md, 11 October 2026). |
|
|
37
|
+
| 18 | Mixed-channel policy | parked | `channel_scope` is currently an unpopulated optional response field, not a computed `mixed` finding awaiting a gate. Reopen with a case where existing per-channel restoration and common pricing produce an unsuitable mix correction. |
|
|
38
|
+
| 19 | Fraction dial (former TODO 7) | parked | No user/title has needed partial licensed restoration as a preference; tilt and tolerance already ship. Reopen with a concrete requested outcome, then price and validate it like every target. |
|
|
39
|
+
| 20 | Protective corner (former TODO 9) | parked | The corner is already derived from boost cap and implied order, floored by `lowest_frequency_hz`. No case requires an independent override or different fallback alignment; reopen with one. |
|
|
40
|
+
| 21 | Device configuration on the wire | parked | The current contract deliberately omits device preference; server flags already configure realisation. Reopen if one server must serve different device formats/rates per request; then agree an upstream extension. Hardware validity remains F2. |
|
|
41
|
+
| 22 | Catalogue comparison (former TODO 8) | parked | A disagreement detector has no demonstrated accuracy benefit. Reopen with an independent discrepancy to investigate; authored filters remain comparisons, never calibration targets. |
|
|
42
|
+
| 23 | Performance | parked | Further reuse changes cost, not filter accuracy. Reopen after measuring repeated numerical identities or material remaining analysis cost; missed-fit search belongs to F3/F4 instead. |
|
|
43
|
+
| 24 | Chart peak bias | parked | Deliberate catalogue-compatible per-bin maximum; it affects presentation only. Reopen if a reader needs an alternative percentile view; keep it distinct from the 95th-percentile evidence envelope. |
|
|
44
|
+
| 25 | Documentation citations | open | Remove or repoint orphan references and stale descriptions of shipped behaviour. Low filter impact, but prevents future changes being based on obsolete assumptions. |
|
|
45
|
+
| 26 | Texture yardstick retune (library review) | parked | The cubic (row 2) keeps the 200 Hz band only because the sub-band cap admitted one synthetic negative, `broadband/5`: chance unevenness in noise bursts, at a 2.6 dB boost. With 45 gated cases a single case moves the gate's upper bound from 0.079 to 0.118. Carrie is still declined (0.85x about a cubic, 1.33x with the cap). Reopen when the beqdesigner library review (~1,290 titles) is complete. Then retune against its declines: the band edge, the trend degree, and whether the gate weights a false acceptance by its boost. The library holds no known negative, so it can expose wrong declines but not measure false acceptances. Any retune still needs the corpus or a fresh E10 set for that, ideally with negatives built from real programme texture (row 5). See [the outcome](plans/texture-yardstick-sub-band.md#outcome-17-october-2026). |
|
|
46
|
+
| 27 | Parameter-space optimisation for several profiles | open | beqdesigner's main window will let a filter designer optimise a filter for chosen device profiles before publishing, in freq/Q/gain terms (a BEQ is published and loaded as parameters, so no catalogue schema change), minimising the worst error across all chosen profiles at once. The search belongs in `beqoptimiser`, beside `optimise()`; beqdesigner builds the interface. See [the request](plans/parameter-space-optimisation.md). |
|
|
44
47
|
|
|
45
48
|
## Review evidence
|
|
46
49
|
|
|
@@ -43,7 +43,7 @@ still holds: no other API, no other service, and the server is a thin transport
|
|
|
43
43
|
| `beqforge/explain.py` | The plain-language account a designer response leads with — `found` (reference plateau, which channels carry it and which are silent, down to where there is real bass content, judged band, goal), `correction` (the cascade, then frequency by frequency what was wanted, what the content supports and before → after), `clipping` (the sub-feed peak and how far to turn the sub down, on the request's or the assumed bass management) and `alternatives` (why each other candidate lost). Reads the `Report`; measures and decides nothing. |
|
|
44
44
|
| `beqforge/designer.py` | The `design(request) -> response` adapter between beqdesigner's `designer-interface.md` v1.1 and `beqforge.pipeline.run` — request/response dataclasses (1.1: every candidate the judge failed goes back in `rejected` with its failures, review only, beside the answer or the decline), wire-format (de)serialisation and the field-by-field mapping onto `Report`/`Candidate`/`Verdict`. Also `validate_response`, an independent re-implementation of beqdesigner's own §3-§5 rules (that repo isn't importable — PyQt6), called by the server before a response ever goes on the wire. Pure and independently testable; no socket. |
|
|
45
45
|
| `tools/design_beq.py` | **The entry point.** One command, a filter and its reasoning. `--charts DIR` for the pictures; writes a `.run.json.gz` record beside the material unless `--no-record`. |
|
|
46
|
-
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in
|
|
46
|
+
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in a long-lived worker process started with "spawn" (`_DesignWorker`): the fitter forks worker processes (`beqforge.filters.PARALLEL_FITS`) when a fit escalates past one section count, and forking a multi-threaded process risks a deadlock, so the process that forks must stay single-threaded — the worker's log records come back over a `SimpleQueue`, not a `Queue`, for that reason. A worker designs one title at a time; `--workers N` (1-8) runs N of them, each request taking the next idle one, and `/health` reports N as `parallel` so a caller can size its sends from the designer. With N > 1 each worker's fit pool is `filters.FIT_WORKERS // N` (`fit_share`) unless `BEQ_FIT_WORKERS` is set: pool size changes timing, never an answer. A worker that dies answers 500 and is replaced; the others carry on. SIGTERM shuts down like Ctrl-C, stopping the worker's process group, fit pool included. Every response names its build (`beqforge_revision`); `--record-dir DIR` also writes each request's run record, named by a digest of the request audio, so a review-queue entry can be replayed. `--cache-dir DIR` keeps the stage cache there (`cache.DirStore`, one file per entry, atomic): a repeat request for a title skips the analysis — 110 s cold, 50 s warm on a 2-hour 8-channel title. Each request logs a timing line (read, parse, decode, design, respond, queued); `tools/experiments/request_timing.py` reports them. `--shared-root DIR` accepts audio by reference (contract 1.2, beqdesigner's `design/designer-by-reference.md` §3): an array names a WAV relative to DIR plus the SHA-256 of its decoded column, and `beqforge/reference.py` resolves, decodes and checks it; one that cannot be honoured is a 422 naming the array, never a decline. `GET /health` reports `contract_version`, `shared_root` and `parallel`. |
|
|
47
47
|
| `tools/replay.py` | Redraw charts and export to beqdesigner from a record — no rerun, no extraction. Refuses on a stale record unless `--force`. |
|
|
48
48
|
| `tools/extract.py` | ffmpeg → 1 kHz per-channel `.npz`. Requires an explicit supported channel layout; preserves layout provenance. Needs no beqdesigner. |
|
|
49
49
|
| `tools/summarise.py` | Sanity-check an extraction before using it. |
|
|
@@ -333,7 +333,11 @@ least `min_judge_octaves` above the floor — no fixed top (historical review T6
|
|
|
333
333
|
(`pipeline.passband_ripple_db`) is left alone: it abstains with `within_programme_ripple`
|
|
334
334
|
before any strategy runs. That ripple is the title's own yardstick, so there is no constant.
|
|
335
335
|
Crest to trough rather than a one-sided dip, because the reference sits on the crests. This
|
|
336
|
-
is what took the negative corpus from 4 false acceptances in 45 to 0.
|
|
336
|
+
is what took the negative corpus from 4 false acceptances in 45 to 0. The mix's ripple is
|
|
337
|
+
taken about a cubic in log frequency, so an LFE-led mix's knee above the crossover is trend,
|
|
338
|
+
not texture; each channel's is taken about a line. Don't shorten the band instead:
|
|
339
|
+
crest to trough shrinks with bin count, and the 1x boundary was calibrated on the full band
|
|
340
|
+
([plans/texture-yardstick-sub-band.md](../plans/texture-yardstick-sub-band.md)).
|
|
337
341
|
* **Steep filters: the default declines them; `judge_from_content_edge` is an opt-in that
|
|
338
342
|
recovers them and lifts some noise.** Below a steep filter's edge the priced target already
|
|
339
343
|
falls into the quiet floor with the programme — contrast bounds it — and the shape clauses
|
|
@@ -772,6 +772,9 @@ MAINS_HZ = (50.0, 60.0)
|
|
|
772
772
|
# inside the 50 Hz spacing so it never reaches the next harmonic
|
|
773
773
|
HUM_NOTCH_HALF_WIDTH_HZ = 2.5
|
|
774
774
|
HUM_SURROUND_HALF_WIDTH_HZ = 10.0
|
|
775
|
+
# the mix's texture is measured about a cubic in log frequency, so an LFE-led mix's knee above
|
|
776
|
+
# the crossover is trend, not ripple (plans/texture-yardstick-sub-band.md)
|
|
777
|
+
MIX_TEXTURE_TREND_DEGREE = 3
|
|
775
778
|
|
|
776
779
|
|
|
777
780
|
def mains_hum_notches(
|
|
@@ -815,7 +818,11 @@ def mains_hum_notches(
|
|
|
815
818
|
|
|
816
819
|
|
|
817
820
|
def passband_ripple_db(
|
|
818
|
-
material: Material,
|
|
821
|
+
material: Material,
|
|
822
|
+
params: "PipelineParams",
|
|
823
|
+
noise_floor_hz: float = math.nan,
|
|
824
|
+
*,
|
|
825
|
+
trend_degree: int = 1,
|
|
819
826
|
) -> tuple[float, tuple[float, float]] | None:
|
|
820
827
|
"""The programme's own texture where nothing is missing: crest-to-trough, dB, and where.
|
|
821
828
|
|
|
@@ -823,6 +830,17 @@ def passband_ripple_db(
|
|
|
823
830
|
correction — the top of the judged band to the top of the analysis band — as the spread of
|
|
824
831
|
the curve about its own log-frequency trend, so a tilted passband is not read as ripple.
|
|
825
832
|
|
|
833
|
+
The trend is a polynomial of `trend_degree` in log frequency. The mix's texture blocker
|
|
834
|
+
asks for a cubic (`MIX_TEXTURE_TREND_DEGREE`). In an LFE-led mix the level falls away
|
|
835
|
+
where the LFE channel's content ends (60-100 Hz), a knee a straight line cannot remove, so
|
|
836
|
+
it read as ripple and withheld a real deficit: 9 (2009) measured 8.8 dB of "ripple" against
|
|
837
|
+
a 6.2 dB shortfall, 2.2 dB about a cubic. The band is not shortened instead. Crest to
|
|
838
|
+
trough shrinks with the number of bins even on noise, so ending it at the crossover lowered
|
|
839
|
+
every yardstick and admitted a broadband negative on the corpus. A cubic keeps the band the
|
|
840
|
+
1x boundary was calibrated on and leaves the negatives where they were: at most 0.76x about
|
|
841
|
+
a cubic against 0.77x about a line, with every real and filtered title at 1.08x and up.
|
|
842
|
+
Each channel's own rule (`channels_missing_low_end`) keeps the line.
|
|
843
|
+
|
|
826
844
|
Crest to trough, not a one-sided dip, because the reference sits near the 90th percentile
|
|
827
845
|
of the spectrum, on the crests: a shortfall measured from it spans the whole swing. On the
|
|
828
846
|
negative corpus (IMPROVEMENT_PLAN E2) every unfiltered title's low-end deficit came in
|
|
@@ -847,7 +865,7 @@ def passband_ripple_db(
|
|
|
847
865
|
return None
|
|
848
866
|
smooth = smooth_unexcluded(response, freqs, bands, 15, levels=True)[band]
|
|
849
867
|
octaves = np.log2(freqs[band])
|
|
850
|
-
about = smooth - np.polyval(np.polyfit(octaves, smooth,
|
|
868
|
+
about = smooth - np.polyval(np.polyfit(octaves, smooth, trend_degree), octaves)
|
|
851
869
|
return float(np.max(about) - np.min(about)), (low, high)
|
|
852
870
|
|
|
853
871
|
|
|
@@ -1550,7 +1568,12 @@ def analyse(
|
|
|
1550
1568
|
f"programme (tracking stops at {diagnosis.noise_floor_hz:.1f} Hz), so there "
|
|
1551
1569
|
"is no bass passband to restore towards; restoration withheld"
|
|
1552
1570
|
)
|
|
1553
|
-
texture = passband_ripple_db(
|
|
1571
|
+
texture = passband_ripple_db(
|
|
1572
|
+
material,
|
|
1573
|
+
params,
|
|
1574
|
+
diagnosis.noise_floor_hz,
|
|
1575
|
+
trend_degree=MIX_TEXTURE_TREND_DEGREE,
|
|
1576
|
+
)
|
|
1554
1577
|
low_end = low_end_deficit_db(material, params, diagnosis.noise_floor_hz)
|
|
1555
1578
|
if texture is not None and low_end is not None:
|
|
1556
1579
|
ripple, (edge_low, edge_high) = texture
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
rem Runs the beqforge designer for beqdesigner on Windows: `beqforge serve-designer`, with the
|
|
3
|
+
rem container image's defaults (README, "Running the designer on Windows"). Keep this file
|
|
4
|
+
rem beside the release's beqforge-windows-x86_64.exe and double-click it, or run it from a
|
|
5
|
+
rem prompt with any extra serve-designer options, e.g. `beqforge-designer.cmd --record-dir D:\beq\records`.
|
|
6
|
+
rem
|
|
7
|
+
rem Every BEQFORGE_* variable already set in the environment wins over the defaults below.
|
|
8
|
+
setlocal
|
|
9
|
+
|
|
10
|
+
rem beqdesigner's work directory, as this machine sees it, so audio arrives by reference rather
|
|
11
|
+
rem than inline. Uncomment and point it at the folder mounted into beqdesigner's container.
|
|
12
|
+
rem if not defined BEQFORGE_SHARED_ROOT set "BEQFORGE_SHARED_ROOT=D:\beq\work"
|
|
13
|
+
|
|
14
|
+
rem How many titles to design at once, if this machine can take more than one (0.2.4 on).
|
|
15
|
+
rem if not defined BEQFORGE_WORKERS set "BEQFORGE_WORKERS=2"
|
|
16
|
+
|
|
17
|
+
if not defined BEQFORGE_HOST set "BEQFORGE_HOST=0.0.0.0"
|
|
18
|
+
if not defined BEQFORGE_PORT set "BEQFORGE_PORT=8420"
|
|
19
|
+
if not defined BEQFORGE_CACHE_DIR set "BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache"
|
|
20
|
+
|
|
21
|
+
set "EXE=%~dp0beqforge-windows-x86_64.exe"
|
|
22
|
+
if not exist "%EXE%" set "EXE=%~dp0beqforge.exe"
|
|
23
|
+
if not exist "%EXE%" (
|
|
24
|
+
echo beqforge-windows-x86_64.exe was not found beside %~nx0
|
|
25
|
+
pause
|
|
26
|
+
exit /b 1
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
"%EXE%" serve-designer %*
|
|
30
|
+
set "STATUS=%ERRORLEVEL%"
|
|
31
|
+
rem keep the window open on a startup error, so a double-click shows why
|
|
32
|
+
if not "%STATUS%"=="0" pause
|
|
33
|
+
exit /b %STATUS%
|
|
@@ -748,6 +748,37 @@ beside the answer or the decline (probe unchanged; no decision touched).
|
|
|
748
748
|
caller's; `beqforge_revision` is already in every response for it.
|
|
749
749
|
|
|
750
750
|
|
|
751
|
+
## Several designs at once, advertised on `/health` (18 October 2026)
|
|
752
|
+
|
|
753
|
+
A Windows machine that could design two titles at once was added beside the container as a
|
|
754
|
+
second endpoint, and the only way to use it was two servers on two ports: one server had a
|
|
755
|
+
single design worker. Saying how many each endpoint takes in beqdesigner's profile also put a
|
|
756
|
+
fact about the designer's host in the caller's configuration.
|
|
757
|
+
|
|
758
|
+
`serve-designer --workers N` (`BEQFORGE_WORKERS`, 1-8, the range beqdesigner accepts for an
|
|
759
|
+
endpoint's `parallel`) now runs N `_DesignWorker`s, each still one spawned, single-threaded
|
|
760
|
+
process. A request takes the next idle worker and waits for one when none is, so arrival order
|
|
761
|
+
is kept and a worker that dies fails only its own request. `/health` gains `"parallel": N` for
|
|
762
|
+
a caller to size its sends from; beqdesigner was sent the caller-side proposal. With N > 1 each
|
|
763
|
+
worker's fit pool is `filters.FIT_WORKERS // N` (`fit_share`), so N designs do not each claim
|
|
764
|
+
the whole machine; an explicit `BEQ_FIT_WORKERS` is left as each design's own. `_run_fits`
|
|
765
|
+
returns results in submission order from fixed seeds, so pool size moves timing and never an
|
|
766
|
+
answer. Nothing under `beqforge/` changed, so the decision regression probe does not apply.
|
|
767
|
+
|
|
768
|
+
* **Checks.** New tests: with two workers a quick request is answered while a real design is
|
|
769
|
+
still running on the other (the same test fails with one worker, so it distinguishes them),
|
|
770
|
+
`/health` reports `parallel`, a count outside 1-8 is refused by the server and the CLI, the
|
|
771
|
+
option comes from the environment, and the fit share. Server, startup, image and parallel-fit
|
|
772
|
+
tests: 61 passed. `tools/smoke_test_exe.py` now starts the executable with `--workers 2` and
|
|
773
|
+
requires `parallel: 2`. Its three requests alternate between the workers, so both design for
|
|
774
|
+
real inside the frozen build and the cache hit is one worker reusing the other's entries. A
|
|
775
|
+
Linux PyInstaller build passed it, with no process left after shutdown. Windows and macOS
|
|
776
|
+
are covered by the release workflow's smoke test, not run here.
|
|
777
|
+
* **Windows packaging.** `packaging/designer/beqforge-designer.cmd` starts the release's
|
|
778
|
+
Windows executable with the container's defaults and is attached to each release; the README
|
|
779
|
+
gains "Running the designer on Windows".
|
|
780
|
+
|
|
781
|
+
|
|
751
782
|
## The designer server answers `/health` during a design (17 October 2026)
|
|
752
783
|
|
|
753
784
|
A deployed image went unhealthy while it designed correctly. `serve-designer` was a plain
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Optimising a filter's parameters for several device profiles at once
|
|
2
|
+
|
|
3
|
+
**Status: design, not built.** A request from beqdesigner (October 2026), for a feature in its main window: a filter
|
|
4
|
+
designer picks one or more device profiles, sees what each does to the filter, and asks for the filter to be optimised
|
|
5
|
+
before it is published. The person who owns both projects decided that the search belongs here, in `beqoptimiser`, and
|
|
6
|
+
beqdesigner only provides the interface. Parameter search is already listed as a follow-up in
|
|
7
|
+
[F2-catalogue-filter-optimisation.md](F2-catalogue-filter-optimisation.md).
|
|
8
|
+
|
|
9
|
+
## What is different from `optimise()`
|
|
10
|
+
|
|
11
|
+
`optimise()` searches the coefficients of one cascade for one rate and precision, and returns custom SOS. This request
|
|
12
|
+
needs:
|
|
13
|
+
|
|
14
|
+
1. **Parameters out, not coefficients.** The result is each section's `freq`, `q` and `gain` (and its type unchanged).
|
|
15
|
+
A BEQ is published and loaded as parameters: beqdesigner's projects and exports, the catalogue, and ezbeq (which
|
|
16
|
+
rebuilds RBJ coefficients from the parameters at each device's rate when it has none published for it) all carry
|
|
17
|
+
parameters. Optimised parameters therefore need no new catalogue schema and reach every consumer as they are.
|
|
18
|
+
2. **Several profiles jointly.** The same parameters are realised on every device. The search minimises the worst
|
|
19
|
+
matching-band error across all the profiles the caller passes (a minimax), so a change that helps one device cannot
|
|
20
|
+
quietly make another worse. The result reports each profile's error before and after.
|
|
21
|
+
3. **Representable parameters.** Only values beqdesigner can show and the exports keep: `freq` to 0.01 Hz, `q` (and a
|
|
22
|
+
shelf's `S`) to 0.0001, `gain` to 0.01 dB. Search on that grid, or round and re-validate, so the result is exactly
|
|
23
|
+
what is published.
|
|
24
|
+
|
|
25
|
+
## Proposed interface
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
optimise_parameters(
|
|
29
|
+
sections, # the cascade as authored: beqoptimiser.core.Section (type, freq, q, gain), in order
|
|
30
|
+
*,
|
|
31
|
+
profiles, # [(rate, precision), ...]: 48000 or 96000, with Float32() or FixedPoint(i, f)
|
|
32
|
+
settings=None, # Settings: margin, band, plus the parameter bounds below
|
|
33
|
+
) -> ParameterResult
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`ParameterResult` carries the outcome (with the same `replacement`/`improvement`/`no_replacement` rules as
|
|
37
|
+
`optimise()`), the new sections when there is one, and per profile: the original error, the candidate's error and its
|
|
38
|
+
guard (out-of-band) error, with the numerical version and settings for provenance.
|
|
39
|
+
|
|
40
|
+
* **Reference:** each section's ideal float64 RBJ response, from the authored parameters, as now.
|
|
41
|
+
* **Realised:** for each profile, the coefficients computed from the candidate parameters at that rate in float64,
|
|
42
|
+
then stored with that profile's precision (`quantise`), as a device that receives parameters (or coefficients
|
|
43
|
+
computed from them) would hold them.
|
|
44
|
+
* **Bounds:** the parameters may only move as far as keeps the filter the author's: proposed defaults of ±1% in
|
|
45
|
+
frequency, ±2% in Q and ±0.1 dB in gain per section, configurable. A result outside them is not a correction of
|
|
46
|
+
the same filter.
|
|
47
|
+
* **Sections it cannot change:** the request may contain other biquads (pass filters, a gain stage). They stay
|
|
48
|
+
fixed but count in the cascade's response; a cascade with nothing searchable returns `no_replacement`.
|
|
49
|
+
|
|
50
|
+
## Open points for this repository to decide
|
|
51
|
+
|
|
52
|
+
* **Whose RBJ:** the coefficients must be computed as the consumers compute them. beqdesigner (`model/iir.py`),
|
|
53
|
+
`beq_common.biquad` and ezbeq each implement the cookbook; check that they agree, including the shelf Q/S conversion,
|
|
54
|
+
to the precision a profile can resolve. If they do not, name the one a profile models.
|
|
55
|
+
* **Search method:** a coordinate or pattern search on the parameter grid, a scalar minimiser per parameter as now, or
|
|
56
|
+
something else; deterministic, bounded and cacheable as `optimise()` is.
|
|
57
|
+
* **Whether it ever helps:** benchmark against direct-coefficient search on the catalogue's float32/96 kHz worst cases
|
|
58
|
+
(e.g. the 5 Hz Q 6 peak the 2x4 HD pilot measured). Parameter search may recover little where the coefficient grid is
|
|
59
|
+
coarse; the outcome rules already report that honestly as `no_replacement`.
|
|
60
|
+
|
|
61
|
+
## What beqdesigner will build around it
|
|
62
|
+
|
|
63
|
+
* Device profiles: one per device it exports to (miniDSP HD-class: float32 at 96 kHz; earlier miniDSPs: Q5.23 fixed
|
|
64
|
+
point at 48 kHz; HTP-1 and JRiver compute in double precision, so nothing to show), plus profiles a user defines
|
|
65
|
+
(rate and storage). It uses `beqoptimiser`'s `Float32`/`FixedPoint` and `magnitude` for part (a).
|
|
66
|
+
* Part (a), now: the main chart shows the filter as each chosen profile realises it, and its error against the ideal.
|
|
67
|
+
* Part (b), after this ships: an *Optimise for devices* action on the filter, which calls `optimise_parameters` with the
|
|
68
|
+
ticked profiles, shows the before/after errors per profile and lets the designer accept the new parameters.
|
|
69
|
+
|
|
70
|
+
## Completion criteria
|
|
71
|
+
|
|
72
|
+
* `optimise_parameters` in `beqoptimiser` with tests covering: both rates, both precisions, the joint (minimax)
|
|
73
|
+
objective protecting each profile, the parameter grid and bounds, fixed non-searchable sections, and cache identity.
|
|
74
|
+
* A short benchmark against `optimise()` on the catalogue report's worst float32 cases, recorded here.
|
|
75
|
+
* Released in a `beqforge` version beqdesigner can pin.
|