Spaces:
Running
Running
| from __future__ import annotations | |
| import io | |
| import math | |
| from dataclasses import dataclass | |
| from typing import Dict, Tuple | |
| import librosa | |
| import numpy as np | |
| import soundfile as sf | |
| from scipy import signal | |
| DECADES = [f"{year}s" for year in range(1920, 2030, 10)] | |
| ERA_PROFILES: Dict[str, Dict[str, object]] = { | |
| "1920s": { | |
| "instruments": ["upright piano", "banjo", "muted trumpet", "clarinet", "tuba"], | |
| "texture": "small acoustic jazz ensemble, mono recording, narrow bandwidth", | |
| "imperfections": ["shellac noise", "78 RPM crackle", "room bleed"], | |
| "lowpass": 4200, | |
| "highpass": 140, | |
| "mono": True, | |
| "noise": 0.018, | |
| "reverb": 0.08, | |
| "saturation": 1.9, | |
| }, | |
| "1930s": { | |
| "instruments": ["swing brass", "walking bass", "brush drums", "clarinet", "stride piano"], | |
| "texture": "big-band swing arrangement with early microphone coloration", | |
| "imperfections": ["light disc crackle", "bandstand room tone"], | |
| "lowpass": 5200, | |
| "highpass": 110, | |
| "mono": True, | |
| "noise": 0.014, | |
| "reverb": 0.11, | |
| "saturation": 1.7, | |
| }, | |
| "1940s": { | |
| "instruments": ["crooner strings", "horn section", "upright bass", "brush kit", "celeste"], | |
| "texture": "wartime radio warmth with intimate vocal-band balance", | |
| "imperfections": ["radio compression", "soft optical distortion"], | |
| "lowpass": 6100, | |
| "highpass": 90, | |
| "mono": True, | |
| "noise": 0.011, | |
| "reverb": 0.14, | |
| "saturation": 1.55, | |
| }, | |
| "1950s": { | |
| "instruments": ["clean electric guitar", "upright piano", "trumpet", "saxophone", "slapback drums"], | |
| "texture": "early rock-and-roll or jazz-club recording with slapback echo", | |
| "imperfections": ["vinyl surface noise", "tube warmth", "needle lift"], | |
| "lowpass": 7600, | |
| "highpass": 70, | |
| "mono": True, | |
| "noise": 0.009, | |
| "reverb": 0.19, | |
| "saturation": 1.35, | |
| }, | |
| "1960s": { | |
| "instruments": ["jangly guitar", "organ", "live drums", "Motown bass", "horn stabs"], | |
| "texture": "tape-era pop with plate reverb and tight band performance", | |
| "imperfections": ["tape hiss", "wow/flutter impression"], | |
| "lowpass": 9000, | |
| "highpass": 55, | |
| "mono": False, | |
| "noise": 0.007, | |
| "reverb": 0.22, | |
| "saturation": 1.22, | |
| }, | |
| "1970s": { | |
| "instruments": ["Rhodes piano", "analog strings", "funk bass", "dry drums", "wah guitar"], | |
| "texture": "warm album-track production with analog console color", | |
| "imperfections": ["tape saturation", "soft hiss"], | |
| "lowpass": 11000, | |
| "highpass": 45, | |
| "mono": False, | |
| "noise": 0.005, | |
| "reverb": 0.18, | |
| "saturation": 1.16, | |
| }, | |
| "1980s": { | |
| "instruments": ["FM synth", "gated snare", "chorus guitar", "DX-style bass", "digital pads"], | |
| "texture": "bright stereo mix with gated ambience and glossy synth layers", | |
| "imperfections": ["early digital grain", "chorus shimmer"], | |
| "lowpass": 13500, | |
| "highpass": 35, | |
| "mono": False, | |
| "noise": 0.003, | |
| "reverb": 0.32, | |
| "saturation": 1.06, | |
| }, | |
| "1990s": { | |
| "instruments": ["sampled drums", "distorted guitar", "rompler piano", "sub bass", "turntable cuts"], | |
| "texture": "CD-era loudness with sampler-driven edges", | |
| "imperfections": ["sample grit", "mild digital clipping"], | |
| "lowpass": 15000, | |
| "highpass": 30, | |
| "mono": False, | |
| "noise": 0.002, | |
| "reverb": 0.15, | |
| "saturation": 1.12, | |
| }, | |
| "2000s": { | |
| "instruments": ["Auto-Tune sheen", "compressed drums", "supersaw synth", "808 kick", "clean pop guitar"], | |
| "texture": "hyper-compressed digital pop with clean top-end", | |
| "imperfections": ["brickwall limiting", "edited precision"], | |
| "lowpass": 17000, | |
| "highpass": 25, | |
| "mono": False, | |
| "noise": 0.001, | |
| "reverb": 0.10, | |
| "saturation": 1.05, | |
| }, | |
| "2010s": { | |
| "instruments": ["trap hats", "sidechain synth", "808 bass", "vocal chops", "wide pads"], | |
| "texture": "streaming-era width, sub bass, and polished transient control", | |
| "imperfections": ["intentional lo-fi risers", "tight quantization"], | |
| "lowpass": 18500, | |
| "highpass": 22, | |
| "mono": False, | |
| "noise": 0.0008, | |
| "reverb": 0.12, | |
| "saturation": 1.03, | |
| }, | |
| "2020s": { | |
| "instruments": ["hybrid synths", "AI vocal textures", "spatial pads", "punchy 808s", "granular ear candy"], | |
| "texture": "modern streaming master with immersive, genre-fluid production", | |
| "imperfections": ["creative glitch edits", "ultra-clean limiting"], | |
| "lowpass": 20000, | |
| "highpass": 20, | |
| "mono": False, | |
| "noise": 0.0003, | |
| "reverb": 0.09, | |
| "saturation": 1.0, | |
| }, | |
| } | |
| class AudioAnalysis: | |
| duration_seconds: float | |
| tempo_bpm: float | |
| key_hint: str | |
| peak: float | |
| def analyze_audio(audio: np.ndarray, sample_rate: int) -> AudioAnalysis: | |
| mono = librosa.to_mono(audio.T) if audio.ndim == 2 else audio | |
| duration = float(len(mono) / sample_rate) | |
| tempo, _ = librosa.beat.beat_track(y=mono, sr=sample_rate) | |
| chroma = librosa.feature.chroma_cqt(y=mono, sr=sample_rate) | |
| pitch_classes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] | |
| key_hint = pitch_classes[int(np.argmax(np.mean(chroma, axis=1)))] if chroma.size else "Unknown" | |
| return AudioAnalysis(duration, float(np.atleast_1d(tempo)[0]), key_hint, float(np.max(np.abs(audio)) or 0.0)) | |
| def _butter_filter(audio: np.ndarray, sr: int, cutoff: float, kind: str) -> np.ndarray: | |
| nyquist = sr / 2 | |
| cutoff = min(max(cutoff, 20), nyquist - 100) | |
| sos = signal.butter(4, cutoff / nyquist, btype=kind, output="sos") | |
| return signal.sosfiltfilt(sos, audio, axis=0).astype(np.float32) | |
| def _simple_reverb(audio: np.ndarray, sr: int, amount: float) -> np.ndarray: | |
| if amount <= 0: | |
| return audio | |
| delay = max(1, int(sr * 0.055)) | |
| wet = np.zeros_like(audio) | |
| wet[delay:] += audio[:-delay] * 0.55 | |
| if delay * 2 < len(audio): | |
| wet[delay * 2 :] += audio[: -delay * 2] * 0.28 | |
| return (audio * (1 - amount) + wet * amount).astype(np.float32) | |
| def _add_noise(audio: np.ndarray, amount: float, seed: int = 7) -> np.ndarray: | |
| if amount <= 0: | |
| return audio | |
| rng = np.random.default_rng(seed) | |
| noise = rng.normal(0, amount, size=audio.shape).astype(np.float32) | |
| crackle_mask = rng.random(audio.shape) > 0.9975 | |
| crackle = rng.normal(0, amount * 7, size=audio.shape).astype(np.float32) * crackle_mask | |
| return (audio + noise + crackle).astype(np.float32) | |
| def transform_audio(audio: np.ndarray, sample_rate: int, decade: str) -> Tuple[np.ndarray, AudioAnalysis, Dict[str, object]]: | |
| if decade not in ERA_PROFILES: | |
| raise ValueError(f"Unsupported decade: {decade}") | |
| profile = ERA_PROFILES[decade] | |
| analysis = analyze_audio(audio, sample_rate) | |
| processed = audio.astype(np.float32) | |
| if processed.ndim == 1: | |
| processed = processed[:, None] | |
| processed = _butter_filter(processed, sample_rate, float(profile["highpass"]), "highpass") | |
| processed = _butter_filter(processed, sample_rate, float(profile["lowpass"]), "lowpass") | |
| saturation = float(profile["saturation"]) | |
| processed = np.tanh(processed * saturation) / max(math.tanh(saturation), 1e-6) | |
| processed = _simple_reverb(processed, sample_rate, float(profile["reverb"])) | |
| processed = _add_noise(processed, float(profile["noise"])) | |
| if bool(profile["mono"]): | |
| mono = np.mean(processed, axis=1, keepdims=True) | |
| processed = np.repeat(mono, 2, axis=1) | |
| peak = np.max(np.abs(processed)) or 1.0 | |
| processed = (processed / peak * 0.92).astype(np.float32) | |
| return processed, analysis, profile | |
| def build_report(decade: str, analysis: AudioAnalysis, profile: Dict[str, object], model_notes: str | None = None) -> str: | |
| instruments = list(profile["instruments"]) | |
| imperfections = list(profile["imperfections"]) | |
| score = min(100, 55 + len(instruments) * 6 + len(imperfections) * 5) | |
| lines = [ | |
| f"# Historical fidelity report: {decade}", | |
| "", | |
| f"**Input analysis:** about {analysis.duration_seconds:.1f}s, {analysis.tempo_bpm:.0f} BPM, key center hint: {analysis.key_hint}.", | |
| f"**Era texture:** {profile['texture']}.", | |
| f"**Era instruments referenced ({len(instruments)}):** {', '.join(instruments)}.", | |
| f"**Era imperfections added ({len(imperfections)}):** {', '.join(imperfections)}.", | |
| f"**Fidelity score:** {score}/100 for this prototype DSP pass.", | |
| ] | |
| if model_notes: | |
| lines.extend(["", "## Qwen era-arrangement notes", model_notes.strip()]) | |
| else: | |
| lines.extend([ | |
| "", | |
| "## Prototype notes", | |
| "This quick version preserves the uploaded performance while applying decade-specific bandwidth, mono/stereo width, saturation, reverb, and noise. A polished version would regenerate the arrangement with dedicated music models.", | |
| ]) | |
| return "\n".join(lines) | |
| def wav_bytes_from_array(audio: np.ndarray, sample_rate: int) -> bytes: | |
| buffer = io.BytesIO() | |
| sf.write(buffer, audio, sample_rate, format="WAV") | |
| return buffer.getvalue() | |
| def array_from_path(path: str, target_sr: int = 44100) -> Tuple[np.ndarray, int]: | |
| audio, sr = librosa.load(path, sr=target_sr, mono=False) | |
| if audio.ndim == 1: | |
| audio = audio[:, None] | |
| else: | |
| audio = audio.T | |
| return audio.astype(np.float32), sr | |