import hashlib import uuid import wave from pathlib import Path import numpy as np from .base import GenerationRequest, GenerationResult, MusicGenerationProvider SAMPLE_RATE = 32000 # A few simple chord progressions (root frequencies in Hz) so different # prompts audibly differ instead of producing the same tone every time. _PROGRESSIONS = [ [261.63, 329.63, 392.00, 261.63], # C major-ish [220.00, 261.63, 329.63, 220.00], # A minor-ish [196.00, 246.94, 293.66, 196.00], # G-ish [174.61, 220.00, 261.63, 174.61], # F-ish ] class MockMusicGenerationProvider(MusicGenerationProvider): """Fast, dependency-light stand-in for a real model. Synthesizes a short procedurally-generated audio clip (chord pad + simple melody, with a gentle "vocal-ish" hum layer when mode is "vocals") so the rest of the product — jobs, storage, playback, library, feed — can be built and verified before wiring a real, much slower/heavier local model. """ def generate(self, request: GenerationRequest, output_dir: Path) -> GenerationResult: seed = int(hashlib.sha256(request.prompt.encode()).hexdigest(), 16) % (2**32) rng = np.random.default_rng(seed) progression = _PROGRESSIONS[seed % len(_PROGRESSIONS)] duration = max(5, min(request.duration_seconds, 30)) t = np.linspace(0, duration, int(SAMPLE_RATE * duration), endpoint=False) audio = np.zeros_like(t) bar_len = duration / len(progression) for i, freq in enumerate(progression): start = int(i * bar_len * SAMPLE_RATE) end = int((i + 1) * bar_len * SAMPLE_RATE) seg_t = t[start:end] - t[start] pad = ( 0.18 * np.sin(2 * np.pi * freq * seg_t) + 0.12 * np.sin(2 * np.pi * freq * 1.5 * seg_t) + 0.08 * np.sin(2 * np.pi * freq * 2 * seg_t) ) envelope = np.clip(np.minimum(seg_t, bar_len - seg_t) * 4, 0, 1) audio[start:end] += pad * envelope # simple plucked melody riding on top, tempo/notes derived from the prompt note_len = 60 / (90 + seed % 60) n_notes = int(duration / note_len) for i in range(n_notes): start = int(i * note_len * SAMPLE_RATE) end = min(int((i + 1) * note_len * SAMPLE_RATE), len(t)) if end <= start: continue freq = progression[i % len(progression)] * rng.choice([1, 1.25, 1.5, 2]) seg_t = t[start:end] - t[start] note = 0.1 * np.sin(2 * np.pi * freq * seg_t) * np.exp(-3 * seg_t) audio[start:end] += note if request.mode == "vocals" and request.lyrics: words = request.lyrics.split() if words: syl_len = duration / max(len(words), 1) for i, _word in enumerate(words): start = int(i * syl_len * SAMPLE_RATE) end = min(int((i + 1) * syl_len * SAMPLE_RATE), len(t)) if end <= start: continue base = progression[i % len(progression)] * 2 vib = 1 + 0.01 * np.sin(2 * np.pi * 5 * (t[start:end] - t[start])) seg_t = t[start:end] - t[start] voice = 0.14 * np.sin(2 * np.pi * base * vib * seg_t) env = np.clip(np.minimum(seg_t, syl_len - seg_t) * 10, 0, 1) audio[start:end] += voice * env audio = audio / max(np.abs(audio).max(), 1e-6) * 0.9 pcm = (audio * 32767).astype(np.int16) output_dir.mkdir(parents=True, exist_ok=True) filename = f"{uuid.uuid4().hex}.wav" path = output_dir / filename with wave.open(str(path), "wb") as f: f.setnchannels(1) f.setsampwidth(2) f.setframerate(SAMPLE_RATE) f.writeframes(pcm.tobytes()) return GenerationResult(audio_path=path, duration_seconds=duration, sample_rate=SAMPLE_RATE)