beqforge 0.3.0__tar.gz → 0.3.2__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.3.0 → beqforge-0.3.2}/.github/workflows/build-device-check.yml +1 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/.github/workflows/build-executable.yml +2 -2
- {beqforge-0.3.0 → beqforge-0.3.2}/.github/workflows/tests.yml +3 -3
- {beqforge-0.3.0 → beqforge-0.3.2}/PKG-INFO +1 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/TODO.md +5 -3
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/__init__.py +1 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/accept.py +46 -9
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/designer.py +4 -2
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/e10.py +92 -14
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/fit_kernel.py +20 -3
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/impact.py +1 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/impact.toml +28 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/pipeline.py +27 -4
- {beqforge-0.3.0 → beqforge-0.3.2}/negative_corpus.json +327 -207
- {beqforge-0.3.0 → beqforge-0.3.2}/packaging/designer/Dockerfile +3 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/packaging/designer/smoke.py +40 -9
- beqforge-0.3.2/plans/E10-validation-protocol.md +684 -0
- beqforge-0.3.2/plans/bass-ceiling.md +144 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/design-version.md +1 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/fit-kernel.md +10 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/fixtures/design_golden.json +24 -30
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_accept.py +25 -3
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_evidence.py +20 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_pipeline.py +12 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_playback.py +5 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_strategy_config.py +4 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_e10.py +79 -1
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_fit_kernel.py +20 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/e10_cases.py +2 -2
- {beqforge-0.3.0 → beqforge-0.3.2}/uv.lock +20 -20
- beqforge-0.3.0/plans/E10-validation-protocol.md +0 -327
- {beqforge-0.3.0 → beqforge-0.3.2}/.dockerignore +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.gitattributes +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.github/dependabot.yml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.github/workflows/build-designer-image.yml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.gitignore +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/.gitignore +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/beqforge.iml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/editor.xml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/misc.xml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/modules.xml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/.idea/vcs.xml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/AGENTS.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/CLAUDE.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/LICENCE.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/README.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/AGENTS.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/biquad.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/provenance.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/publication.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beq_common/types.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/AGENTS.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/__init__.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/adjustments.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/beqd.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/biquad.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/charts.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/cli.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/dc_offset.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/design.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/diagnose.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/explain.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/extraction.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/filters.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/harness.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/identify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/material.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/record.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/reference.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/rolloff.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge/verify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge-device-check.spec +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge.spec +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/report.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/AGENTS.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/__init__.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/cli.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/core.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/beqoptimiser/parameters.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/device-check.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/docs/optimiser.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/evidence_validation.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/packaging/designer/beqforge-designer.cmd +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/contract-1.5-adjustments.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/default-record-and-log-locations.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/done-design-and-pipeline.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/e10-titles-v1.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/fit-weighting.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/library-review-improvements.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/library-v5-review.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/parameter-space-optimisation.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/playback-contract.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/redesign-scope.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/research-design-decisions.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/texture-yardstick-sub-band.md +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/plans/texture-yardstick-sub-band.patch +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/pyproject.toml +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/__init__.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/conftest.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_cli.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_adjustments.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_channel_evidence.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_charts.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_corpus.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_dc_offset.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_design.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_designer.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_designer_server.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_diagnose.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_explain.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_extraction.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_filters.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_golden.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_guard.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_harness.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_headroom.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_hum.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_identify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_impact.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_parallel.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_publication.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_record.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_reference.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_references.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_ripple_band.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_target_fit.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_timings.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_design_verify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_designer_image.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_designer_server_startup.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_extract_layout.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_optimiser.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_optimiser_parameters.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/__init__.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/design_beq.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/designer_server.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/e10_run.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/parameter_benchmark.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/probe.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/extract.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/impact.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/ledger_template.html +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/negative_corpus.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/render_ledger.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/replay.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/smoke_test_exe.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/smoke_test_install.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/summarise.py +0 -0
- {beqforge-0.3.0 → beqforge-0.3.2}/tools/validate_evidence.py +0 -0
|
@@ -112,7 +112,7 @@ jobs:
|
|
|
112
112
|
contents: read
|
|
113
113
|
steps:
|
|
114
114
|
- uses: actions/checkout@v7
|
|
115
|
-
- uses: astral-sh/setup-uv@v10.3.
|
|
115
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
116
116
|
with:
|
|
117
117
|
enable-cache: true
|
|
118
118
|
- name: Build wheel and source distribution
|
|
@@ -201,7 +201,7 @@ jobs:
|
|
|
201
201
|
- uses: actions/checkout@v7
|
|
202
202
|
|
|
203
203
|
- name: Install uv
|
|
204
|
-
uses: astral-sh/setup-uv@v10.3.
|
|
204
|
+
uses: astral-sh/setup-uv@v10.3.2
|
|
205
205
|
with:
|
|
206
206
|
enable-cache: true
|
|
207
207
|
|
|
@@ -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.3.
|
|
70
|
+
uses: astral-sh/setup-uv@v10.3.2
|
|
71
71
|
with:
|
|
72
72
|
enable-cache: true
|
|
73
73
|
|
|
@@ -100,7 +100,7 @@ jobs:
|
|
|
100
100
|
- uses: actions/checkout@v7
|
|
101
101
|
|
|
102
102
|
- name: Install uv
|
|
103
|
-
uses: astral-sh/setup-uv@v10.3.
|
|
103
|
+
uses: astral-sh/setup-uv@v10.3.2
|
|
104
104
|
with:
|
|
105
105
|
enable-cache: true
|
|
106
106
|
|
|
@@ -131,7 +131,7 @@ jobs:
|
|
|
131
131
|
profile: [base, designer, optimiser, device-check, all]
|
|
132
132
|
steps:
|
|
133
133
|
- uses: actions/checkout@v7
|
|
134
|
-
- uses: astral-sh/setup-uv@v10.3.
|
|
134
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
135
135
|
with:
|
|
136
136
|
enable-cache: true
|
|
137
137
|
- name: Build the single distribution
|
|
@@ -28,7 +28,7 @@ ID alone (AGENTS.md says where each lives), and the stale contract versions are
|
|
|
28
28
|
| --- | --- | --- | --- |
|
|
29
29
|
| 1 | Playback contract | released in 0.2.10 (design version 6) | The sub feed is now built from the request's `bass_management` (one LR4 for "Before"/"After", none for "Off", WCS or numeric headroom gains); `clip_before`/`clip_after` are named in the clipping line, not simulated. Default path unchanged (probe tol 0). On the 11 titles no verdict or winner moves; the clipping figure does, by up to 1.8 dB (Black Bag's gain reduction 1.40 to 3.16 dB). Caller propagation was already done in beqdesigner. See [the outcome](plans/playback-contract.md#outcome). |
|
|
30
30
|
| 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). |
|
|
31
|
-
| 3 | E10 | instrument
|
|
31
|
+
| 3 | E10 | instrument v4; ready for its first gated change | The [protocol](plans/E10-validation-protocol.md) is predeclared and its instrument built: synthetic families and scorer (`beqforge/e10.py`, `tools/e10_run.py`), the real-texture half (`tools/e10_cases.py`), the title partition frozen. The [first development run](plans/E10-validation-protocol.md#first-development-run-22-october-2026) corrected the instrument to v2, and v3 [settled the tilted goal's pivot](plans/E10-validation-protocol.md#the-tilted-goals-pivot-v3-22-october-2026). [v4](plans/E10-validation-protocol.md#version-8-baseline-and-v4-28-october-2026) reran the development seeds at design version 8 and settled the rest: the cancellation recipe cancels in phase (its causal low-pass added +3-3.8 dB at 40-71 Hz); `finite_stopband/2`'s gentle overshoot is the designer's (0.25 of its 0.65 dB is pivot scatter); overshoot and missed recovery use a 1.5 dB accuracy bound at every goal, the goal tolerance being the pipeline's act threshold (tight scored 11 overshoots of ordinary fit ripple); and the decision rule gates on no safety count getting worse, with 5% a reported target. Under v4 at version 8, flat: noise lift 4/15, overshoot 5/36, missed 21/24, median shortfall 0.21 dB. v5 replaced the pumping floor (row 33). Row 33's candidate, bounding lift by the evidence, was [measured in three forms and not adopted](plans/E10-validation-protocol.md#bounding-lift-by-the-evidence-three-experiments-none-adopted-29-october-2026). Next: choose another first change for it to gate, then the held-out run. |
|
|
32
32
|
| 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. |
|
|
33
33
|
| 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. |
|
|
34
34
|
| 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. |
|
|
@@ -47,7 +47,7 @@ ID alone (AGENTS.md says where each lives), and the stale contract versions are
|
|
|
47
47
|
| 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. |
|
|
48
48
|
| 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. |
|
|
49
49
|
| 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. |
|
|
50
|
-
| 23 | Performance | fit kernel released in 0.3.0 (design version 8) | [The measurement and outcome](plans/fit-kernel.md#outcome). The fit's search now scores each DE generation at once in the linear domain (`beqforge/fit_kernel.py`; numba, numpy fallback, bit-identical). Real runs on the 11 titles: 703 to 283 s wall, 1905 to 373 CPU-s; the `fit` stage 10x, so a title is 28 s and 37 CPU-s. A re-roll: refit probe no worse on balance, no baseline title's winner or verdict changed in real runs, protocol and corpus unchanged where gated. **Left:** the fit re-rolls near the top of the band as freely as at 10 Hz (row 37: weighting the search works, held back behind row 36); measure a library sample for the impact basis and replace it in 0.3
|
|
50
|
+
| 23 | Performance | fit kernel released in 0.3.0 (design version 8); 0.3.1 fixes the designer image run as another user | [The measurement and outcome](plans/fit-kernel.md#outcome). The fit's search now scores each DE generation at once in the linear domain (`beqforge/fit_kernel.py`; numba, numpy fallback, bit-identical). Real runs on the 11 titles: 703 to 283 s wall, 1905 to 373 CPU-s; the `fit` stage 10x, so a title is 28 s and 37 CPU-s. A re-roll: refit probe no worse on balance, no baseline title's winner or verdict changed in real runs, protocol and corpus unchanged where gated. **Left:** the fit re-rolls near the top of the band as freely as at 10 Hz (row 37: weighting the search works, held back behind row 36); measure a library sample for the impact basis and replace it in a later 0.3 release (0.3.0's is mostly injected variants); CI's Windows and macOS executables with numba. Still parked: reuse of repeated numerical identities, the ~9 s a title outside the pipeline (measure on the server path), an own DE loop. GPU offload rejected. Missed-fit search belongs to F3/F4. |
|
|
51
51
|
| 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. |
|
|
52
52
|
| 26 | Texture yardstick retune (library review) | blocked on E10 | 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). The reopening trigger (the library review) is met and the first measurement is done ([research item 4](plans/library-review-improvements.md#4-the-texture-retune-row-26)): the trend degree does nothing; ending the band at 80 Hz clears five of the seven review declines but lets five more gated negatives past this rule and costs three filtered cases their passband. Its full corpus run lets in exactly `broadband/5` (a false acceptance at a 2.5 dB boost; gate 0/45 to 1/45, upper bound 0.118, FAIL) and moves nothing else, `filtered` included ([the run](plans/library-review-improvements.md#4-the-texture-retune-row-26)). So the open question is the boost-weighted gate, and E10 (row 3) before adopting any retune. 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). |
|
|
53
53
|
| 28 | Fit sensitivity to arithmetic | released in 0.2.10 (design version 6) | Identification now converges to 1e-12 and the fit gets its target and ceiling quantised to 0.01 dB. The refit probe gives the same filters natively and as an emulated older CPU (0 of 11 titles moved; 28 Years Later's parametric moved before). Protocol false acceptances unchanged; corpus gate pass, `filtered` 7/9 (was 6/9). The 11 titles' corrected curves stay within 1.3 dB, though three swap between near-tied winners ([outcome](plans/library-review-improvements.md#built-a-reproducible-fit-input-22-october-2026-design_version-6)). CI's verdict on the golden: `counterfactual` matches on Linux, Windows and macOS and is back in it; `parametric` matches on the x86 runners that disagreed in 0.2.6, but macOS arm64 publishes the same filters with figures 0.06 dB apart, so it stays out ([verdict](plans/library-review-improvements.md#built-a-reproducible-fit-input-22-october-2026-design_version-6)). Left: find where arm64's parametric fit parts from x86's (needs an arm64 machine: compare the identified target, the quantised fit input and the fitted cascade unrounded) and either close the gap or accept it and say so in the golden's docstring; until then an Apple-silicon caller can see parametric figures a few hundredths of a dB from an x86 one. From design version 8 (row 23's kernel) `counterfactual` on the golden's rolloff title also publishes one step away on arm64 (36.25 Hz against 36.24, figures within tolerance), so the golden skips that case on arm64 too. Ship before the version 5 redesign (row 31) so the library is redesigned once. |
|
|
@@ -55,11 +55,13 @@ ID alone (AGENTS.md says where each lives), and the stale contract versions are
|
|
|
55
55
|
| 30 | Contract 1.5: design adjustments | released in 0.2.9; one gap parked | beqdesigner can ask for a different design per title: ignore a band, a floor, a boost cap, a tilted goal, a note. `beqforge/adjustments.py` answers each; default answers unchanged (golden; probe `--tol 0`, all 11 titles). See [the outcome](plans/contract-1.5-adjustments.md#outcome). Only gap: a note's text is not written into the run record (the record holds the adjusted params, so the design replays). Reopen if a review needs the note from the record. |
|
|
56
56
|
| 31 | Review the version 5 library redesign | open: handoff received 23 October 2026; group 1 released in 0.2.10 (design version 6) | Outcome and evidence: [the v5 review](plans/library-v5-review.md). beqdesigner's v5 rerun (job `ef4265fa`, 1,083 titles, about 1,000 designed and 69 declined; 18 titles v4 designed now decline, about half wrongly) is triaged into four groups in `/media/beq/triage/handoff-v5.md` (queue ids, v5 records and earlier runs per title). 1 (built): O2 refused a plateau starting just under 10 Hz on the knee of a real rolloff (A Quiet Place: Day One, Elysium, Pan); it now keeps such a plateau when it reaches 20 Hz and the shortfall beneath exceeds the passband's ripple, and all three design. Probe, protocol and corpus unchanged. 2: a whole candidate rejected for missing one check by a fraction: cliff by under 3 dB/oct (Heartburn, Back to the Future, Guardians of the Galaxy, The Commitments), tilt (Guardians, The Exorcist, Alien Resurrection, Philadelphia), level (Ray), F9 residual just below the content edge (Drive, Amy). The library has no negatives, so a tolerance it suggests still goes through the protocol and E10, never the catalogue. 3: every candidate corrected only down to above the content edge (Return to Seoul, Spirited Away, The Jungle Book, Bugonia). 4: known, rows 5/6 (steep filter; `judge_from_content_edge` is a server flag, not on the wire), F5 (Eastern Promises, Fame, Home Alone) and row 26. Earlier scope of this row: 0.2.9 reports design version 5, so beqdesigner's out-of-date filter flags every title designed before it. Review the redesign's declines and changed designs from their run records, as the first review was (row 29): confirm the fixes' titles moved as measured, rerun the open cases (row 29's tilt titles, the near-texture watch cases Black Bag, Bugonia and Incredible Hulk), and collect real-title evidence for rows 26 and 29's research. It can show wrong declines but not false acceptances (the library has no known negatives), so a retune it suggests still goes through E10. Do row 28 first if both machines will design. Records are kept by default from 0.2.10 (row 27, done). |
|
|
57
57
|
| 32 | Fit optimum selection | part built (design version 7, not released); strategy choice still unstable | Found by row 28: reproducible is not stable. Changing the fit's input by at most 0.005 dB changed 24 winners on 54 titles and variants; Black Bag LR4 35's recovery fell from a 13.1 dB to a 3.4 dB peak. **Built (25 October 2026):** the fitter returns every fit within its 0.5 dB residual bar of its answer, and the pipeline keeps the first the judge passes, fewest sections first; an alternative must lift by more than the goal tolerance to replace the answer. Perturbing the fit input by 0.004 dB now flips 14 winners where HEAD flipped 19, 33 verdicts against 36, and no title swaps between designed and declined (HEAD 1). Black Bag LR4 35 goes to a 0.4 dB shortfall and holds; 13 of row 35's 14 new declines design again and none of its 17 gains is lost. Protocol unchanged, corpus gate passes. **Left:** most remaining flips are which strategy wins (flatten against a counterfactual on the steep variants, up to 38 dB apart below the content edge). The choice among strategies is untouched, and that is the next research step. See [the outcome](plans/library-review-improvements.md#row-32-choosing-among-near-equal-fits-25-october-2026-design_version-7) and [the finding](plans/library-review-improvements.md#built-a-reproducible-fit-input-22-october-2026-design_version-6). |
|
|
58
|
-
| 33 | Noise lift on a pumping floor | open | E10's first
|
|
58
|
+
| 33 | Noise lift beyond the evidence (found on a pumping floor) | open: the evidence bound was measured and not adopted; next, validate the floor detection | E10's first runs lifted `nonstationary_floor` 27-33 dB, but that recipe swung about 37 dB inside every event: event-locked content, matching real stopband leakage on contrast and coherence, so not a failure under the content principle ([v5](plans/E10-validation-protocol.md#the-pumping-floor-v5-28-october-2026) reports it as `event_locked_floor` and counts realistic 2-6 dB `tracking_floor` and `breathing_floor`). On seeds 1-12 under [v6](plans/E10-validation-protocol.md#the-skirt-and-the-cases-it-leaves-v6-28-october-2026) (noise lift counted beyond half an octave under the truth band), 4 of 72 realistic-floor rows lift noise. The two large ones are the judge passing the candidate that fits worst: on `breathing_floor/2` four three-section fits within 1.1-2.1 dB of target fail the cliff check, and a single shelf 6.4 dB off target and 5.5 dB over the evidence ceiling passes and is selected; on `breathing_floor/5` (gentle) a single section 9.0 dB over the ceiling at an unsupported bin is selected over a passing three-section fit 1.9 dB over. Nothing refuses a design for exceeding its evidence: the over-lift bar is measured from the goal, and `evidence_excess` is reported only. **Bounding lift by the evidence, not adopted (29 October 2026):** the fitter bound at the licence plus 1.5 dB (A), a judge check on any excess over 1.5 dB (B), and that check limited to excesses at least half an octave wide (C) were each built on branch `experiment-evidence-bound`, uncommitted. C cleared E10's counted noise lifts, but applied to the v8 library's stored `evidence_excess` it refuses the accepted design for 96 of 1,033 titles, and 14 of those would decline. Inspection showed that most of these are a smooth fit against a ragged licence on content that tracks the programme (Any Given Sunday, To Die For). The large lifts it catches below a diagnosed floor (Wait Until Dark, White Heat) sit inside a licence that allows 20-25 dB there, and C catches them only by an edge excess. **Lift below the diagnosed floor:** 247 of the 304 designed titles with measured bins under `noise_floor_hz` lift more than 3 dB there, and 151 more than 10 dB. Almost all are flatten, holding its lift flat, among them Moana (37 dB under 8.4 Hz) and White Heat (29 dB under 40 Hz). These are not counts of noise lift. `noise_floor_hz` is the mix tracking boundary, the first band whose envelope fails to track the passband, and failing that is not proof of noise. The contrast licence often disagrees with it (Wait Until Dark: 20 dB licensed below it). **On E10's truth** ([evidence](plans/E10-validation-protocol.md#is-the-tracking-boundary-a-noise-detector-29-october-2026)), the boundary sits 0.24-0.42 octave (median) below the true noise boundary on every floor family, up to 1.12 octave, and never above it: it calls the noise just above it content. It also misses half of `finite_stopband`'s exposed cases, and calls LFE-only content noise at 15-27 Hz on 3 of 12 `partial_channels`. **On the library**, an independent test calibrated on E10 (does the band's level scale with programme loudness, and is it steady) finds content under the boundary on 151 of 300 floored titles, among them Moana, Creed III and Juror #2, the largest lifts below it. Only 10 look like steady noise. The other 139, mostly older, neither scale nor stay steady, which these statistics cannot resolve. So the boundary is not fit to gate a rule, and its content-as-noise error also holds restorations flat that the content supports. Measured within 240 s stretches, only 27 of those 139 become steady noise; 85 stay bursty within minutes without scaling with the plateau, which is either content independent of the plateau or transient noise (splices, handling), and E10 has no transient noise to calibrate against. **Next:** listen to Wait Until Dark, Marnie and GoodFellas (bursty, not scaling) against Mary Poppins and The Exorcist (steady noise). If bursty and non-scaling proves to be noise, add a transient-noise family to E10. Then improve the floor detection itself, before deciding how a restoration should behave below a floor shown to be noise: hold flat, cap at the local target as the counterfactuals do, or return to 0 dB. [Evidence](plans/E10-validation-protocol.md#bounding-lift-by-the-evidence-three-experiments-none-adopted-29-october-2026). Related: rows 32, 36, E4/E9. |
|
|
59
59
|
| 34 | Redesign scope | built and released in 0.2.12 (contract 1.6's `POST /impact`); beqdesigner's step 2 live check passed, step 4 verification next | The pipeline is a catalogue of 37 named features (`beqforge/impact.py`), each owning its code and reading from a run record how a design relied on it. Each answer-changing design version declares in `beqforge/impact.toml` which features it changed and how much that moved; `tools/impact.py check` refuses changed code no feature owns and undeclared answer-reaching changes (regression step 5). `POST /impact` scores up to 500 records (15 s over the share), and records from before 0.2.11 are identified by the release that wrote them, so the current library can be scored. Version 7's fit-choice degree was measured on the library on one machine: 18 of 34 post-fit declines moved, 7 of 15 designs with a failing candidate, 2 of 15 with all passing, 0 of 8 analysis declines. Against the library: 34 likely, 1,015 possible, 236 unaffected, 70 unknown (pre-design-version builds). It doesn't meet the plan's bar for this change (likely holds 18 of the sample's 27 movers): a change that re-rolls the fit can't be narrowed by outcome class. The ranking still holds: the top 30% hold about two thirds of the expected movers. Records now carry each verdict's margins and each fit `_choose_fit` had, so a threshold change is scored exactly (`will_move` or `unaffected`), and `unknown` (`no_figure`) only for older records or a losing candidate that would start passing; this changes no answer (rejudge probe 11 of 11 at `--tol 0`, refit probe 0 of 54 moved). Responses carry each change's `basis` and `unknown_reason` (agreed with beqdesigner). `impact.MOVED`'s cascade line is now 20/f dB, capped at 2 dB at 10 Hz and below, not a flat 1 dB. Version 7's designed classes were recounted on beqdesigner's 105-title sample: 16 of 35 with a failing candidate, 22 of 60 all passing. **Left:** beqdesigner's step 2 live check and step 4 verification sample (105 titles), and the check's from-the-results direction. See [the outcome](plans/redesign-scope.md#outcome). |
|
|
60
60
|
| 35 | Review the version 6 library redesign | open: handoff received 24 October 2026; the 500 and record overwriting fixed in 0.2.11; the 14 new declines traced to row 28, 13 of them designing again on row 32 (version 7, unreleased) | beqdesigner's v6 rerun (job `e6a70f7e`, 1,287 titles): 1,016 designed and 270 declined. A Quiet Place: Day One, Elysium and Pan now design (row 31's O2 fix). 17 titles declined on v5 now design, and 14 designed on v5 now decline. Handoff: `/media/beq/triage/handoff-v6.md`; ledger `/media/beq/triage/ledger.jsonl`, stage `E`. **Fixed:** A Quiet Place Part II got a 500 every time, because `TargetFit.within_tolerance` came back as 1.0000000000000002. Zero-weight bins that miss the tolerance change how numpy pairs up a sum, so the in-tolerance sum could round above the full one. It is now met / (met + missed), which can't exceed 1; this code predates row 28. The figure is reporting only, so no design version: the title's filters are unchanged (low shelf 18.25 Hz +24.6 dB, peak 28.38 Hz −3.5 dB), and its response is now valid. Probe 11 of 11 unchanged at `--tol 0`; suite passes. **The 14 new declines (analysed 24 October 2026):** all 14 were rerun on one machine, at v0.2.9, at `f6dca94` (row 28 alone) and at HEAD, from requests identical to the library's (same digest). All 14 design on 0.2.9 and decline at `f6dca94`; HEAD agrees with `f6dca94`. So row 28 alone is the cause, and processor variance accounts for none of them. In every case the target is identical (0.000 dB apart) and the fit lands on a different cascade: row 28's 0.01 dB quantisation of the fit's input re-rolls the fitter's choice among near-equal optima (row 32). Except for TINA, the new fits are within 1 dB of the old by the fitter's own measure, worst-case error (Kung Fu Panda 4 0.4-0.9 dB worse, You've Got Mail 0.9 dB better), yet a judge check that depends on the error's shape flips: cliff (Platoon, The Manchurian Candidate, You've Got Mail, How to Marry a Millionaire), tilt (On Golden Pond, Christine, Twilight's counterfactual), ripple (Arrival, Kung Fu Panda 4), turnover (Magnolia, Zoolander), extent (Twilight's flatten) or slot (Rocky, Souleymane's Story). In TINA's case the fit itself is worse: 0.2.9's three-section fits drifted 2.1-3.0 dB on publication rounding, against a 3.0 limit, with their lowest sections about 4 quantisation steps from the device's DC limit. The new fits most likely drifted just past it, and the exposure rule fell back to one shelf fitting 4-6 dB off. No threshold should move on this evidence: the declines are as arbitrary as the 0.2.9 acceptances were, and the fix is row 32's, choosing among near-equal fits by what the judge checks rather than by which optimum the arithmetic lands on. **Slot check:** Rocky's and Souleymane's Story's new fits carry sections under 1 dB that the pruner keeps, because dropping them costs 1.02 and 1.12 dB of worst-case error against its 1 dB allowance. Their 0.2.9 two-section fits reached the same error without them. `designer-interface.md` §4 allows a rejection only for a reason internal to the evidence, which a redundant section is not, so the stated reason is wrong whatever the outcome. The outcome itself is doubtful too: both designs are 0.2-2.6 dB sections at 71-353 Hz, mostly above an 80 Hz crossover, not low-end restoration. The library's flip lists are not ground truth for row 34 by themselves (see [the proposal](plans/redesign-scope.md#validation-before-relying-on-it)): its v5 records were overwritten by v6's, because a record was named by its request's audio digest. **Fixed (0.2.11):** a record is now named by digests of the audio and of the rest of the request (coverage, bass management, adjustments, settings) and by the design version, and holds `design_version` and `design_settings` itself. A response's `run_record` (1.6, agreed in principle with beqdesigner) names it relative to the shared root. No answer changes. |
|
|
61
61
|
| 36 | Is a design worth publishing at all? | open | The judge checks that a design does what its target asks, never whether the target is worth asking for. Only the target stage refuses a correction within the goal tolerance (1.5 dB), so anything that reaches the judge by another route can pass as a trivial design: row 32's search over near-equal fits accepted a single 0.98 dB shelf on the band-exclusion test until `pipeline._lifts_beyond_goal` gated alternatives. Rocky's and Souleymane's Story's v6 designs (row 35: 0.2-2.6 dB sections at 71-353 Hz, mostly above the crossover) are the real-title form of it. Decide whether the judge should refuse a design whose low-end lift stays within the goal tolerance, and with what reason under `designer-interface.md` §4. A decision change: protocol, corpus and the library's small designs. |
|
|
62
62
|
| 37 | A frequency-dependent fit tolerance | variant C works; held back behind row 36, not in 0.3.0 | The fit scores the worst absolute dB error the same at every frequency, so near-equal fits are interchangeable to it wherever their error sits. Version 8's re-roll (row 23) moved 28 Years Later by 1.13 dB at 33 Hz and Obsession by 1.09 dB at 29 Hz. `impact.MOVED` now uses 20/f dB, capped at 2 dB at 10 Hz. Weighting the fit's bars as well as its search failed: the fitter either spent sections on fractions of a dB above 50 Hz (A), or the near-equal window, 2 dB wide at 10 Hz, let single shelves up to 3.6 dB off win on section count (B). **C**, weighting only the search (f/40 Hz, 0.25 to 1), works: over the 31 winners, median worst error at 30-40 Hz falls from 1.11 to 0.63 dB and at 5-10 Hz rises from 1.05 to 1.65. No real winner changes, protocol and corpus gate are unchanged, and corpus recovery goes from 1.37 to 0.85 dB RMS. It is held back because the band-exclusion test now designs a 1 dB nothing where version 8 declined, which is row 36's hole. **Next:** C together with a row 36 rule (refuse the fitter's answer, not only its alternatives, when its lift stays within the goal tolerance), validated as one decision change. Code on branch `experiment-fit-weighting-search`. See [the evidence](plans/fit-weighting.md). |
|
|
63
|
+
| 38 | Review the version 8 library redesign | open: handoff received 28 October 2026; nothing new to build, evidence for rows 32 and 36 | beqdesigner redesigned 1,051 titles on 0.3.1 (design version 8). Handoff: `/media/beq/triage/handoff-v8.md`, per-title results in `/media/beq/triage/dv8-redesign-moved.json`. Every decline now has a ledger row against the build that made it (`ledger.jsonl` stage `F` for the 44 at v7 and v8; the 208 still at v6 are stage `E`). The handoff reports no new class of finding: all seven items of beqdesigner's library-run review (its L4) are already in row 29 and, where still open, rows 31 and 35. **Row 32:** one new designed-to-declined flip, 22 Jump Street (`-1428523`), designed on v5-v7. On v8 flatten fails only the cliff check (40.7 dB/oct at 12.9 Hz against the input's 35.5), the same shape as Platoon's v6 flip; records `designer-37ed4387a860f027-7806f696-v7.run.json.gz` and `-v8`. 19 titles declined on v6 now design, including 12 of row 35's 14 flips. **Row 36:** four titles reviewed as genuine declines now design, one design each: The Adventures of Priscilla, Queen of the Desert; The Book Thief; The Manchurian Candidate; and Bugonia (declined before on coverage below 8.1 Hz). Check whether each is worth publishing. The other 43 of the 44 declines are unchanged in reason and failing checks from the stage E review. |
|
|
64
|
+
| 39 | A bass ceiling | released in 0.3.2 (design version 9) | Bass EQ restores the low end a mix's filtering removed, and that ends at or below about 60 Hz. The designer has no such limit: the judged band's top follows the deficit's first settled end, at least an octave above the tracking floor, so on a mix whose floor sits high it reaches the midbass and `flatten` fills everything under it. Wait Until Dark was judged to 192 Hz, flat; its cascade lifted 110 Hz by 7.4 dB and 250 Hz by 2 dB, and passed, because the over-lift bar's slack (half the input's 31.5 dB roughness) allowed 15.7 dB. Measured by where each cascade's lift last reaches 3 dB: 12.4% of the version 8 library's accepted designs pass 60 Hz, 7.2% pass 80 Hz and 45 pass 100 Hz, against 0.7%, 0.1% and 2 of the 15,586 catalogue entries. This checks the premise; it is not a calibration. Most of the catalogue's exceptions above 80 Hz are one author's flatten-like designs that review judged wrong. The library's age mix explains about two thirds of the gap: lifting past 60 Hz is 49-63% of pre-1980 designs against 2.5-5% from 2000. Reweighted to the catalogue's age mix it is still 4.5% against 0.7% at 60 Hz, and 1.7% against 0.1% at 80 Hz. Candidate: `AcceptParams.bass_ceiling_hz` (60). It caps the deficit's end in the judged top, but not the one-octave span, which a slope needs. Every priced target is tapered to nothing by the ceiling. Above the ceiling, the over-lift bar bounds the cascade's own lift to the level tolerance, with no roughness slack, and the fitter is given that bound. The mix-texture blocker keeps its uncapped band. Measured: probe moves no verdict or winner; protocol moves no false acceptance; corpus 0/45 gated, natural droop 5/9 to 4/9, filtered 7/9 to 6/9 (the loss had 15.6 dB rms recovery). On 80 paired library titles, 57 of 60 lifters still accept and none lifts 3 dB past 58.7 Hz; 3 decline (The Manchurian Candidate, Solaris, The Red Shoes, all flatten filling a plateau above 60 Hz); 20 of 20 controls unchanged. The lift below 60 Hz is untouched, so Wait Until Dark's rumble is still row 33's. Of 22 sampled library declines, 3 now design, all fit re-rolls below the ceiling (row 32). Adopted: `impact.toml` declares it, the golden answers, corpus and baseline records are refreshed. Left: row 33, for the lift that remains below 60 Hz. Evidence and outcome: [the experiment](plans/bass-ceiling.md). |
|
|
63
65
|
|
|
64
66
|
## Review evidence
|
|
65
67
|
|
|
@@ -147,6 +147,23 @@ class AcceptParams:
|
|
|
147
147
|
constrained by this clause, so how wide their bands are says nothing about where it should
|
|
148
148
|
sit; a value chosen that way rejected a good filter whose band was perfectly measurable."""
|
|
149
149
|
|
|
150
|
+
bass_ceiling_hz: float | None = 60.0
|
|
151
|
+
"""Where bass EQ stops: no target asks for lift above it and no cascade may add more than
|
|
152
|
+
`level_tolerance_db` beyond it. `None` turns it off (TODO 39).
|
|
153
|
+
|
|
154
|
+
A BEQ restores the low end a mix's filtering removed, and that low end ends at or below
|
|
155
|
+
about 60 Hz. The judged band's top used to follow the deficit's first settled end, at
|
|
156
|
+
least `min_judge_octaves` above the tracking floor, so on a mix whose floor sits high it
|
|
157
|
+
reached the midbass, and `flatten` then filled everything under it: Wait Until Dark's band
|
|
158
|
+
ran to 192 Hz and its cascade lifted 110 Hz by 7.4 dB. 12.4% of the version 8 library's
|
|
159
|
+
accepted designs lifted 3 dB or more above 60 Hz, against 0.7% of the published catalogue
|
|
160
|
+
(a check of the premise, not a calibration).
|
|
161
|
+
|
|
162
|
+
The ceiling caps the deficit's end, not the band's span: a floor high enough that less
|
|
163
|
+
than `min_judge_octaves` lies under the ceiling still gets its octave to read a slope over
|
|
164
|
+
(above the ceiling, where nothing is asked for), because a slope read over less is
|
|
165
|
+
arbitrary."""
|
|
166
|
+
|
|
150
167
|
turnover_min_octaves: float = 0.5
|
|
151
168
|
"""How far inside the band the peak must sit before a turnover is measured.
|
|
152
169
|
|
|
@@ -414,19 +431,32 @@ def overlift_bar_db(
|
|
|
414
431
|
band only, never on a filter, so the fitter scores against the same bar before anything is
|
|
415
432
|
fitted (`overlift_allowance` in `pipeline`).
|
|
416
433
|
"""
|
|
417
|
-
|
|
434
|
+
in_band = (correction.freqs >= DESIGN_GRID[0]) & (
|
|
418
435
|
correction.freqs <= correction.band_hz[1]
|
|
419
436
|
)
|
|
420
437
|
slack = spectral_roughness(correction, params.roughness_degree) / 2.0
|
|
421
438
|
bar = (
|
|
422
439
|
np.maximum(
|
|
423
|
-
correction.requested_db(params.target_tilt_db_per_octave)
|
|
424
|
-
correction.before_db
|
|
440
|
+
correction.requested_db(params.target_tilt_db_per_octave),
|
|
441
|
+
correction.before_db,
|
|
425
442
|
)
|
|
426
443
|
+ params.level_tolerance_db
|
|
427
444
|
+ slack
|
|
428
445
|
)
|
|
429
|
-
|
|
446
|
+
guard = in_band
|
|
447
|
+
if params.bass_ceiling_hz is not None:
|
|
448
|
+
# Above the bass ceiling nothing is asked for, so the cascade's own lift is what is
|
|
449
|
+
# bounded: no more than the level tolerance over the material, whatever its roughness
|
|
450
|
+
# (TODO 39).
|
|
451
|
+
above = (correction.freqs > params.bass_ceiling_hz) & (
|
|
452
|
+
correction.freqs <= DESIGN_GRID[-1]
|
|
453
|
+
)
|
|
454
|
+
ceiling_bar = correction.before_db + params.level_tolerance_db
|
|
455
|
+
bar = np.where(
|
|
456
|
+
above, np.where(in_band, np.minimum(bar, ceiling_bar), ceiling_bar), bar
|
|
457
|
+
)
|
|
458
|
+
guard = in_band | above
|
|
459
|
+
return guard, bar[guard]
|
|
430
460
|
|
|
431
461
|
|
|
432
462
|
def spectral_roughness(correction: Correction, degree: int = 3) -> float:
|
|
@@ -633,11 +663,18 @@ def assess(
|
|
|
633
663
|
figures["overlift"] = float(overshoot[worst])
|
|
634
664
|
if _at(float(overshoot[worst]), quantum) > 0.0:
|
|
635
665
|
at_hz = float(correction.freqs[guard][worst])
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
666
|
+
if params.bass_ceiling_hz is not None and at_hz > params.bass_ceiling_hz:
|
|
667
|
+
failures.append(
|
|
668
|
+
f"lifts {at_hz:.1f} Hz by "
|
|
669
|
+
f"{float(overshoot[worst]) + params.level_tolerance_db:.1f} dB, above the "
|
|
670
|
+
f"{params.bass_ceiling_hz:g} Hz bass ceiling where bass EQ stops"
|
|
671
|
+
)
|
|
672
|
+
else:
|
|
673
|
+
failures.append(
|
|
674
|
+
f"lifts {at_hz:.1f} Hz to {correction.after_db[guard][worst]:+.1f} dB, "
|
|
675
|
+
f"{float(overshoot[worst]):.1f} dB above anything the material or the "
|
|
676
|
+
"requested shape supports there — boosting what is not content"
|
|
677
|
+
)
|
|
641
678
|
|
|
642
679
|
roughness = spectral_roughness(correction, params.roughness_degree)
|
|
643
680
|
wobble = corrected_wobble(correction, params.roughness_degree)
|
|
@@ -77,7 +77,7 @@ logger = logging.getLogger(__name__)
|
|
|
77
77
|
CONTRACT_VERSION = "1.6"
|
|
78
78
|
"""The version this module implements. Responses echo the request's own version instead."""
|
|
79
79
|
|
|
80
|
-
DESIGN_VERSION =
|
|
80
|
+
DESIGN_VERSION = 9
|
|
81
81
|
"""Which answer this code gives (1.4): bumped by any change that can alter `candidates`,
|
|
82
82
|
`rejected`, a decline or a 1.3 fit figure for an unchanged request and unchanged settings.
|
|
83
83
|
|
|
@@ -103,7 +103,9 @@ tolerance in [0, 1] (a last-digit difference, not an answer change) and only ren
|
|
|
103
103
|
the first the judge passes, fewest sections first, instead of whichever the arithmetic landed on
|
|
104
104
|
(TODO 32). 0.3.0 is 8: the fit's search scores a whole generation at once, in the linear
|
|
105
105
|
domain (TODO 23), which re-rolls the fit among near-equal optima as any change to its
|
|
106
|
-
arithmetic does.
|
|
106
|
+
arithmetic does. 0.3.2 is 9: it stops bass EQ at a 60 Hz ceiling: no target asks for lift
|
|
107
|
+
above it, the judged band's top is capped there, and a cascade may add no more than the level
|
|
108
|
+
tolerance beyond it (TODO 39). `plans/design-version.md` has each one's build."""
|
|
107
109
|
|
|
108
110
|
Coverage = Literal["complete_programme", "excerpt"]
|
|
109
111
|
ChannelScope = Literal["all_channels", "lfe_only", "mixed"]
|
|
@@ -23,9 +23,13 @@ from scipy.stats import beta
|
|
|
23
23
|
from beqforge import Alignment, BiquadSpec, HighPass
|
|
24
24
|
from beqforge.harness import SyntheticProfile, apply_high_pass, synthesise
|
|
25
25
|
|
|
26
|
-
PROTOCOL_VERSION = "e10-
|
|
26
|
+
PROTOCOL_VERSION = "e10-v6"
|
|
27
27
|
"""v1 read the goal deficit from the observed spectrum alone; v2 pivoted a tilted goal at the
|
|
28
|
-
reference's bottom, not where the deficit begins
|
|
28
|
+
reference's bottom, not where the deficit begins (see `truth`); v3 scored a correction's
|
|
29
|
+
accuracy against the goal tolerance (see `ACCURACY_DB`) and cancelled through a causal low-pass
|
|
30
|
+
(see `e10_case`); v4's `nonstationary_floor` swung about 37 dB with every event, which is
|
|
31
|
+
event-locked content, not a pumping floor (see `REPORTED`); v5 counted a restoration's skirt
|
|
32
|
+
under the truth band as noise lift (see `TRANSITION_OCTAVES`)."""
|
|
29
33
|
|
|
30
34
|
GOALS: dict[str, tuple[float, float]] = {
|
|
31
35
|
"flat": (0.0, 1.5),
|
|
@@ -34,6 +38,15 @@ GOALS: dict[str, tuple[float, float]] = {
|
|
|
34
38
|
}
|
|
35
39
|
"""Goal name: (`target_tilt_db_per_octave`, `goal_tolerance_db`), predeclared."""
|
|
36
40
|
|
|
41
|
+
ACCURACY_DB = 1.5
|
|
42
|
+
"""How far a correction may land from the goal, at every goal: overshoot and missed recovery.
|
|
43
|
+
|
|
44
|
+
The goal tolerance is the pipeline's act threshold (`AcceptParams.goal_tolerance_db`: how far
|
|
45
|
+
from the goal a low end may sit and still need nothing), not a bound on a correction's
|
|
46
|
+
accuracy, so it decides only whether a case needs a correction. v3 scored overshoot and missed
|
|
47
|
+
recovery against it too, so the tight goal, whose designs are the flat goal's filter for
|
|
48
|
+
filter, counted the fit's ordinary 0.3-1 dB ripple as overshoot (11 of 36 on seeds 1-3)."""
|
|
49
|
+
|
|
37
50
|
DEVELOPMENT_SEEDS = range(1, 1000)
|
|
38
51
|
HELD_OUT_SEEDS = range(100001, 101000)
|
|
39
52
|
HELD_OUT_PER_FAMILY = 36
|
|
@@ -45,7 +58,7 @@ FAMILIES = (
|
|
|
45
58
|
"sparse_dialogue",
|
|
46
59
|
"stationary_noise",
|
|
47
60
|
"coloured_floor",
|
|
48
|
-
"
|
|
61
|
+
"tracking_floor",
|
|
49
62
|
"correlated_noise",
|
|
50
63
|
"finite_stopband",
|
|
51
64
|
"partial_channels",
|
|
@@ -53,7 +66,25 @@ FAMILIES = (
|
|
|
53
66
|
"natural_droop",
|
|
54
67
|
"continuous_bass",
|
|
55
68
|
"filtered",
|
|
69
|
+
"breathing_floor",
|
|
70
|
+
"event_locked_floor",
|
|
56
71
|
)
|
|
72
|
+
"""Appended, never reordered: a family's index is part of its seed's draws."""
|
|
73
|
+
|
|
74
|
+
REPORTED = ("event_locked_floor",)
|
|
75
|
+
"""Families reported beside the counts but kept out of every pooled tally.
|
|
76
|
+
|
|
77
|
+
`event_locked_floor` is v4's `nonstationary_floor`: noise scaled by `0.25 + level / mean`, so it
|
|
78
|
+
swings about 37 dB, rising only inside programme events. Below the corner it shows the contrast
|
|
79
|
+
real stopband leakage does (32-36 dB against `finite_stopband`'s 30-32) and the same partial
|
|
80
|
+
coherence (about zero), so under the content principle ("content correlated with the rest of
|
|
81
|
+
the soundtrack is not noise") it is event-locked content, and no rule could refuse it without
|
|
82
|
+
refusing that leakage too. Like the frozen protocol's `natural_bass_light`, it is reported, not
|
|
83
|
+
counted. `tracking_floor` and `breathing_floor` are the pumping floors a mix can carry."""
|
|
84
|
+
|
|
85
|
+
PUMPING_DB = (2.0, 6.0)
|
|
86
|
+
"""How far a pumping floor moves with the programme, drawn per case: a compressor's or a codec's
|
|
87
|
+
few dB, not an event's dynamic range."""
|
|
57
88
|
|
|
58
89
|
FS = 1000
|
|
59
90
|
DURATION_S = 240.0
|
|
@@ -68,6 +99,16 @@ FLOOR_HZ = 5.0
|
|
|
68
99
|
"""The lowest frequency scored: the pipeline's verification floor."""
|
|
69
100
|
|
|
70
101
|
NOISE_LIFT_DB = 3.0
|
|
102
|
+
|
|
103
|
+
TRANSITION_OCTAVES = 0.5
|
|
104
|
+
"""How far under the truth band's bottom noise lift starts to count.
|
|
105
|
+
|
|
106
|
+
A realisable filter that restores the bottom of the truth band cannot stop dead at its edge:
|
|
107
|
+
v5 counted that skirt as noise lift on cases whose design followed its evidence-priced target
|
|
108
|
+
within about 1 dB (`tracking_floor/1`, `breathing_floor/3`; plans/E10-validation-protocol.md,
|
|
109
|
+
"The pumping floor"). Half an octave is the allowance the real-texture half's `noise_replaced`
|
|
110
|
+
already makes under its corner. A case with no truth band has no restoration to skirt, so every
|
|
111
|
+
noise-only bin counts."""
|
|
71
112
|
MIN_SPAN_OCTAVES = 1.0 / 3.0
|
|
72
113
|
SMOOTHING_OCTAVES = 1.0 / 6.0
|
|
73
114
|
|
|
@@ -145,6 +186,25 @@ def _shaped_noise(
|
|
|
145
186
|
return noise / (np.std(noise) or 1.0)
|
|
146
187
|
|
|
147
188
|
|
|
189
|
+
def _pumping_db(samples: np.ndarray, depth_db: float, family: str) -> np.ndarray:
|
|
190
|
+
"""A floor's gain over time as the programme's 1 s level moves, at most `depth_db`.
|
|
191
|
+
|
|
192
|
+
Tracking: the floor rises with the programme above its median level, as a codec's noise
|
|
193
|
+
follows the signal. Breathing: a compressor's gain reduction pulls the floor down under loud
|
|
194
|
+
passages and releases it over about a second."""
|
|
195
|
+
level = np.convolve(np.abs(samples), np.ones(FS) / FS)[: samples.size]
|
|
196
|
+
above = np.clip(
|
|
197
|
+
20.0 * np.log10(np.maximum(level, 1e-30) / (np.median(level) or 1.0)),
|
|
198
|
+
0.0,
|
|
199
|
+
depth_db,
|
|
200
|
+
)
|
|
201
|
+
if family == "tracking_floor":
|
|
202
|
+
return above
|
|
203
|
+
release = math.exp(-1.0 / FS)
|
|
204
|
+
held = signal.lfilter([1.0 - release], [1.0, -release], above)
|
|
205
|
+
return -np.maximum(held, above)
|
|
206
|
+
|
|
207
|
+
|
|
148
208
|
def e10_case(family: str, seed: int, duration_s: float = DURATION_S) -> E10Case:
|
|
149
209
|
"""One reproducible case: its family, and every parameter drawn from its seed."""
|
|
150
210
|
if family not in FAMILIES:
|
|
@@ -191,20 +251,29 @@ def e10_case(family: str, seed: int, duration_s: float = DURATION_S) -> E10Case:
|
|
|
191
251
|
colour = str(rng.choice(("pink", "brown", "codec")))
|
|
192
252
|
noise = {name: _shaped_noise(rng, n, colour) * 0.01 for name in ("L", "LFE")}
|
|
193
253
|
change = f"{colour} noise only"
|
|
194
|
-
elif family in (
|
|
254
|
+
elif family in (
|
|
255
|
+
"coloured_floor",
|
|
256
|
+
"tracking_floor",
|
|
257
|
+
"breathing_floor",
|
|
258
|
+
"event_locked_floor",
|
|
259
|
+
):
|
|
195
260
|
hp = _high_pass(rng, (8, 12, 16))
|
|
196
261
|
observed = {name: apply_high_pass(s, hp, FS) for name, s in recorded.items()}
|
|
197
262
|
colour = str(rng.choice(("pink", "brown", "codec")))
|
|
198
263
|
down = float(rng.uniform(30.0, 60.0))
|
|
264
|
+
depth = float(rng.uniform(*PUMPING_DB))
|
|
199
265
|
noise = {}
|
|
200
266
|
for name, samples in recorded.items():
|
|
201
267
|
floor = _shaped_noise(rng, n, colour) * below(samples, down)
|
|
202
|
-
if family == "
|
|
203
|
-
# pumping: the floor rises with the programme, as a compressor's would
|
|
268
|
+
if family == "event_locked_floor":
|
|
204
269
|
level = np.convolve(np.abs(samples), np.ones(FS) / FS, mode="same")
|
|
205
270
|
floor *= 0.25 + level / (np.mean(level) or 1.0)
|
|
271
|
+
elif family != "coloured_floor":
|
|
272
|
+
floor *= 10.0 ** (_pumping_db(samples, depth, family) / 20.0)
|
|
206
273
|
noise[name] = floor
|
|
207
274
|
change = f"{hp}; {colour} floor {down:.0f} dB down"
|
|
275
|
+
if family in ("tracking_floor", "breathing_floor"):
|
|
276
|
+
change += f", {family.split('_')[0]} {depth:.1f} dB"
|
|
208
277
|
elif family == "correlated_noise":
|
|
209
278
|
hp = _high_pass(rng, (8, 12))
|
|
210
279
|
recorded = {name: apply_high_pass(s, hp, FS) for name, s in recorded.items()}
|
|
@@ -230,7 +299,11 @@ def e10_case(family: str, seed: int, duration_s: float = DURATION_S) -> E10Case:
|
|
|
230
299
|
change = f"{hp} on {touched} only"
|
|
231
300
|
elif family == "cancellation":
|
|
232
301
|
depth = float(rng.uniform(0.3, 0.8))
|
|
233
|
-
low
|
|
302
|
+
# zero phase, so the low band cancels in phase and the mix's change is 1 - d|H|^2, a
|
|
303
|
+
# plain deficit. v3's causal low-pass turned through -180 degrees near its corner, so
|
|
304
|
+
# the inverted copy added there: +3 to +3.8 dB at 40-71 Hz, which the designer then
|
|
305
|
+
# levelled the band below up to
|
|
306
|
+
low = signal.sosfiltfilt(
|
|
234
307
|
signal.butter(4, 60.0, fs=FS, output="sos"), recorded["LFE"]
|
|
235
308
|
)
|
|
236
309
|
from beqforge.material import LFE_GAIN, MAIN_GAIN
|
|
@@ -494,9 +567,15 @@ def score_selection(
|
|
|
494
567
|
if filters is None
|
|
495
568
|
else published_response_db(filters, params.realisation, known.freqs)
|
|
496
569
|
)
|
|
497
|
-
|
|
498
|
-
|
|
570
|
+
counted = known.noise_only
|
|
571
|
+
if known.truth_band.any():
|
|
572
|
+
edge = float(known.freqs[known.truth_band].min())
|
|
573
|
+
counted = counted & (known.freqs <= edge * 2.0**-TRANSITION_OCTAVES)
|
|
574
|
+
lifted = response[counted]
|
|
575
|
+
skirt = response[known.noise_only]
|
|
576
|
+
row["noise_exposed"] = bool(counted.any())
|
|
499
577
|
row["noise_lift_db"] = round(float(lifted.max()), 2) if lifted.size else 0.0
|
|
578
|
+
row["noise_lift_skirt_db"] = round(float(skirt.max()), 2) if skirt.size else 0.0
|
|
500
579
|
row["noise_lift"] = bool(label is not None and row["noise_lift_db"] > NOISE_LIFT_DB)
|
|
501
580
|
row["unnecessary"] = bool(label is not None and not known.needs_correction)
|
|
502
581
|
corrected = known.programme_db + response
|
|
@@ -507,13 +586,12 @@ def score_selection(
|
|
|
507
586
|
# there. Either alone mistakes the source's chance shape for an overshoot: filling a
|
|
508
587
|
# chance dip in the programme rises over the first, restoring a chance peak the recipe
|
|
509
588
|
# cut rises over the second
|
|
510
|
-
|
|
511
|
-
left = corrected > np.maximum(known.goal_db, known.programme_db) + tolerance
|
|
589
|
+
left = corrected > np.maximum(known.goal_db, known.programme_db) + ACCURACY_DB
|
|
512
590
|
seen = known.observed_db + response
|
|
513
|
-
there = seen > np.maximum(known.goal_db, known.observed_db) +
|
|
591
|
+
there = seen > np.maximum(known.goal_db, known.observed_db) + ACCURACY_DB
|
|
514
592
|
over = band & left & there
|
|
515
593
|
gap = np.maximum(known.goal_db - corrected, 0.0)[band]
|
|
516
|
-
short = band & (corrected < known.goal_db -
|
|
594
|
+
short = band & (corrected < known.goal_db - ACCURACY_DB)
|
|
517
595
|
row["overshoot"] = bool(label is not None and over.any())
|
|
518
596
|
row["shortfall_median_db"] = round(float(np.median(gap)), 2)
|
|
519
597
|
row["shortfall_worst_db"] = round(float(gap.max()), 2)
|
|
@@ -602,7 +680,7 @@ def summarise(rows: list[dict]) -> dict:
|
|
|
602
680
|
for goal in sorted({r["goal"] for r in rows}):
|
|
603
681
|
at_goal = [r for r in rows if r["goal"] == goal]
|
|
604
682
|
summary[goal] = {
|
|
605
|
-
"pooled": tally(at_goal),
|
|
683
|
+
"pooled": tally([r for r in at_goal if r["family"] not in REPORTED]),
|
|
606
684
|
"families": {
|
|
607
685
|
family: tally([r for r in at_goal if r["family"] == family])
|
|
608
686
|
for family in sorted({r["family"] for r in at_goal})
|
|
@@ -221,12 +221,29 @@ def _power_numpy(coefficients, c1, s1, c2, s2):
|
|
|
221
221
|
return num / den
|
|
222
222
|
|
|
223
223
|
|
|
224
|
+
def _compiled(function):
|
|
225
|
+
"""`numba.njit`, with its on-disk cache when there is somewhere to write one.
|
|
226
|
+
|
|
227
|
+
numba caches beside the source, or failing that in the user's cache directory, and refuses
|
|
228
|
+
to compile at all when it can write to neither: an install the running user can't write to,
|
|
229
|
+
with no home of its own, such as the designer image run as another user. The cache only saves
|
|
230
|
+
the compile (~0.7 s a process), so without one the kernel is compiled afresh, to the same
|
|
231
|
+
answer.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
return numba.njit(cache=True)(function)
|
|
235
|
+
except RuntimeError as e:
|
|
236
|
+
if "cannot cache" not in str(e):
|
|
237
|
+
raise
|
|
238
|
+
return numba.njit(function)
|
|
239
|
+
|
|
240
|
+
|
|
224
241
|
if numba is None:
|
|
225
242
|
_fit_and_drift = _fit_and_drift_numpy
|
|
226
243
|
_worst_ratio = _worst_ratio_numpy
|
|
227
244
|
else:
|
|
228
245
|
|
|
229
|
-
@
|
|
246
|
+
@_compiled
|
|
230
247
|
def _power_into(
|
|
231
248
|
coefficients, m, c1, s1, c2, s2, out
|
|
232
249
|
): # pragma: no cover - compiled
|
|
@@ -249,7 +266,7 @@ else:
|
|
|
249
266
|
for i in range(bins):
|
|
250
267
|
out[i] = num[i] / den[i]
|
|
251
268
|
|
|
252
|
-
@
|
|
269
|
+
@_compiled
|
|
253
270
|
def _fit_and_drift(
|
|
254
271
|
response, realised, c1, s1, c2, s2, target_inverse
|
|
255
272
|
): # pragma: no cover - compiled
|
|
@@ -277,7 +294,7 @@ else:
|
|
|
277
294
|
drift[m] = worst
|
|
278
295
|
return error, drift
|
|
279
296
|
|
|
280
|
-
@
|
|
297
|
+
@_compiled
|
|
281
298
|
def _worst_ratio(
|
|
282
299
|
numerator, denominator, c1, s1, c2, s2, weight, symmetric
|
|
283
300
|
): # pragma: no cover - compiled
|
|
@@ -485,7 +485,7 @@ FEATURES: tuple[Feature, ...] = (
|
|
|
485
485
|
_judged(
|
|
486
486
|
"judge.overlift",
|
|
487
487
|
"lifting above anything the material or the target asks",
|
|
488
|
-
("accept:overlift_bar_db",),
|
|
488
|
+
("accept:overlift_bar_db", "accept:AcceptParams.bass_ceiling_hz"),
|
|
489
489
|
"overlift",
|
|
490
490
|
"lifts ",
|
|
491
491
|
),
|
|
@@ -116,3 +116,31 @@ note = "the target and ceiling reach the fit quantised as before"
|
|
|
116
116
|
feature = "fit.choice"
|
|
117
117
|
kind = "unchanged"
|
|
118
118
|
note = "`_choose_fit` and the near-equal window are untouched; what they choose among is re-rolled"
|
|
119
|
+
|
|
120
|
+
[[change]]
|
|
121
|
+
id = "bass-ceiling"
|
|
122
|
+
design_version = 9
|
|
123
|
+
summary = "bass EQ stops at a 60 Hz ceiling: every priced target tapers to nothing by it, the judged band's top is capped there (its one-octave span is not), and above it a cascade may add no more than the level tolerance, a bound the fitter is given too (TODO 39)"
|
|
124
|
+
evidence = "plans/bass-ceiling.md#outcome-31-october-2026"
|
|
125
|
+
|
|
126
|
+
[[change.touches]]
|
|
127
|
+
feature = "targets.shared"
|
|
128
|
+
kind = "behaviour"
|
|
129
|
+
# Measured on the library, moved by `impact.MOVED`, together with judge.overlift (one change,
|
|
130
|
+
# not separable): v8 library titles designed at version 8 and 9 on one machine, from the
|
|
131
|
+
# library's audio with default settings. The designed classes are 60 titles lifting 3 dB above
|
|
132
|
+
# 60 Hz at version 8 (random, plus those named in review) and 20 random others; the declined
|
|
133
|
+
# classes a random sample of each. The 3 declined_after_fit titles that now design are fit
|
|
134
|
+
# re-rolls inside bands that end at 36-43 Hz, where the ceiling does not bind (row 32).
|
|
135
|
+
basis = { no_fit = [0, 10], declined_after_fit = [3, 12], designed_with_failures = [11, 15], designed_all_passed = [54, 65] }
|
|
136
|
+
|
|
137
|
+
[[change.touches]]
|
|
138
|
+
feature = "judge.overlift"
|
|
139
|
+
kind = "behaviour"
|
|
140
|
+
# The same measurement as targets.shared.
|
|
141
|
+
basis = { no_fit = [0, 10], declined_after_fit = [3, 12], designed_with_failures = [11, 15], designed_all_passed = [54, 65] }
|
|
142
|
+
|
|
143
|
+
[[change.touches]]
|
|
144
|
+
feature = "judge.assess"
|
|
145
|
+
kind = "unchanged"
|
|
146
|
+
note = "the over-lift failure names the bass ceiling when its worst bin lies above it; wording only"
|
|
@@ -819,18 +819,25 @@ def _flat_deficit(material: Material, params: "PipelineParams"):
|
|
|
819
819
|
|
|
820
820
|
|
|
821
821
|
def _judged_top_hz(
|
|
822
|
-
anchor_hz: float,
|
|
822
|
+
anchor_hz: float,
|
|
823
|
+
params: "PipelineParams",
|
|
824
|
+
noise_floor_hz: float = math.nan,
|
|
825
|
+
*,
|
|
826
|
+
ceiling: bool = True,
|
|
823
827
|
) -> float:
|
|
824
828
|
"""Upper edge of the judged band — and the pivot of the goal below the knee.
|
|
825
829
|
|
|
826
|
-
The deficit's first settled end (where the correction stops),
|
|
830
|
+
The deficit's first settled end (where the correction stops), no higher than the bass
|
|
831
|
+
ceiling (`AcceptParams.bass_ceiling_hz`) unless `ceiling` is false, but at least
|
|
827
832
|
`min_judge_octaves` above the band's floor, so there is always enough band to read a
|
|
828
|
-
slope over.
|
|
833
|
+
slope over.
|
|
829
834
|
"""
|
|
830
835
|
low = params.verify_floor_hz
|
|
831
836
|
if not math.isnan(noise_floor_hz):
|
|
832
837
|
low = max(low, noise_floor_hz)
|
|
833
838
|
widest = low * 2.0**params.accept.min_judge_octaves
|
|
839
|
+
if ceiling and params.accept.bass_ceiling_hz is not None:
|
|
840
|
+
anchor_hz = min(anchor_hz, params.accept.bass_ceiling_hz)
|
|
834
841
|
return min(max(anchor_hz, widest), float(DESIGN_GRID[-1]))
|
|
835
842
|
|
|
836
843
|
|
|
@@ -1033,8 +1040,9 @@ def passband_ripple_db(
|
|
|
1033
1040
|
if measured is None:
|
|
1034
1041
|
return None
|
|
1035
1042
|
freqs, response, _, _, _, anchor = measured
|
|
1043
|
+
# the passband above the deficit's own end, wherever the bass ceiling stops the correction
|
|
1036
1044
|
low, high = (
|
|
1037
|
-
_judged_top_hz(anchor, params, noise_floor_hz),
|
|
1045
|
+
_judged_top_hz(anchor, params, noise_floor_hz, ceiling=False),
|
|
1038
1046
|
params.diagnose.band_hz[1],
|
|
1039
1047
|
)
|
|
1040
1048
|
bands = (*params.exclude_bands_hz, *mains_hum_notches(freqs, response, low, high))
|
|
@@ -1131,6 +1139,21 @@ def priced_by_evidence(
|
|
|
1131
1139
|
— the ambition E3 found selection rewarding.
|
|
1132
1140
|
"""
|
|
1133
1141
|
target, notes = _held_below_floor(target, diagnosis.noise_floor_hz)
|
|
1142
|
+
ceiling_hz = params.accept.bass_ceiling_hz
|
|
1143
|
+
if ceiling_hz is not None:
|
|
1144
|
+
# tapered to nothing at the bass ceiling, over the same ratio a deficit closes over
|
|
1145
|
+
capped = target * _taper(
|
|
1146
|
+
DESIGN_GRID,
|
|
1147
|
+
ceiling_hz / params.flatten_taper_ratio,
|
|
1148
|
+
params.flatten_taper_ratio,
|
|
1149
|
+
)
|
|
1150
|
+
cut = int(np.argmax(target - capped))
|
|
1151
|
+
if target[cut] - capped[cut] > 0.5:
|
|
1152
|
+
notes.append(
|
|
1153
|
+
f"bass ceiling binds: the target asks for {target[cut]:.1f} dB at "
|
|
1154
|
+
f"{DESIGN_GRID[cut]:.1f} Hz, tapered to nothing by {ceiling_hz:g} Hz"
|
|
1155
|
+
)
|
|
1156
|
+
target = capped
|
|
1134
1157
|
if deficit_db is not None:
|
|
1135
1158
|
excess = target - deficit_db
|
|
1136
1159
|
worst = int(np.argmax(excess))
|