CodeAntidote commited on
Commit
b0e85c9
·
verified ·
1 Parent(s): 4e5c824

Updating it

Browse files
Files changed (1) hide show
  1. audio_engine.py +0 -183
audio_engine.py DELETED
@@ -1,183 +0,0 @@
1
- """Generate a short song with sung vocals from the edited rhyme."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- import os
7
- import re
8
- import time
9
-
10
- # Import spaces before torch/diffusers so ZeroGPU can install its CUDA shim.
11
- try:
12
- import spaces
13
- except ModuleNotFoundError:
14
- if os.environ.get("SPACE_ID"):
15
- raise # A Space needs the ZeroGPU runtime and its spaces module.
16
-
17
- # Local CPU machines can still open the form and write rhymes.
18
- class _LocalSpaces:
19
- @staticmethod
20
- def GPU(**_kwargs):
21
- return lambda fn: fn
22
-
23
- spaces = _LocalSpaces()
24
-
25
- import numpy as np
26
- from rhyme_engine import LANGUAGES, validate_lyric_for_audio
27
-
28
- logging.basicConfig(level=logging.INFO)
29
- logger = logging.getLogger("kids_rhyme.audio")
30
-
31
-
32
- MODEL_ID = "ACE-Step/acestep-v15-xl-turbo-diffusers"
33
- VOCAL_LANGUAGE = {key: value["code"] for key, value in LANGUAGES.items()}
34
- # Released ACE-Step checkpoints use 48 kHz; prefer the pipeline's own value.
35
- # https://huggingface.co/docs/diffusers/api/pipelines/ace_step
36
- FALLBACK_SAMPLE_RATE = 48000
37
-
38
- # ZeroGPU expects the model to be placed on CUDA at module import time. Its
39
- # CUDA shim makes this possible even before a real GPU has been allocated.
40
- # On a local machine without CUDA, lyric writing still works and the song
41
- # button explains what is needed.
42
- _pipe = None
43
- _load_error = None
44
- try:
45
- import torch
46
- except ModuleNotFoundError:
47
- torch = None
48
- if torch is not None and torch.cuda.is_available():
49
- try:
50
- from diffusers import AceStepPipeline
51
-
52
- _pipe = AceStepPipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16).to("cuda")
53
- logger.info("Singing model loaded: %s", MODEL_ID)
54
- except Exception as exc:
55
- _load_error = exc
56
- logger.exception("Singing model failed to load")
57
-
58
-
59
- def _require_pipeline():
60
- if _pipe is None:
61
- if _load_error is not None:
62
- raise RuntimeError("The singing model could not load. Check the Space logs.") from _load_error
63
- raise RuntimeError("Singing needs GPU hardware. Set the Space hardware to ZeroGPU.")
64
- return _pipe
65
-
66
-
67
- def _sample_rate() -> int:
68
- rate = getattr(_require_pipeline(), "sample_rate", None)
69
- if rate is None:
70
- logger.warning("Pipeline has no sample_rate; using %s Hz for %s", FALLBACK_SAMPLE_RATE, MODEL_ID)
71
- rate = FALLBACK_SAMPLE_RATE
72
- if isinstance(rate, (bool, np.bool_)) or not isinstance(rate, (int, np.integer)) or rate <= 0:
73
- raise ValueError("The pipeline returned an invalid sample rate.")
74
- return int(rate)
75
-
76
-
77
- def _song_lyrics(text: str) -> str:
78
- """Preserve every edited line and mark its two parts for the song model."""
79
- text = text.replace("\r\n", "\n").replace("\r", "\n")
80
- stanzas = [part.strip() for part in re.split(r"\n\s*\n", text) if part.strip()]
81
- if len(stanzas) >= 2:
82
- first = stanzas[0]
83
- second = "\n".join(stanzas[1:])
84
- else:
85
- lines = [line.strip() for line in text.splitlines() if line.strip()]
86
- if len(lines) < 2:
87
- raise ValueError("Add at least two short lines to sing.")
88
- midpoint = (len(lines) + 1) // 2
89
- first, second = "\n".join(lines[:midpoint]), "\n".join(lines[midpoint:])
90
- return f"[verse]\n{first}\n[chorus]\n{second}"
91
-
92
-
93
- def _song_settings(text: str, language: str, mood: str,
94
- theme: str = "", theme_prompt: str = "") -> dict:
95
- text = validate_lyric_for_audio(text)
96
- if language not in VOCAL_LANGUAGE or mood not in ("Bouncy", "Calm"):
97
- raise ValueError("Write a rhyme first to select its language and music mood.")
98
- if mood == "Calm":
99
- prompt = (
100
- "Gentle original children's lullaby, a clear warm voice SINGING a simple "
101
- "memorable melody in the language of the lyrics. Soft piano, glockenspiel, "
102
- "light acoustic guitar, slow swaying rhythm. Vocal-forward mix. "
103
- "Sing the supplied lyrics; no spoken words or narration."
104
- )
105
- bpm = 82
106
- else:
107
- prompt = (
108
- "Playful original children's sing-along, a clear cheerful voice SINGING "
109
- "a simple catchy melody in the language of the lyrics. Ukulele, handclaps, "
110
- "toy piano, bright steady beat. Vocal-forward mix. "
111
- "Sing the supplied lyrics; no spoken words or narration."
112
- )
113
- bpm = 112
114
- language_name = LANGUAGES[language]["name"]
115
- prompt = f"Sing all vocals in {language_name}. " + prompt
116
- if theme:
117
- prompt += f" Song theme: {theme}."
118
- if theme_prompt:
119
- prompt += f" Topic: {theme_prompt}."
120
- return dict(
121
- prompt=prompt,
122
- lyrics=_song_lyrics(text),
123
- vocal_language=VOCAL_LANGUAGE[language],
124
- # Give longer rhymes time for every line: 40 seconds for 8 lines,
125
- # up to 56 seconds for 12 lines when all ten words are supplied.
126
- audio_duration=min(56.0, 40.0 + 4.0 * max(0, sum(bool(line.strip()) for line in text.splitlines()) - 8)),
127
- num_inference_steps=8,
128
- bpm=bpm,
129
- task_type="text2music",
130
- output_type="np",
131
- )
132
-
133
-
134
- def _polish(wave: np.ndarray, sr: int) -> np.ndarray:
135
- """Peak-normalize and fade the tail; this cannot recover omitted lyrics."""
136
- wave = np.array(wave, dtype=np.float32, copy=True)
137
- peak = float(np.max(np.abs(wave)))
138
- if peak > 0:
139
- wave *= 0.89 / peak
140
- n = min(int(0.6 * sr), wave.shape[1] // 4)
141
- if n > 0:
142
- wave[:, -n:] *= np.linspace(1.0, 0.0, n, dtype=np.float32)
143
- return wave
144
-
145
-
146
- def generate_song(settings: dict) -> tuple[int, np.ndarray]:
147
- """Inference core without a UI decorator; caller must provide GPU access.
148
-
149
- A future API server needs CUDA hardware or the ZeroGPU wrapper below.
150
- This function is not a CPU inference path.
151
- """
152
- pipe = _require_pipeline()
153
- sr = _sample_rate()
154
- started = time.monotonic()
155
- result = pipe(**settings)
156
- # ACE-Step returns [batch, channels, samples]; Gradio takes [samples, channels].
157
- waveform = np.asarray(result.audios[0], dtype=np.float32)
158
- if waveform.ndim != 2 or waveform.shape[0] not in (1, 2) or waveform.shape[1] < sr:
159
- raise ValueError(f"Unexpected audio shape {waveform.shape}")
160
- if not np.isfinite(waveform).all() or np.max(np.abs(waveform)) < 0.005:
161
- raise ValueError("Model returned empty or invalid audio")
162
- waveform = _polish(waveform, sr)
163
- pcm = (np.clip(waveform.T, -1.0, 1.0) * 32767).astype(np.int16)
164
- logger.info("Generated %.1fs of audio at %s Hz in %.1fs", len(pcm) / sr, sr, time.monotonic() - started)
165
- return sr, pcm
166
-
167
-
168
- @spaces.GPU(duration=120) # Keep this budget until real deployed runs have been timed.
169
- def _generate_on_gpu(settings: dict) -> tuple[int, np.ndarray]:
170
- return generate_song(settings)
171
-
172
-
173
- def make_sung_song(text: str, language: str, mood: str,
174
- theme: str = "", theme_prompt: str = "") -> tuple[tuple[int, np.ndarray], str]:
175
- """Validate before requesting a GPU; retain the app's five-argument API."""
176
- settings = _song_settings(text, language, mood, theme, theme_prompt)
177
- _require_pipeline()
178
- try:
179
- audio = _generate_on_gpu(settings)
180
- except Exception as exc:
181
- logger.exception("Song generation failed")
182
- raise RuntimeError("Singing failed this time. Please try again.") from exc
183
- return audio, "Your sung song is ready to listen to and download."