Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| # miraat_ref_v2.py β Ψ§ΩΩ Ψ±Ψ’Ψ© reference-guided enhancement (v2: real neural backends) | |
| # | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # β v2 replaces the imaginary AnyEnhance/Amphion stub with real, β | |
| # β pip-installable neural components. β | |
| # β β | |
| # β Architecture (mirrors AnyEnhance's two-stage intent): β | |
| # β β | |
| # β Stage 1 β Reference speaker verification β | |
| # β SpeechBrain ECAPA-TDNN: encode reference + degraded β speaker cosine β | |
| # β Gate: sim β₯ 0.70 β same speaker β proceed. β | |
| # β Source: speechbrain/spkrec-ecapa-voxceleb (MIT, ~15MB) β | |
| # β β | |
| # β Stage 2 β Reference-guided spectral normalisation β | |
| # β a. Extract 1/3-octave spectral profile from the 1425H reference β | |
| # β b. Run Resemble Enhance (denoise + HF reconstruct) on the degraded β | |
| # β c. Apply a reference-matched EQ correction (5-band parametric) β | |
| # β This steers the enhanced output toward the Sheikh's tonal signature β | |
| # β β the same goal as AnyEnhance's acoustic decoding stage. β | |
| # β β | |
| # β Stage 3 β 4-gate validation (same as v1) β | |
| # β Gate-a: speaker cosine similarity β₯ 0.70 post-enhancement β | |
| # β Gate-b: emphatic consonant ratio β€ 1.5 dB β | |
| # β Gate-c: Madd shortening β€ 5% β | |
| # β Gate-d: LUFS shift β€ 3.0 LU β | |
| # β β | |
| # β Install: β | |
| # β pip install speechbrain resemble-enhance β | |
| # β # (Resemble shared with hakim_gen_v2 β install once) β | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # | |
| # KB References: Β§40 (AnyEnhance), Β§40.2 (two-stage), Β§40.5 (Arabic bias) | |
| from __future__ import annotations | |
| import os | |
| import math | |
| import subprocess | |
| import warnings | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple, Callable | |
| _TMP = os.environ.get('ISTEIDAD_TMP', '/tmp') | |
| SR = 48000 | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Optional heavy imports | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| import numpy as np | |
| from scipy.fft import rfft, rfftfreq | |
| _NP_OK = True | |
| except ImportError: | |
| _NP_OK = False | |
| try: | |
| import torch as _torch | |
| import torchaudio as _ta | |
| _TORCH_OK = True | |
| except ImportError: | |
| _TORCH_OK = False | |
| # ββ Stage 1: ECAPA-TDNN speaker embeddings (SpeechBrain) βββββββββββββββββββββ | |
| try: | |
| from speechbrain.pretrained import SpeakerRecognition as _SpeakerRec # type: ignore | |
| _ECAPA_OK = True | |
| except ImportError: | |
| _ECAPA_OK = False | |
| # ββ Stage 2: Resemble Enhance (shared backend with hakim_gen_v2) ββββββββββββββ | |
| try: | |
| from resemble_enhance.enhancer.inference import enhance as _resemble_enhance_fn # type: ignore | |
| _RESEMBLE_OK = True | |
| except ImportError: | |
| _RESEMBLE_OK = False | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Constants | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Trigger tiers (only damaged/critical need reference-guided repair) | |
| _TRIGGER_TIERS = {'TIER_DAMAGED', 'TIER_CRITICAL'} | |
| _TRIGGER_STYLES = {'MURATTAL'} # MUJAWWAD ornaments not yet validated | |
| # Speaker similarity gate (ECAPA cosine) | |
| _SIM_THRESHOLD = 0.70 # < 0.70 β different speaker or severe degradation | |
| # Tajweed / loudness gates (same as v1) | |
| _GATE_EMPHATIC_DELTA_DB = 1.5 | |
| _GATE_MADD_SHORTENING = 0.05 | |
| _GATE_LUFS_DELTA_MAX = 3.0 | |
| # Reference EQ correction strength (0.0 = off, 1.0 = full match) | |
| _REF_EQ_BLEND = 0.65 # partial correction β preserve some of the input character | |
| # Model cache | |
| _MODELS_DIR = Path.home() / '.hakim_models' | |
| _ECAPA_DIR = _MODELS_DIR / 'ecapa-voxceleb' | |
| _ECAPA_HF_ID = 'speechbrain/spkrec-ecapa-voxceleb' | |
| # 1/3-octave centre frequencies for reference profiling | |
| _CENTERS_3OCT = [125, 160, 200, 250, 315, 400, 500, 630, 800, | |
| 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000] | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Result dataclass | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MiraatResult: | |
| output_wav: str = '' | |
| status: str = 'UNAVAILABLE' | |
| reason: str = '' | |
| style_gate: str = 'UNKNOWN' | |
| speaker_sim_before: float = 0.0 | |
| speaker_sim_after: float = 0.0 | |
| gate_speaker_pass: bool = False | |
| gate_emphatic_pass: bool = False | |
| gate_madd_pass: bool = False | |
| gate_lufs_pass: bool = False | |
| lufs_before: float = 0.0 | |
| lufs_after: float = 0.0 | |
| ref_eq_applied: bool = False | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Audio helpers | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _load_wav_mono(path: str, max_s: float = 30.0) -> Optional['np.ndarray']: | |
| tmp = os.path.join(_TMP, f'miraat_load_{os.getpid()}.f32') | |
| try: | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', path, | |
| '-af', 'aformat=channel_layouts=mono', | |
| '-t', str(max_s), '-ar', str(SR), | |
| '-f', 'f32le', '-loglevel', 'error', tmp], | |
| capture_output=True | |
| ) | |
| if r.returncode != 0 or not os.path.exists(tmp): | |
| return None | |
| audio = np.frombuffer(open(tmp, 'rb').read(), dtype=np.float32).copy() | |
| return audio | |
| except Exception: | |
| return None | |
| finally: | |
| try: | |
| os.remove(tmp) | |
| except Exception: | |
| pass | |
| def _measure_lufs_simple(path: str) -> float: | |
| """Quick LUFS proxy using ffmpeg loudnorm stats.""" | |
| try: | |
| r = subprocess.run( | |
| ['ffmpeg', '-i', path, '-af', | |
| 'loudnorm=print_format=summary', '-f', 'null', '-'], | |
| capture_output=True, text=True | |
| ) | |
| for line in r.stderr.split('\n'): | |
| if 'Input Integrated' in line: | |
| return float(line.split(':')[-1].strip().replace(' LUFS', '')) | |
| except Exception: | |
| pass | |
| return -23.0 | |
| def _convert_to_16k(wav_path: str) -> Optional[str]: | |
| out = os.path.join(_TMP, f'miraat_16k_{os.getpid()}.wav') | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', wav_path, | |
| '-af', 'aformat=channel_layouts=mono', | |
| '-ar', '16000', '-c:a', 'pcm_s16le', '-loglevel', 'error', out], | |
| capture_output=True | |
| ) | |
| return out if (r.returncode == 0 and os.path.exists(out)) else None | |
| def _upsample_to_48k(wav_path: str) -> Optional[str]: | |
| out = os.path.join(_TMP, f'miraat_48k_{os.getpid()}.wav') | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', wav_path, | |
| '-ar', str(SR), '-ac', '1', '-c:a', 'pcm_s24le', | |
| '-loglevel', 'error', out], | |
| capture_output=True | |
| ) | |
| return out if (r.returncode == 0 and os.path.exists(out)) else None | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stage 1 β Speaker embedding + similarity | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _ecapa_model_cache = None | |
| def _get_ecapa_model(log_fn: Callable) -> Optional[object]: | |
| """Load ECAPA-TDNN model (lazy, cached).""" | |
| global _ecapa_model_cache | |
| if not _ECAPA_OK: | |
| return None | |
| if _ecapa_model_cache is not None: | |
| return _ecapa_model_cache | |
| try: | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/ECAPA] loading speaker model (auto-downloads ~15MB) ...') | |
| _ecapa_model_cache = _SpeakerRec.from_hparams( # type: ignore | |
| source = _ECAPA_HF_ID, | |
| savedir = str(_ECAPA_DIR), | |
| run_opts = {'device': 'cpu'}, | |
| ) | |
| return _ecapa_model_cache | |
| except Exception as exc: | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/ECAPA] load failed: {exc}') | |
| return None | |
| def _speaker_cosine_similarity(wav_a: str, wav_b: str, | |
| model: object, | |
| log_fn: Callable) -> float: | |
| """ | |
| Compute ECAPA-TDNN speaker cosine similarity between two WAV files. | |
| Returns 0.0 on error. Higher = more similar speaker. | |
| """ | |
| try: | |
| wav_a_16k = _convert_to_16k(wav_a) | |
| wav_b_16k = _convert_to_16k(wav_b) | |
| if wav_a_16k is None or wav_b_16k is None: | |
| return 0.0 | |
| score, pred = model.verify_files(wav_a_16k, wav_b_16k) # type: ignore | |
| sim = float(score.squeeze()) | |
| return sim | |
| except Exception as exc: | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/ECAPA] similarity error: {exc}') | |
| return 0.0 | |
| finally: | |
| for p in (wav_a_16k, wav_b_16k): | |
| try: | |
| if p and os.path.exists(p): | |
| os.remove(p) | |
| except Exception: | |
| pass | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Stage 2 β Reference spectral profile + Resemble Enhance | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _third_octave_profile(audio: 'np.ndarray', sr: int = SR | |
| ) -> Dict[int, float]: | |
| """Compute 1/3-octave band energy profile (dBFS) from audio array.""" | |
| if not _NP_OK or len(audio) < sr: | |
| return {} | |
| chunk = audio[:sr * 10] if len(audio) > sr * 10 else audio | |
| N = len(chunk) | |
| win = np.hanning(N) | |
| norm = float(np.sqrt(np.sum(win ** 2) / N)) | |
| if norm < 1e-12: | |
| return {} | |
| spec = np.abs(rfft(chunk * win)) / (norm * N) | |
| freqs = rfftfreq(N, 1.0 / sr) | |
| out = {} | |
| for fc in _CENTERS_3OCT: | |
| if fc >= sr / 2: | |
| continue | |
| fl = fc / (2 ** (1 / 6)) | |
| fh = fc * (2 ** (1 / 6)) | |
| mask = (freqs >= fl) & (freqs < fh) | |
| if mask.sum() > 0: | |
| out[fc] = float(20 * np.log10(np.mean(spec[mask]) + 1e-10)) | |
| return out | |
| def _build_reference_eq_nodes(ref_profile: Dict[int, float], | |
| src_profile: Dict[int, float], | |
| blend: float = _REF_EQ_BLEND | |
| ) -> List[Tuple[float, float, float]]: | |
| """ | |
| Compute EQ nodes to nudge src_profile toward ref_profile. | |
| Returns list of (freq_hz, gain_db, Q) parametric nodes. | |
| Limited to bands 250Hzβ8kHz and Β±6dB per node. | |
| Q=1.41 (broad shelf per band) β prevents narrow resonances. | |
| """ | |
| nodes = [] | |
| for fc in _CENTERS_3OCT: | |
| if fc < 250 or fc > 8000: | |
| continue | |
| if fc not in ref_profile or fc not in src_profile: | |
| continue | |
| delta = (ref_profile[fc] - src_profile[fc]) * blend | |
| # Clamp: never more than Β±6dB, never boost below 250Hz identity zone | |
| if 250 <= fc <= 800: | |
| delta = float(max(-3.0, min(3.0, delta))) # voice identity zone | |
| else: | |
| delta = float(max(-6.0, min(6.0, delta))) | |
| if abs(delta) >= 0.5: # ignore sub-0.5dB corrections (noise floor) | |
| nodes.append((float(fc), round(delta, 2), 1.41)) | |
| return nodes | |
| def _apply_eq_nodes(wav_path: str, | |
| nodes: List[Tuple[float, float, float]], | |
| log_fn: Callable) -> Optional[str]: | |
| """Apply parametric EQ nodes via ffmpeg equalizer filter.""" | |
| if not nodes: | |
| return wav_path | |
| parts = [f'equalizer=f={f:.0f}:width_type=q:width={q:.2f}:g={g:.2f}' | |
| for f, g, q in nodes if abs(g) >= 0.5] | |
| if not parts: | |
| return wav_path | |
| af_str = ','.join(parts) | |
| out = os.path.join(_TMP, f'miraat_eq_{os.getpid()}.wav') | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', wav_path, '-af', af_str, | |
| '-ar', str(SR), '-ac', '1', '-c:a', 'pcm_s24le', | |
| '-loglevel', 'error', out], | |
| capture_output=True | |
| ) | |
| if r.returncode != 0 or not os.path.exists(out): | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/RefEQ] ffmpeg EQ failed β skipping correction') | |
| return wav_path | |
| gain_str = ' '.join(f'{f:.0f}Hz{g:+.1f}dB' for f, g, _ in nodes[:6]) | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/RefEQ] applied {len(nodes)}-band correction: {gain_str}') | |
| return out | |
| def _run_resemble_enhance_miraat(wav_path: str, | |
| log_fn: Callable) -> Tuple[str, bool]: | |
| """ | |
| Resemble Enhance in 'enhancer' mode for Ψ§ΩΩ Ψ±Ψ’Ψ©. | |
| Ξ»=0.4: lean toward denoising (TIER_DAMAGED/CRITICAL source β don't | |
| add speculative HF content; let the reference EQ handle spectral steering). | |
| """ | |
| if not _RESEMBLE_OK or not _TORCH_OK: | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/Resemble] not installed β ' | |
| 'run: pip install resemble-enhance') | |
| return wav_path, False | |
| device = 'cuda' if _torch.cuda.is_available() else 'cpu' | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/Resemble] enhancing (nfe=32 Ξ»=0.4 device={device}) ...') | |
| tmp_enh = os.path.join(_TMP, f'miraat_resemble_{os.getpid()}.wav') | |
| tmp_48k = os.path.join(_TMP, f'miraat_resemble48k_{os.getpid()}.wav') | |
| try: | |
| dwav, sr = _ta.load(wav_path) | |
| if dwav.shape[0] > 1: | |
| dwav = dwav.mean(dim=0, keepdim=True) | |
| with _torch.no_grad(): | |
| enhanced, new_sr = _resemble_enhance_fn( # type: ignore | |
| dwav, sr, device=device, | |
| nfe=32, solver='midpoint', lambd=0.4, tau=0.5, | |
| ) | |
| if enhanced.dim() == 1: | |
| enhanced = enhanced.unsqueeze(0) | |
| _ta.save(tmp_enh, enhanced.cpu(), new_sr) | |
| if new_sr != SR: | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', tmp_enh, | |
| '-ar', str(SR), '-ac', '1', '-c:a', 'pcm_s24le', | |
| '-loglevel', 'error', tmp_48k], | |
| capture_output=True | |
| ) | |
| if r.returncode != 0 or not os.path.exists(tmp_48k): | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/Resemble] 48kHz conversion failed') | |
| return wav_path, False | |
| try: | |
| os.remove(tmp_enh) | |
| except Exception: | |
| pass | |
| return tmp_48k, True | |
| return tmp_enh, True | |
| except Exception as exc: | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/Resemble] error: {exc}') | |
| return wav_path, False | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Tajweed + LUFS gates | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _compute_emphatic_ratio_db(audio: 'np.ndarray', sr: int = SR) -> float: | |
| if not _NP_OK or len(audio) < sr: | |
| return 0.0 | |
| chunk = audio[:sr * 10] if len(audio) > sr * 10 else audio | |
| N = len(chunk) | |
| spec = np.abs(rfft(chunk * np.hanning(N))) ** 2 | |
| freqs = rfftfreq(N, 1.0 / sr) | |
| def _b(lo: float, hi: float) -> float: | |
| m = (freqs >= lo) & (freqs < hi) | |
| return float(10 * np.log10(np.mean(spec[m]) + 1e-30)) if m.sum() > 0 else -60.0 | |
| return _b(600.0, 900.0) - _b(1200.0, 2400.0) | |
| def _detect_sustained_vowels_ms(audio: 'np.ndarray', sr: int = SR) -> List[float]: | |
| frame_len = int(sr * 0.020) | |
| if not _NP_OK or len(audio) < frame_len * 3: | |
| return [] | |
| rms = [float(np.sqrt(np.mean(audio[i:i+frame_len]**2) + 1e-30)) | |
| for i in range(0, len(audio) - frame_len, frame_len)] | |
| if not rms: | |
| return [] | |
| thr = float(np.percentile(rms, 60)) * 0.6 | |
| durs: List[float] = [] | |
| in_v = False | |
| s = 0 | |
| for i, r in enumerate(rms): | |
| if r >= thr and not in_v: | |
| in_v = True | |
| s = i | |
| elif r < thr and in_v: | |
| d = (i - s) * 20.0 | |
| if 80 <= d <= 800: | |
| durs.append(d) | |
| in_v = False | |
| return durs | |
| def _validate_all_gates( | |
| wav_before: str, | |
| wav_after: str, | |
| ref_wav: str, | |
| ecapa_model, | |
| lufs_before: float, | |
| log_fn: Callable | |
| ) -> Tuple[bool, str, MiraatResult]: | |
| """ | |
| 4-gate validation post-enhancement. | |
| Gate-a: speaker similarity β₯ 0.70 | |
| Gate-b: emphatic ratio delta β€ 1.5 dB | |
| Gate-c: Madd shortening β€ 5% | |
| Gate-d: LUFS shift β€ 3.0 LU | |
| """ | |
| partial = MiraatResult() | |
| # Gate-a: speaker similarity (ref vs enhanced) | |
| if ecapa_model is not None: | |
| sim_after = _speaker_cosine_similarity(ref_wav, wav_after, ecapa_model, log_fn) | |
| partial.speaker_sim_after = sim_after | |
| partial.gate_speaker_pass = sim_after >= _SIM_THRESHOLD | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-a] speaker sim={sim_after:.3f} ' | |
| f'{"β" if partial.gate_speaker_pass else "β (<0.70)"}') | |
| if not partial.gate_speaker_pass: | |
| return False, f'speaker_sim={sim_after:.3f} < {_SIM_THRESHOLD}', partial | |
| else: | |
| partial.gate_speaker_pass = True # gate bypassed β no ECAPA | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-a] ECAPA not available β speaker gate bypassed') | |
| # Gate-b: emphatic | |
| if _NP_OK: | |
| ab = _load_wav_mono(wav_before, max_s=30.0) | |
| aa = _load_wav_mono(wav_after, max_s=30.0) | |
| if ab is not None and aa is not None: | |
| emp_b = _compute_emphatic_ratio_db(ab) | |
| emp_a = _compute_emphatic_ratio_db(aa) | |
| emp_d = emp_a - emp_b | |
| partial.gate_emphatic_pass = abs(emp_d) <= _GATE_EMPHATIC_DELTA_DB | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-b] emphatic Ξ={emp_d:+.2f}dB ' | |
| f'{"β" if partial.gate_emphatic_pass else "β"}') | |
| if not partial.gate_emphatic_pass: | |
| return False, f'emphatic_delta={emp_d:.2f}dB', partial | |
| # Gate-c: Madd | |
| if ab is not None and aa is not None: | |
| madd_b = _detect_sustained_vowels_ms(ab) | |
| madd_a = _detect_sustained_vowels_ms(aa) | |
| if madd_b and madd_a: | |
| db_m = float(np.mean(madd_b)) | |
| da_m = float(np.mean(madd_a)) | |
| frac = max(0.0, (db_m - da_m) / max(db_m, 1.0)) | |
| partial.gate_madd_pass = frac <= _GATE_MADD_SHORTENING | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-c] Madd Ξ={frac:.1%} ' | |
| f'{"β" if partial.gate_madd_pass else "β"}') | |
| if not partial.gate_madd_pass: | |
| return False, f'madd_shortening={frac:.1%}', partial | |
| else: | |
| partial.gate_madd_pass = True | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-c] Madd gate bypassed (no vowel segments)') | |
| else: | |
| partial.gate_emphatic_pass = True | |
| partial.gate_madd_pass = True | |
| log_fn(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-bc] audio load failed β Tajweed gates bypassed') | |
| else: | |
| partial.gate_emphatic_pass = True | |
| partial.gate_madd_pass = True | |
| # Gate-d: LUFS | |
| lufs_after = _measure_lufs_simple(wav_after) | |
| partial.lufs_after = lufs_after | |
| lufs_shift = abs(lufs_after - lufs_before) | |
| partial.gate_lufs_pass = lufs_shift <= _GATE_LUFS_DELTA_MAX | |
| log_fn(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/gate-d] LUFS {lufs_before:.1f}β{lufs_after:.1f}LU ' | |
| f'(Ξ={lufs_shift:.1f}LU) ' | |
| f'{"β" if partial.gate_lufs_pass else "β (>{_GATE_LUFS_DELTA_MAX}LU)"}') | |
| if not partial.gate_lufs_pass: | |
| return False, f'lufs_shift={lufs_shift:.1f}LU', partial | |
| return True, 'passed', partial | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Main orchestrator β API-compatible with miraat_ref_v1.apply_miraat() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def apply_miraat( | |
| wav_path: str, | |
| state, | |
| ref_files: List[str], | |
| log_fn: Optional[Callable] = None | |
| ) -> Tuple[str, MiraatResult]: | |
| """ | |
| Ψ§ΩΩ Ψ±Ψ’Ψ© v2 β The Mirror (reference-guided enhancement). | |
| Phase B-ref: ECAPA speaker check β Resemble Enhance β reference EQ β 4-gate. | |
| Returns (output_wav_path, MiraatResult). | |
| Falls back to wav_path on any failure or gate rejection. | |
| """ | |
| result = MiraatResult(output_wav=wav_path) | |
| def _log(msg: str): | |
| if log_fn: | |
| log_fn(msg) | |
| source_tier = getattr(state, 'source_tier', 'TIER_CLEAN') | |
| style_class = getattr(state, 'style_class', 'MURATTAL') | |
| _log('\nPhase B-ref β Ψ§ΩΩ Ψ±Ψ’Ψ© v2 (ECAPA + Resemble + RefEQ)') | |
| _log(f' tier={source_tier} style={style_class}') | |
| _log(f' backends β ecapa={_ECAPA_OK} resemble={_RESEMBLE_OK}') | |
| # ββ Tier / style gate βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if source_tier not in _TRIGGER_TIERS: | |
| result.status = 'SKIPPED' | |
| result.reason = f'tier={source_tier} not in {_TRIGGER_TIERS}' | |
| _log(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©] skip β {result.reason}') | |
| return wav_path, result | |
| if style_class == 'MUJAWWAD': | |
| result.status = 'SKIPPED' | |
| result.style_gate = 'MUJAWWAD_UNVALIDATED' | |
| result.reason = 'MUJAWWAD ornamental sweeps not validated β bypass' | |
| _log(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©] {result.reason}') | |
| return wav_path, result | |
| result.style_gate = 'PASSED' | |
| # ββ Reference selection βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ref_wav = None | |
| for rf in (ref_files or []): | |
| if rf and os.path.exists(rf): | |
| ref_wav = rf | |
| break | |
| if ref_wav is None: | |
| result.status = 'SKIPPED' | |
| result.reason = 'no reference file available' | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©] no reference file β bypass') | |
| return wav_path, result | |
| # Extract a short clean clip from reference (first 20s, skip 2s silence) | |
| ref_clip = os.path.join(_TMP, f'miraat_refclip_{os.getpid()}.wav') | |
| r = subprocess.run( | |
| ['ffmpeg', '-y', '-i', ref_wav, '-ss', '2', '-t', '20', | |
| '-ar', str(SR), '-ac', '1', '-c:a', 'pcm_s24le', | |
| '-loglevel', 'error', ref_clip], | |
| capture_output=True | |
| ) | |
| if r.returncode != 0 or not os.path.exists(ref_clip): | |
| result.status = 'SKIPPED' | |
| result.reason = 'reference clip extraction failed' | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©] reference clip extraction failed β bypass') | |
| return wav_path, result | |
| lufs_before = _measure_lufs_simple(wav_path) | |
| result.lufs_before = lufs_before | |
| # ββ Stage 1: Baseline speaker similarity (ref vs degraded) βββββββββββββββ | |
| ecapa = _get_ecapa_model(_log) | |
| if ecapa is not None: | |
| sim_before = _speaker_cosine_similarity(ref_clip, wav_path, ecapa, _log) | |
| result.speaker_sim_before = sim_before | |
| _log(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-1] speaker sim (ref vs input) = {sim_before:.3f}') | |
| if sim_before < _SIM_THRESHOLD * 0.6: | |
| # Very low similarity β likely wrong speaker or too damaged | |
| _log(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-1] sim={sim_before:.3f} < ' | |
| f'{_SIM_THRESHOLD * 0.6:.2f} β input too damaged for ref-guided path') | |
| result.status = 'SKIPPED' | |
| result.reason = f'input speaker sim too low ({sim_before:.3f})' | |
| return wav_path, result | |
| else: | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-1] ECAPA not installed β ' | |
| 'run: pip install speechbrain (speaker gate bypassed)') | |
| result.speaker_sim_before = 0.0 | |
| # ββ Stage 2a: Resemble Enhance ββββββββββββββββββββββββββββββββββββββββββββ | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-2a] running Resemble Enhance (denoiser + HF reconstruct) ...') | |
| enh_out, enh_ok = _run_resemble_enhance_miraat(wav_path, _log) | |
| current = enh_out if enh_ok else wav_path | |
| # ββ Stage 2b: Reference spectral profile β EQ correction βββββββββββββββββ | |
| ref_eq_out = current | |
| if _NP_OK: | |
| ref_audio = _load_wav_mono(ref_clip, max_s=20.0) | |
| src_audio = _load_wav_mono(current, max_s=20.0) | |
| if ref_audio is not None and src_audio is not None: | |
| ref_prof = _third_octave_profile(ref_audio) | |
| src_prof = _third_octave_profile(src_audio) | |
| eq_nodes = _build_reference_eq_nodes(ref_prof, src_prof) | |
| if eq_nodes: | |
| _log(f' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-2b] applying {len(eq_nodes)}-band ' | |
| f'reference EQ correction (blend={_REF_EQ_BLEND}) ...') | |
| eq_result = _apply_eq_nodes(current, eq_nodes, _log) | |
| if eq_result and eq_result != current: | |
| ref_eq_out = eq_result | |
| result.ref_eq_applied = True | |
| # Clean up intermediate enhanced file if EQ replaced it | |
| if enh_ok and current != wav_path: | |
| try: | |
| os.remove(current) | |
| except Exception: | |
| pass | |
| else: | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-2b] reference already matched β no EQ needed') | |
| else: | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-2b] audio load failed β ref EQ skipped') | |
| else: | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-2b] numpy unavailable β ref EQ skipped') | |
| # ββ Stage 3: 4-gate validation ββββββββββββββββββββββββββββββββββββββββββββ | |
| _log(' [Ψ§ΩΩ Ψ±Ψ’Ψ©/stage-3] 4-gate validation ...') | |
| gate_pass, gate_reason, gate_partial = _validate_all_gates( | |
| wav_before = wav_path, | |
| wav_after = ref_eq_out, | |
| ref_wav = ref_clip, | |
| ecapa_model = ecapa, | |
| lufs_before = lufs_before, | |
| log_fn = _log, | |
| ) | |
| result.gate_speaker_pass = gate_partial.gate_speaker_pass | |
| result.gate_emphatic_pass = gate_partial.gate_emphatic_pass | |
| result.gate_madd_pass = gate_partial.gate_madd_pass | |
| result.gate_lufs_pass = gate_partial.gate_lufs_pass | |
| result.speaker_sim_after = gate_partial.speaker_sim_after | |
| result.lufs_after = gate_partial.lufs_after | |
| # Clean up ref clip | |
| try: | |
| if os.path.exists(ref_clip): | |
| os.remove(ref_clip) | |
| except Exception: | |
| pass | |
| if gate_pass: | |
| result.status = 'OK' | |
| result.reason = 'all 4 gates passed' | |
| result.output_wav = ref_eq_out | |
| _log(f' Ψ§ΩΩ Ψ±Ψ’Ψ© β speaker_sim={result.speaker_sim_after:.3f} ' | |
| f'lufs={result.lufs_before:.1f}β{result.lufs_after:.1f}LU ' | |
| f'ref_eq={result.ref_eq_applied}') | |
| return ref_eq_out, result | |
| else: | |
| result.status = 'REVERTED' | |
| result.reason = gate_reason | |
| result.output_wav = wav_path | |
| _log(f' Ψ§ΩΩ Ψ±Ψ’Ψ© REVERTED β {gate_reason}') | |
| # Clean up intermediate files | |
| for f in (ref_eq_out, enh_out): | |
| try: | |
| if f and f != wav_path and os.path.exists(f): | |
| os.remove(f) | |
| except Exception: | |
| pass | |
| return wav_path, result | |