beqforge 0.2.1__tar.gz → 0.2.3__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.1 → beqforge-0.2.3}/.github/workflows/build-device-check.yml +1 -1
- {beqforge-0.2.1 → beqforge-0.2.3}/.github/workflows/build-executable.yml +5 -3
- {beqforge-0.2.1 → beqforge-0.2.3}/.github/workflows/tests.yml +25 -5
- {beqforge-0.2.1 → beqforge-0.2.3}/AGENTS.md +3 -1
- {beqforge-0.2.1 → beqforge-0.2.3}/PKG-INFO +4 -3
- {beqforge-0.2.1 → beqforge-0.2.3}/README.md +3 -2
- {beqforge-0.2.1 → beqforge-0.2.3}/TODO.md +25 -23
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/__init__.py +1 -1
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/AGENTS.md +6 -2
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/diagnose.py +10 -4
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/pipeline.py +26 -3
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/done-design-and-pipeline.md +70 -0
- beqforge-0.2.3/plans/texture-yardstick-sub-band.md +215 -0
- beqforge-0.2.3/plans/texture-yardstick-sub-band.patch +154 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/pyproject.toml +18 -1
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_channel_evidence.py +3 -1
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_designer_server.py +81 -48
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_diagnose.py +30 -0
- beqforge-0.2.3/tests/test_design_ripple_band.py +56 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_designer_image.py +8 -11
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_designer_server_startup.py +17 -18
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/designer_server.py +268 -59
- {beqforge-0.2.1 → beqforge-0.2.3}/uv.lock +136 -76
- {beqforge-0.2.1 → beqforge-0.2.3}/.dockerignore +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.github/dependabot.yml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.github/workflows/build-designer-image.yml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.gitignore +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/.gitignore +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/beqforge.iml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/editor.xml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/misc.xml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/modules.xml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/.idea/vcs.xml +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/CLAUDE.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/LICENCE.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/AGENTS.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/biquad.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/provenance.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/publication.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beq_common/types.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/__init__.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/accept.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/beqd.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/biquad.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/charts.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/cli.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/design.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/designer.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/explain.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/extraction.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/filters.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/harness.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/identify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/material.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/record.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/reference.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/rolloff.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge/verify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge-device-check.spec +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge.spec +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/report.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/AGENTS.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/__init__.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/cli.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/core.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/device-check.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/docs/optimiser.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/evidence_validation.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/negative_corpus.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/packaging/designer/Dockerfile +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/packaging/designer/smoke.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/research-design-decisions.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/__init__.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/conftest.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_cli.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_accept.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_charts.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_corpus.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_design.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_designer.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_evidence.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_explain.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_extraction.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_filters.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_guard.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_harness.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_headroom.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_hum.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_identify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_parallel.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_pipeline.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_playback.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_publication.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_record.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_reference.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_references.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_strategy_config.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_timings.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_design_verify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_extract_layout.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_optimiser.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/__init__.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/design_beq.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/probe.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/extract.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/ledger_template.html +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/negative_corpus.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/render_ledger.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/replay.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/smoke_test_exe.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/smoke_test_install.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/summarise.py +0 -0
- {beqforge-0.2.1 → beqforge-0.2.3}/tools/validate_evidence.py +0 -0
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
steps:
|
|
26
26
|
- name: Find successful tests for the tagged commit
|
|
27
27
|
id: check
|
|
28
|
-
uses: actions/github-script@
|
|
28
|
+
uses: actions/github-script@v9
|
|
29
29
|
with:
|
|
30
30
|
script: |
|
|
31
31
|
core.setOutput('reusable', 'false');
|
|
@@ -72,6 +72,8 @@ jobs:
|
|
|
72
72
|
needs: previous_tests
|
|
73
73
|
if: needs.previous_tests.outputs.reusable != 'true'
|
|
74
74
|
uses: ./.github/workflows/tests.yml
|
|
75
|
+
# CODECOV_TOKEN, for the coverage upload
|
|
76
|
+
secrets: inherit
|
|
75
77
|
permissions:
|
|
76
78
|
contents: read
|
|
77
79
|
|
|
@@ -102,7 +104,7 @@ jobs:
|
|
|
102
104
|
contents: read
|
|
103
105
|
steps:
|
|
104
106
|
- uses: actions/checkout@v7
|
|
105
|
-
- uses: astral-sh/setup-uv@v10.
|
|
107
|
+
- uses: astral-sh/setup-uv@v10.3.0
|
|
106
108
|
with:
|
|
107
109
|
enable-cache: true
|
|
108
110
|
- name: Build wheel and source distribution
|
|
@@ -191,7 +193,7 @@ jobs:
|
|
|
191
193
|
- uses: actions/checkout@v7
|
|
192
194
|
|
|
193
195
|
- name: Install uv
|
|
194
|
-
uses: astral-sh/setup-uv@v10.
|
|
196
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
195
197
|
with:
|
|
196
198
|
enable-cache: true
|
|
197
199
|
|
|
@@ -67,7 +67,7 @@ jobs:
|
|
|
67
67
|
run: choco install ffmpeg -y
|
|
68
68
|
|
|
69
69
|
- name: Install uv
|
|
70
|
-
uses: astral-sh/setup-uv@v10.
|
|
70
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
71
71
|
with:
|
|
72
72
|
enable-cache: true
|
|
73
73
|
|
|
@@ -76,7 +76,17 @@ jobs:
|
|
|
76
76
|
|
|
77
77
|
# the device-check tests run in their own job below, in parallel, with the extra installed
|
|
78
78
|
- name: Run tests
|
|
79
|
-
run: uv run --extra designer pytest -n auto --durations=25 --ignore-glob='tests/test_device_check_*.py'
|
|
79
|
+
run: uv run --extra designer pytest -n auto --durations=25 --ignore-glob='tests/test_device_check_*.py' --cov --cov-report=xml
|
|
80
|
+
|
|
81
|
+
- name: Upload coverage
|
|
82
|
+
if: ${{ !cancelled() }}
|
|
83
|
+
uses: codecov/codecov-action@v7.1.1
|
|
84
|
+
with:
|
|
85
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
86
|
+
files: coverage.xml
|
|
87
|
+
disable_search: true
|
|
88
|
+
flags: designer,${{ runner.os }}
|
|
89
|
+
name: designer-${{ runner.os }}
|
|
80
90
|
|
|
81
91
|
device-check:
|
|
82
92
|
name: Device check (${{ matrix.os }})
|
|
@@ -90,7 +100,7 @@ jobs:
|
|
|
90
100
|
- uses: actions/checkout@v7
|
|
91
101
|
|
|
92
102
|
- name: Install uv
|
|
93
|
-
uses: astral-sh/setup-uv@v10.
|
|
103
|
+
uses: astral-sh/setup-uv@v10.3.0
|
|
94
104
|
with:
|
|
95
105
|
enable-cache: true
|
|
96
106
|
|
|
@@ -100,7 +110,17 @@ jobs:
|
|
|
100
110
|
|
|
101
111
|
- name: Run device-check tests
|
|
102
112
|
shell: bash
|
|
103
|
-
run: uv run --extra device-check pytest -n auto --durations=25 tests/test_device_check_*.py
|
|
113
|
+
run: uv run --extra device-check pytest -n auto --durations=25 tests/test_device_check_*.py --cov --cov-report=xml
|
|
114
|
+
|
|
115
|
+
- name: Upload coverage
|
|
116
|
+
if: ${{ !cancelled() }}
|
|
117
|
+
uses: codecov/codecov-action@v7.1.1
|
|
118
|
+
with:
|
|
119
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
120
|
+
files: coverage.xml
|
|
121
|
+
disable_search: true
|
|
122
|
+
flags: device-check,${{ runner.os }}
|
|
123
|
+
name: device-check-${{ runner.os }}
|
|
104
124
|
|
|
105
125
|
package-profiles:
|
|
106
126
|
name: Install profile (${{ matrix.profile }})
|
|
@@ -111,7 +131,7 @@ jobs:
|
|
|
111
131
|
profile: [base, designer, optimiser, device-check, all]
|
|
112
132
|
steps:
|
|
113
133
|
- uses: actions/checkout@v7
|
|
114
|
-
- uses: astral-sh/setup-uv@v10.
|
|
134
|
+
- uses: astral-sh/setup-uv@v10.3.0
|
|
115
135
|
with:
|
|
116
136
|
enable-cache: true
|
|
117
137
|
- name: Build the single distribution
|
|
@@ -56,7 +56,9 @@ importing them; use an isolated worktree for simultaneous experiments.
|
|
|
56
56
|
The root build configuration includes every workflow and the optimiser's bundled seed in one
|
|
57
57
|
wheel and source distribution. `.github/workflows/tests.yml` checks isolated dependency
|
|
58
58
|
profiles and runs the suite in parallel (`pytest -n auto`, so tests must not share mutable state
|
|
59
|
-
or fixed ports)
|
|
59
|
+
or fixed ports), with coverage (`--cov`, configured in `pyproject.toml`, `multiprocessing`
|
|
60
|
+
children included) uploaded to Codecov by each test job using the `CODECOV_TOKEN` secret;
|
|
61
|
+
release runs inherit it. Feature branch pushes test on Linux only and a newer push cancels the branch's
|
|
60
62
|
previous run; `main`, tags and manual runs test Linux, macOS and Windows and are never cancelled. `.github/workflows/build-executable.yml` builds release executables and distributions;
|
|
61
63
|
tag pushes publish to PyPI after validation and successful executable builds. Tags must match
|
|
62
64
|
the shared version exactly (`vX.Y.Z`). Manual runs build artifacts without publishing.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: beqforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -270,8 +270,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
270
270
|
|
|
271
271
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
272
272
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
273
|
-
`HEALTHCHECK` asks `/health
|
|
274
|
-
caller sending several at once queues them; size the
|
|
273
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in a separate
|
|
274
|
+
worker process, one at a time, so a caller sending several at once queues them; size the
|
|
275
|
+
caller's timeout for that. `docker stop` stops a design in progress.
|
|
275
276
|
|
|
276
277
|
Build and check it locally from the repository root:
|
|
277
278
|
|
|
@@ -244,8 +244,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
244
244
|
|
|
245
245
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
246
246
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
247
|
-
`HEALTHCHECK` asks `/health
|
|
248
|
-
caller sending several at once queues them; size the
|
|
247
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in a separate
|
|
248
|
+
worker process, one at a time, so a caller sending several at once queues them; size the
|
|
249
|
+
caller's timeout for that. `docker stop` stops a design in progress.
|
|
249
250
|
|
|
250
251
|
Build and check it locally from the repository root:
|
|
251
252
|
|
|
@@ -18,29 +18,31 @@ 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). |
|
|
44
46
|
|
|
45
47
|
## Review evidence
|
|
46
48
|
|
|
@@ -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.
|
|
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 one 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. One worker keeps designs serial (the contract is one synchronous POST per `design()` call); a worker that dies answers 500 and is replaced. 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` and `shared_root`. |
|
|
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
|
|
@@ -618,15 +618,21 @@ def diagnose(
|
|
|
618
618
|
constrain channel restoration and the band over which a filter is judged.
|
|
619
619
|
"""
|
|
620
620
|
params = params or DiagnoseParams()
|
|
621
|
-
|
|
622
|
-
band = (
|
|
623
|
-
freqs, mix_db =
|
|
621
|
+
spectrum_freqs, mix_spectrum = mean_spectrum(material.mono_mix, material.fs)
|
|
622
|
+
band = (spectrum_freqs >= params.band_hz[0]) & (spectrum_freqs <= params.band_hz[1])
|
|
623
|
+
freqs, mix_db = spectrum_freqs[band], mix_spectrum[band]
|
|
624
624
|
spectra = {
|
|
625
625
|
name: mean_spectrum(samples, material.fs)[1]
|
|
626
626
|
for name, samples in material.channels.items()
|
|
627
627
|
}
|
|
628
628
|
shares, share_errors = coherent_shares(material)
|
|
629
|
-
|
|
629
|
+
# The mix plateau is found on the whole spectrum, exactly as `pipeline` finds it, so tracking
|
|
630
|
+
# is measured from the plateau the run judges against. The reference grid ends exactly at the
|
|
631
|
+
# band top: cut off at the last bin below it, a plateau reaching that edge loses its top point
|
|
632
|
+
# and can vanish. Diagnosis then lost the plateau `pipeline` had, measured no tracking at all
|
|
633
|
+
# and reported the band top as where tracking stopped (A Few Good Men: 150.5-200 Hz found by
|
|
634
|
+
# one, none by the other).
|
|
635
|
+
_, mix_plateau = plateau_reference(mix_spectrum, spectrum_freqs, params)
|
|
630
636
|
from beqforge.extraction import ExtractionParams, extract
|
|
631
637
|
|
|
632
638
|
extraction_params = extraction_params or ExtractionParams()
|
|
@@ -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
|
|
@@ -748,6 +748,76 @@ 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
|
+
## The designer server answers `/health` during a design (17 October 2026)
|
|
752
|
+
|
|
753
|
+
A deployed image went unhealthy while it designed correctly. `serve-designer` was a plain
|
|
754
|
+
`HTTPServer` running `design()` on its only thread, so a `GET /health` waited behind a 50-110 s
|
|
755
|
+
design; the image's `HEALTHCHECK` gives up after 3 s and three failures mark the container
|
|
756
|
+
unhealthy. It had been single-threaded on purpose: the fitter forks its pool, and forking a
|
|
757
|
+
multi-threaded process risks a deadlock.
|
|
758
|
+
|
|
759
|
+
The server is now a `ThreadingHTTPServer`, and every design runs in one long-lived worker
|
|
760
|
+
process started with "spawn" (`_DesignWorker` in `tools/designer_server.py`). The worker's
|
|
761
|
+
only thread runs the design, so the fitter forks from a single-threaded process as before. Its
|
|
762
|
+
log records come back over a `SimpleQueue` (a `Queue` would start a feeder thread in it) and
|
|
763
|
+
go through the parent's loggers, and the parent waits for a request's records before
|
|
764
|
+
answering. One worker keeps designs serial. A worker that dies answers 500 and is replaced.
|
|
765
|
+
SIGTERM now shuts down as Ctrl-C does (as a container's PID 1 the server used to ignore it),
|
|
766
|
+
and closing stops the worker's process group, fit pool included, rather than wait for a
|
|
767
|
+
design in progress. The timing line gains `queued`. Nothing under `beqforge/` changed, so the
|
|
768
|
+
decision regression probe does not apply.
|
|
769
|
+
|
|
770
|
+
* **Checks.** New tests: `/health` answers within 3 s while a real design runs, and a killed
|
|
771
|
+
worker gives a 500 and then a working replacement. The existing server, image and startup
|
|
772
|
+
tests now build `DesignServer` with `DesignSettings` (51 passed). Against the installed
|
|
773
|
+
`beqforge serve-designer`: `/health` took at most 16 ms during a design, and SIGTERM in the
|
|
774
|
+
middle of one exited in 0.3 s with no worker or fit-pool process left. Before the
|
|
775
|
+
process-group fix, the fit pool was orphaned and kept computing. The image built and
|
|
776
|
+
`packaging/designer/smoke.py` passed, inline and by reference. Full designer suite with
|
|
777
|
+
coverage: 741 passed, 1 skipped. The fork-from-a-threaded-process warnings in it come from
|
|
778
|
+
xdist's own worker threads and appear without these changes.
|
|
779
|
+
* **Coverage.** CI now measures coverage (`pytest-cov`; `[tool.coverage.run]` in
|
|
780
|
+
`pyproject.toml`, with `concurrency = multiprocessing` so the design worker and fit pools
|
|
781
|
+
count on every platform; `patch = fork` is unsupported on Windows). Both test jobs upload
|
|
782
|
+
`coverage.xml` to Codecov with `CODECOV_TOKEN`, and the release workflow passes its secrets
|
|
783
|
+
to the tests it calls.
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
## The mix plateau diagnosis tracks from is the pipeline's (17 October 2026)
|
|
787
|
+
|
|
788
|
+
`diagnose` found the mix plateau on the spectrum cut off at the analysis band's top, while
|
|
789
|
+
`pipeline` finds it on the whole spectrum. `plateau_reference`'s grid ends exactly at the band
|
|
790
|
+
top with `right=nan`, so the cut-off copy always lost the top grid point, and a plateau reaching
|
|
791
|
+
that edge could vanish. Diagnosis then took `_temporal_evidence`'s early return (no strata, the
|
|
792
|
+
tracking floor set to the band top), and the pipeline's plateau blocker read that as "nothing
|
|
793
|
+
below it tracks the programme (tracking stops at 200.0 Hz)". The mix plateau is now found on
|
|
794
|
+
the whole spectrum, as the pipeline finds it. Channel plateaus are unchanged.
|
|
795
|
+
|
|
796
|
+
* **Found in the full-library run (beqdesigner).** A Few Good Men (5.1) declined
|
|
797
|
+
`no_usable_plateau`: the pipeline's plateau was 150.5-200 Hz, diagnosis had none, and its
|
|
798
|
+
record showed no strata and a tracking floor of exactly 200.0. `band_tracking` against the
|
|
799
|
+
pipeline's plateau passes every octave band to ~13 Hz (0.53-0.62). Patched, it accepts two
|
|
800
|
+
low shelves (16.8 Hz +5.8 dB, 26.8 Hz +6.5 dB, confidence 0.91). Before the Devil Knows
|
|
801
|
+
You're Dead and Blink Twice show the same record signature.
|
|
802
|
+
* **First version: every plateau on the whole spectrum, rejected.** Handing channels the whole
|
|
803
|
+
spectrum too moved channel plateaus on eight baseline titles (an extra finite top point moves
|
|
804
|
+
the 90th-percentile threshold), changed counterfactual targets on four, and in real runs
|
|
805
|
+
against HEAD flipped counterfactual verdicts on Alto Knights and Obsession. As with the
|
|
806
|
+
one-sided fallback above, the channel calls were left alone.
|
|
807
|
+
* **Validation of the mix-only version**, against 7a1702f. Probe: 28 Years Later's tracking
|
|
808
|
+
floor 20.50 -> 20.30 Hz (judged band and targets by up to 0.17 dB); Dossier 137's filter
|
|
809
|
+
floor 59.3 -> 79.8 Hz and Send Help's 20.0 -> 20.8 Hz, nothing else; the other eight titles
|
|
810
|
+
unchanged. 28 Years Later's real run against a HEAD run: the winner changes `parametric` ->
|
|
811
|
+
`flatten`, both passing either way; the accepted corrections agree within 0.6 dB in every band
|
|
812
|
+
from 5 to 80 Hz (+5.8/+6.0/+6.6/+3.3/+0.6 -> +5.9/+6.6/+6.4/+3.1/+0.7 dB), the same answer.
|
|
813
|
+
Protocol, seeds 101 and 947: identical false acceptances, abstentions, selections and recovery.
|
|
814
|
+
Corpus, seeds 1-9: gated 0/45, gate pass 0.079; positives 7/9 (median recovery 1.27 dB RMS);
|
|
815
|
+
`natural_droop` 4/9 against the committed file's 5/9 (an older revision; reported, not gated).
|
|
816
|
+
Full suite: 804 passed, 2 skipped. New test in `tests/test_design_diagnose.py`: a mix
|
|
817
|
+
high-passed at 144-146 Hz, whose only plateau reaches the band top, is tracked from that
|
|
818
|
+
plateau (fails on the old code: no strata, floor at 200 Hz).
|
|
819
|
+
|
|
820
|
+
|
|
751
821
|
## Levels beside an omission, and mains-hum notches as texture (11 October 2026)
|
|
752
822
|
|
|
753
823
|
Three titles from a beqdesigner run against `/media/beq` were declined in ways that should not
|