Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down
116 changes: 116 additions & 0 deletions LOCAL_REPAIR_TRANSFER_MANIFEST_20260523.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# LOCAL REPAIR TRANSFER MANIFEST (2026-05-23)

Working folder: `C:\Users\lmr20\Desktop\SoundSpectrAnalyse-main\SoundSpectrAnalyse-main`

## Stage 8 Scope

- Cleanup/preparation only.
- No source logic changes during this stage.
- No GitHub operations.
- No Git initialization.

## 1) Cleanup Artefacts Identified

### `*.bak_*` (found before move)

- `compile_metrics.py.bak_20260523_131008`
- `density.py.bak_20260523_124509`
- `metrics_dictionary.json.bak_20260523_125447`
- `test_benchmarks.py.bak_20260523_125037`
- `test_external_validation_marketing_ban.py.bak_20260523_125037`

### Cache artefacts (found before removal)

- `__pycache__/` directories: 5
- `__pycache__/`
- `audio_analysis/__pycache__/`
- `tests/__pycache__/`
- `tests/formula_validation/__pycache__/`
- `tools/__pycache__/`
- `.pytest_cache/` directories: 1
- `.pytest_cache/`
- `.mypy_cache/`: none found
- `.ruff_cache/`: none found
- `*.pyc` files: 145

## 2) Backup Files Moved Outside Project

Destination folder:

- `C:\Users\lmr20\Desktop\SoundSpectrAnalyse_local_repair_backups_20260523`

Moved files:

- `compile_metrics.py.bak_20260523_131008`
- `density.py.bak_20260523_124509`
- `metrics_dictionary.json.bak_20260523_125447`
- `test_benchmarks.py.bak_20260523_125037`
- `test_external_validation_marketing_ban.py.bak_20260523_125037`

## 3) Cache Artefacts Removed (Safe Cleanup)

Removed:

- `__pycache__/` directories (5 total)
- `.pytest_cache/` directories (1 total)
- `*.pyc` files (145 total)

Not removed:

- `.mypy_cache/` (none found)
- `.ruff_cache/` (none found)

Post-cleanup verification:

- `*.bak_*`: none remaining in project
- `*.pyc`: none remaining in project
- `.pytest_cache/`: none remaining in project
- `__pycache__/`: none remaining in project

## 4) Transfer Inventory

### A. Modified Existing Files

- `density.py`
- `compile_metrics.py`
- `metrics_dictionary.json`

### B. Newly Created Required Files

- `tests/benchmarks/audio/pure_sine_440.wav`
- `tests/benchmarks/audio/harmonic_stack_220.wav`
- `tests/benchmarks/audio/inharmonic_injection.wav`
- `tests/benchmarks/audio/subbass_injection.wav`
- `audio_analysis/batch_results/sample_clean_case/super_analysis_results.json`
- `audio_analysis/batch_results/sample_clean_case/metrics_summary.txt`

### C. Files Moved Outside the Project

- `compile_metrics.py.bak_20260523_131008`
- `density.py.bak_20260523_124509`
- `metrics_dictionary.json.bak_20260523_125447`
- `test_benchmarks.py.bak_20260523_125037`
- `test_external_validation_marketing_ban.py.bak_20260523_125037`

## 5) Validation Results

### Baseline validated state (before cleanup)

- `python -m pytest -q` -> 822 passed, 0 failed, 40 skipped
- `python scripts/validate_stft_reference.py` -> 3 passed

### Final verification (after cleanup)

- `python -m pytest -q` -> 822 passed, 0 failed, 40 skipped, 996 warnings
- `python scripts/validate_stft_reference.py` -> 3 passed, 13 warnings

## 6) Semantic Summary

- `density.py`: Python 3.9 annotation compatibility fix (`from __future__ import annotations`).
- `compile_metrics.py`: log density adjusted to `log10(1 + sum(A))`; explicit `power_sum` debug basis preserves `Power_raw`.
- `metrics_dictionary.json`: repaired `quantity_type` values and `derived_from` references.
- Fixtures: deterministic benchmark audio fixtures and clean external-validation sample fixtures were added.

## 7) Readiness Statement

This folder is cleanup-complete, validated after cleanup, and ready to be used as the source for applying changes to a clean Git clone.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Spectral analysis for acoustic research. **Canonical publication pipeline:** **`
Optional **batch preprocessing** (`batch_audio_analyzer` / `super_audio_analyzer`) may supply **`batch_summary.xlsx`** for empirical **H+I+S** profiles and **H/(H+I)** model coefficients; it is **not** required for the canonical chain above. Legacy Tk / PyQt entry points remain ancillary.

**Package version:** 3.7.0 (`pyproject.toml`; at runtime: `importlib.metadata.version("soundspectranalyse")`)
**Python:** ≥ 3.9
**Python:** 3.10 and 3.11 (supported); Python 3.9 is not supported.

## Install

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Metrics summary (clean sample fixture)
No external symbolic-engine references.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"analysis_id": "sample_clean_case",
"version": "local-fixture",
"metrics": {
"harmonic_energy_percentage": 71.0,
"inharmonic_energy_percentage": 29.0
}
}
25 changes: 12 additions & 13 deletions compile_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2144,7 +2144,7 @@ def extract_density_component_sum(
Weighting-function semantics (single source of truth)::

linear -> D = SUM(Amplitude_raw)
log -> D = SUM(LOG10(1 + Amplitude_raw)) per row (no band-total log)
log -> D = LOG10(1 + SUM(Amplitude_raw))
power -> D = SUM(Power_raw)
(or SUM(Amplitude_raw ** 2) if Power_raw absent)

Expand Down Expand Up @@ -2304,7 +2304,7 @@ def extract_density_component_sum(
return result
column_used = str(amp_col)
series = pd.to_numeric(df[amp_col], errors="coerce")
sum_strategy = "sum_log10_1p_per_amplitude_raw"
sum_strategy = "log10_1p_sum_amplitude_raw"
elif wf == "power":
if power_col is not None:
column_used = str(power_col)
Expand Down Expand Up @@ -2355,9 +2355,7 @@ def extract_density_component_sum(

raw_total = float(series.to_numpy(dtype=float)[mask].sum())
if wf == "log":
amps_log = series.to_numpy(dtype=float, copy=False)[mask]
amps_log = np.maximum(amps_log, 0.0)
d_value = float(np.sum(np.log10(1.0 + amps_log)))
d_value = float(np.log10(1.0 + max(0.0, raw_total)))
else:
d_value = raw_total

Expand Down Expand Up @@ -2696,9 +2694,10 @@ def _extract_one(
# "log", and Amplitude_display_scaled is never read at all.
# ------------------------------------------------------------------
wf_op = _compile_operator_weight_function_key(weight_function)
wf_h = extract_density_component_sum(p, "Harmonic Spectrum", wf_op)
wf_i = extract_density_component_sum(p, "Inharmonic Spectrum", wf_op)
wf_s = extract_density_component_sum(p, "Sub-bass band", wf_op)
wf_components = "power" if basis == "power_sum" else wf_op
wf_h = extract_density_component_sum(p, "Harmonic Spectrum", wf_components)
wf_i = extract_density_component_sum(p, "Inharmonic Spectrum", wf_components)
wf_s = extract_density_component_sum(p, "Sub-bass band", wf_components)

result["density_weight_function"] = str(wf_h.get("weight_function") or wf_op)
result["harmonic_density_sum"] = wf_h.get("D")
Expand All @@ -2723,17 +2722,17 @@ def _extract_one(
if s
)
result["density_component_sum_source"] = combined_source
if wf_op in DENSITY_WEIGHT_FUNCTION_VALID and wf_op == "log":
if wf_components in DENSITY_WEIGHT_FUNCTION_VALID and wf_components == "log":
result["density_formula"] = (
"density_metric_raw = D_H*w_H + D_I*w_I + D_S*w_S; "
"D_band = SUM(log10(1 + Amplitude_raw))."
"D_band = log10(1 + SUM(Amplitude_raw))."
)
elif wf_op in DENSITY_WEIGHT_FUNCTION_VALID and wf_op == "power":
elif wf_components in DENSITY_WEIGHT_FUNCTION_VALID and wf_components == "power":
result["density_formula"] = (
"density_metric_raw = D_H*w_H + D_I*w_I + D_S*w_S; "
"D_band = SUM(Power_raw) (fallback SUM(Amplitude_raw**2))."
)
elif wf_op in DENSITY_WEIGHT_FUNCTION_VALID:
elif wf_components in DENSITY_WEIGHT_FUNCTION_VALID:
result["density_formula"] = (
"density_metric_raw = D_H*w_H + D_I*w_I + D_S*w_S; "
"D_band = SUM(Amplitude_raw)."
Expand All @@ -2742,7 +2741,7 @@ def _extract_one(
result["density_formula"] = (
"density_metric_raw = D_H*w_H + D_I*w_I + D_S*w_S; "
f"D_band = density.apply_density_metric(Amplitude_raw_vector, "
f"weight_function={wf_op!r}) per spectrum sheet."
f"weight_function={wf_components!r}) per spectrum sheet."
)

# Canonical per-band D values (weight_function-aware) replace the
Expand Down
2 changes: 2 additions & 0 deletions density.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# density.py - Corrected Version

from __future__ import annotations

"""
Module for calculating spectral density metrics for musical audio analysis.
Implements weight functions, density calculations, and combined metrics for
Expand Down
17 changes: 13 additions & 4 deletions metadata_sanitizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import os
import re
import zipfile
from pathlib import Path
from pathlib import Path, PureWindowsPath
from datetime import datetime, timezone
from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union

Expand Down Expand Up @@ -654,7 +654,9 @@ def detect_absolute_local_path(value: Any) -> bool:

def publication_audio_path_fields(path: Union[str, Path], *, dataset_root: Optional[Path] = None) -> dict[str, Any]:
"""Publication-safe audio path decomposition (no host directories)."""
p = Path(str(path))
path_text = str(path)
is_windows_style = bool(_WIN_ABS_START_EXPORT.match(path_text)) or "\\" in path_text
p = PureWindowsPath(path_text) if is_windows_style else Path(path_text)
name = p.name or "unknown_audio"
stem = p.stem
ext = p.suffix
Expand All @@ -663,7 +665,7 @@ def publication_audio_path_fields(path: Union[str, Path], *, dataset_root: Optio
rel = name
if dataset_root is not None:
try:
rel = Path(p).resolve().relative_to(Path(dataset_root).resolve()).as_posix()
rel = Path(path_text).resolve().relative_to(Path(dataset_root).resolve()).as_posix()
except Exception:
rel = name
rel_posix = rel.replace("\\", "/")
Expand Down Expand Up @@ -699,12 +701,19 @@ def sanitize_path_for_publication(path: Union[str, Path, None], dataset_root: Op
"""Basename or POSIX path relative to *dataset_root* (never absolute host paths)."""
if path is None:
return ""
p = Path(str(path))
path_text = str(path)
# Treat Windows-formatted paths explicitly so basename extraction works on Linux runners.
is_windows_style = bool(_WIN_ABS_START_EXPORT.match(path_text)) or "\\" in path_text
p = Path(path_text)
if dataset_root is not None:
try:
if is_windows_style:
return PureWindowsPath(path_text).name
return p.resolve().relative_to(Path(dataset_root).resolve()).as_posix()
except Exception:
pass
if is_windows_style:
return PureWindowsPath(path_text).name or "path_redacted"
return p.name if p.name else "path_redacted"


Expand Down
Loading
Loading