Codex commited on
Commit ·
2d8ad35
1
Parent(s): 591e827
Clarify prompt modes and diversify fallbacks
Browse files- app.py +2 -2
- src/music_generator.py +63 -14
- src/prompt_builder.py +3 -4
- src/tts.py +32 -3
- tests/test_app_logic.py +29 -3
app.py
CHANGED
|
@@ -75,7 +75,7 @@ MOODS = [
|
|
| 75 |
"futuristic and strange",
|
| 76 |
"relaxed and intimate",
|
| 77 |
]
|
| 78 |
-
PROMPT_MODES = ["
|
| 79 |
VOCAL_MODES = ["Instrumental only", "Wordless vocal texture", "Original micro-lyrics"]
|
| 80 |
DURATIONS = [10, 15, 20]
|
| 81 |
|
|
@@ -355,7 +355,7 @@ with gr.Blocks(title="Turntable Time Machine", css=CSS) as demo:
|
|
| 355 |
broadcast_language = gr.Dropdown(list(LANGUAGE_LABEL_TO_ID.keys()), value="English", label="Broadcast language", elem_classes=["compact-control"])
|
| 356 |
vocal_mode = gr.Dropdown(VOCAL_MODES, value="Instrumental only", label="Vocal mode", elem_classes=["compact-control"])
|
| 357 |
lyric_theme = gr.Dropdown(list(THEME_LABEL_TO_ID.keys()), value=list(THEME_LABEL_TO_ID.keys())[0], label="Lyric theme", visible=False, elem_classes=["compact-control"])
|
| 358 |
-
prompt_language_mode = gr.Dropdown(PROMPT_MODES, value=PROMPT_MODES[0], label="
|
| 359 |
mood = gr.Dropdown(MOODS, value=MOODS[0], label="Mood", elem_classes=["compact-control"])
|
| 360 |
audio_texture = gr.Dropdown(list(TEXTURE_LABEL_TO_ID.keys()), value=list(TEXTURE_LABEL_TO_ID.keys())[-1], label="Audio texture", elem_classes=["compact-control"])
|
| 361 |
with gr.Row():
|
|
|
|
| 75 |
"futuristic and strange",
|
| 76 |
"relaxed and intimate",
|
| 77 |
]
|
| 78 |
+
PROMPT_MODES = ["Best stability (English)", "Broadcast language", "English + broadcast language"]
|
| 79 |
VOCAL_MODES = ["Instrumental only", "Wordless vocal texture", "Original micro-lyrics"]
|
| 80 |
DURATIONS = [10, 15, 20]
|
| 81 |
|
|
|
|
| 355 |
broadcast_language = gr.Dropdown(list(LANGUAGE_LABEL_TO_ID.keys()), value="English", label="Broadcast language", elem_classes=["compact-control"])
|
| 356 |
vocal_mode = gr.Dropdown(VOCAL_MODES, value="Instrumental only", label="Vocal mode", elem_classes=["compact-control"])
|
| 357 |
lyric_theme = gr.Dropdown(list(THEME_LABEL_TO_ID.keys()), value=list(THEME_LABEL_TO_ID.keys())[0], label="Lyric theme", visible=False, elem_classes=["compact-control"])
|
| 358 |
+
prompt_language_mode = gr.Dropdown(PROMPT_MODES, value=PROMPT_MODES[0], label="Music prompt language", elem_classes=["compact-control"])
|
| 359 |
mood = gr.Dropdown(MOODS, value=MOODS[0], label="Mood", elem_classes=["compact-control"])
|
| 360 |
audio_texture = gr.Dropdown(list(TEXTURE_LABEL_TO_ID.keys()), value=list(TEXTURE_LABEL_TO_ID.keys())[-1], label="Audio texture", elem_classes=["compact-control"])
|
| 361 |
with gr.Row():
|
src/music_generator.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from pathlib import Path
|
|
|
|
| 4 |
import os
|
| 5 |
|
| 6 |
import numpy as np
|
|
@@ -34,36 +35,79 @@ def _tone(freq: float, t: np.ndarray, wave: str = "sine") -> np.ndarray:
|
|
| 34 |
return np.sin(phase).astype(np.float32)
|
| 35 |
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def generate_fallback_music(prompt: str, duration: int, bpm: int, seed: int | None, output_path: str) -> dict:
|
| 38 |
sr = 44100
|
| 39 |
-
rng =
|
| 40 |
total = int(duration * sr)
|
| 41 |
audio = np.zeros(total, dtype=np.float32)
|
| 42 |
beat = 60.0 / max(bpm, 1)
|
| 43 |
samples_per_beat = int(beat * sr)
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
t = np.arange(total) / sr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
bass_notes = [root / 2, root * 2 ** (5 / 12) / 2, root * 2 ** (7 / 12) / 2, root * 2 ** (3 / 12) / 2]
|
| 50 |
for i in range(0, total, samples_per_beat):
|
| 51 |
n = min(samples_per_beat, total - i)
|
| 52 |
local_t = np.arange(n) / sr
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
audio[i : i + n] +=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
bar = max(samples_per_beat * 4, 1)
|
| 59 |
for i in range(0, total, bar):
|
| 60 |
n = min(bar, total - i)
|
| 61 |
local_t = np.arange(n) / sr
|
| 62 |
-
chord = sum(_tone(freq, local_t,
|
| 63 |
-
audio[i : i + n] += chord * _env(n, sr, 0.
|
| 64 |
chords = chords[1:] + chords[:1]
|
| 65 |
|
| 66 |
-
hat_interval = max(samples_per_beat /
|
| 67 |
noise = rng.normal(0, 1, total).astype(np.float32)
|
| 68 |
if butter is not None and lfilter is not None:
|
| 69 |
b, a = butter(1, 7000 / (sr / 2), btype="high")
|
|
@@ -72,9 +116,14 @@ def generate_fallback_music(prompt: str, duration: int, bpm: int, seed: int | No
|
|
| 72 |
noise = (noise - np.convolve(noise, np.ones(64, dtype=np.float32) / 64, mode="same")).astype(np.float32)
|
| 73 |
for i in range(0, total, hat_interval):
|
| 74 |
n = min(int(0.045 * sr), total - i)
|
| 75 |
-
audio[i : i + n] += noise[i : i + n] * _env(n, sr, 0.001, 0.035) * 0.
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
audio += shimmer.astype(np.float32)
|
| 79 |
stereo = np.stack([audio, np.roll(audio, int(0.009 * sr))], axis=1)
|
| 80 |
save_audio(output_path, stereo, sr)
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from pathlib import Path
|
| 4 |
+
import hashlib
|
| 5 |
import os
|
| 6 |
|
| 7 |
import numpy as np
|
|
|
|
| 35 |
return np.sin(phase).astype(np.float32)
|
| 36 |
|
| 37 |
|
| 38 |
+
def _prompt_rng(prompt: str, seed: int | None) -> np.random.Generator:
|
| 39 |
+
digest = hashlib.sha256(f"{seed or 0}:{prompt}".encode("utf-8")).digest()
|
| 40 |
+
route_seed = int.from_bytes(digest[:8], "little") % (2**32)
|
| 41 |
+
return np.random.default_rng(route_seed)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
def generate_fallback_music(prompt: str, duration: int, bpm: int, seed: int | None, output_path: str) -> dict:
|
| 45 |
sr = 44100
|
| 46 |
+
rng = _prompt_rng(prompt, seed)
|
| 47 |
total = int(duration * sr)
|
| 48 |
audio = np.zeros(total, dtype=np.float32)
|
| 49 |
beat = 60.0 / max(bpm, 1)
|
| 50 |
samples_per_beat = int(beat * sr)
|
| 51 |
+
prompt_lower = prompt.lower()
|
| 52 |
+
key = int(rng.integers(0, 12))
|
| 53 |
+
root = 82.41 * (2 ** (key / 12))
|
| 54 |
+
if any(term in prompt_lower for term in ("lo-fi", "trip-hop", "folk", "bedroom")):
|
| 55 |
+
root *= 0.82
|
| 56 |
+
elif any(term in prompt_lower for term in ("hyperpop", "festival", "garage", "techno")):
|
| 57 |
+
root *= 1.18
|
| 58 |
+
|
| 59 |
+
chord_shapes = [
|
| 60 |
+
[0, 3, 7, 10],
|
| 61 |
+
[0, 4, 7, 11],
|
| 62 |
+
[0, 5, 7, 12],
|
| 63 |
+
[0, 2, 7, 9],
|
| 64 |
+
]
|
| 65 |
+
chord_shape = chord_shapes[int(rng.integers(0, len(chord_shapes)))]
|
| 66 |
+
chords = [root * 2 ** (step / 12) for step in chord_shape]
|
| 67 |
t = np.arange(total) / sr
|
| 68 |
+
beat_accent = 0.62
|
| 69 |
+
bass_wave = "square"
|
| 70 |
+
chord_wave = "saw"
|
| 71 |
+
swing = 1.0
|
| 72 |
+
hat_divider = 2
|
| 73 |
+
|
| 74 |
+
if any(term in prompt_lower for term in ("disco", "house", "club", "garage", "amapiano")):
|
| 75 |
+
beat_accent = 0.72
|
| 76 |
+
hat_divider = 2
|
| 77 |
+
if any(term in prompt_lower for term in ("lo-fi", "trip-hop", "bedroom", "folk")):
|
| 78 |
+
beat_accent = 0.42
|
| 79 |
+
bass_wave = "sine"
|
| 80 |
+
chord_wave = "sine"
|
| 81 |
+
swing = 1.18
|
| 82 |
+
if any(term in prompt_lower for term in ("techno", "hyperpop", "future", "ai-era")):
|
| 83 |
+
bass_wave = "saw"
|
| 84 |
+
chord_wave = "square"
|
| 85 |
+
hat_divider = 1
|
| 86 |
|
| 87 |
bass_notes = [root / 2, root * 2 ** (5 / 12) / 2, root * 2 ** (7 / 12) / 2, root * 2 ** (3 / 12) / 2]
|
| 88 |
for i in range(0, total, samples_per_beat):
|
| 89 |
n = min(samples_per_beat, total - i)
|
| 90 |
local_t = np.arange(n) / sr
|
| 91 |
+
beat_index = i // samples_per_beat
|
| 92 |
+
kick_freq = 48 + int(rng.integers(0, 24))
|
| 93 |
+
kick = np.sin(2 * np.pi * (kick_freq * np.exp(-local_t * 24)) * local_t) * np.exp(-local_t * 18)
|
| 94 |
+
audio[i : i + n] += kick.astype(np.float32) * beat_accent
|
| 95 |
+
bass = _tone(bass_notes[beat_index % len(bass_notes)], local_t, bass_wave)
|
| 96 |
+
bass_amount = 0.08 + float(rng.random()) * 0.08
|
| 97 |
+
audio[i : i + n] += bass * _env(n, sr, 0.005, 0.12) * bass_amount
|
| 98 |
+
if "amapiano" in prompt_lower and beat_index % 2 == 1:
|
| 99 |
+
log_hit = _tone(root * 0.74, local_t, "sine") * np.exp(-local_t * 9)
|
| 100 |
+
audio[i : i + n] += log_hit.astype(np.float32) * 0.18
|
| 101 |
|
| 102 |
bar = max(samples_per_beat * 4, 1)
|
| 103 |
for i in range(0, total, bar):
|
| 104 |
n = min(bar, total - i)
|
| 105 |
local_t = np.arange(n) / sr
|
| 106 |
+
chord = sum(_tone(freq, local_t, chord_wave) for freq in chords) / len(chords)
|
| 107 |
+
audio[i : i + n] += chord * _env(n, sr, 0.25, 0.5) * (0.06 + float(rng.random()) * 0.05)
|
| 108 |
chords = chords[1:] + chords[:1]
|
| 109 |
|
| 110 |
+
hat_interval = max(int(samples_per_beat / hat_divider * swing), 1)
|
| 111 |
noise = rng.normal(0, 1, total).astype(np.float32)
|
| 112 |
if butter is not None and lfilter is not None:
|
| 113 |
b, a = butter(1, 7000 / (sr / 2), btype="high")
|
|
|
|
| 116 |
noise = (noise - np.convolve(noise, np.ones(64, dtype=np.float32) / 64, mode="same")).astype(np.float32)
|
| 117 |
for i in range(0, total, hat_interval):
|
| 118 |
n = min(int(0.045 * sr), total - i)
|
| 119 |
+
audio[i : i + n] += noise[i : i + n] * _env(n, sr, 0.001, 0.035) * (0.035 + float(rng.random()) * 0.04)
|
| 120 |
+
|
| 121 |
+
lead_ratio = float(rng.choice([3, 4, 5, 6, 7]))
|
| 122 |
+
shimmer = _tone(root * lead_ratio, t, "sine") * 0.014 + _tone(root * (lead_ratio + 2), t, "sine") * 0.009
|
| 123 |
+
if any(term in prompt_lower for term in ("surf", "psychedelic", "vhs", "cassette")):
|
| 124 |
+
shimmer += _tone(root * 2.5, t, "saw") * 0.014
|
| 125 |
+
if "hyperpop" in prompt_lower:
|
| 126 |
+
shimmer += _tone(root * 8, t, "square") * 0.012
|
| 127 |
audio += shimmer.astype(np.float32)
|
| 128 |
stereo = np.stack([audio, np.roll(audio, int(0.009 * sr))], axis=1)
|
| 129 |
save_audio(output_path, stereo, sr)
|
src/prompt_builder.py
CHANGED
|
@@ -76,15 +76,15 @@ def build_music_prompt(
|
|
| 76 |
duration,
|
| 77 |
bpm,
|
| 78 |
language_id="en",
|
| 79 |
-
prompt_language_mode="
|
| 80 |
vocal_mode="Instrumental only",
|
| 81 |
lyrics=None,
|
| 82 |
) -> str:
|
| 83 |
english = _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
|
| 84 |
localized, _ = _localized_prompt(language_id, source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
|
| 85 |
-
if prompt_language_mode
|
| 86 |
prompt = localized
|
| 87 |
-
elif prompt_language_mode
|
| 88 |
prompt = f"{english}\n{localized}"
|
| 89 |
else:
|
| 90 |
prompt = english
|
|
@@ -159,4 +159,3 @@ def build_mixtape_card(
|
|
| 159 |
{kv}
|
| 160 |
</div>
|
| 161 |
"""
|
| 162 |
-
|
|
|
|
| 76 |
duration,
|
| 77 |
bpm,
|
| 78 |
language_id="en",
|
| 79 |
+
prompt_language_mode="Best stability (English)",
|
| 80 |
vocal_mode="Instrumental only",
|
| 81 |
lyrics=None,
|
| 82 |
) -> str:
|
| 83 |
english = _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
|
| 84 |
localized, _ = _localized_prompt(language_id, source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
|
| 85 |
+
if prompt_language_mode in ("Broadcast language", "Selected language prompt"):
|
| 86 |
prompt = localized
|
| 87 |
+
elif prompt_language_mode in ("English + broadcast language", "Bilingual prompt") and language_id != "en":
|
| 88 |
prompt = f"{english}\n{localized}"
|
| 89 |
else:
|
| 90 |
prompt = english
|
|
|
|
| 159 |
{kv}
|
| 160 |
</div>
|
| 161 |
"""
|
|
|
src/tts.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from pathlib import Path
|
|
|
|
| 4 |
import os
|
| 5 |
|
| 6 |
import numpy as np
|
|
@@ -8,6 +9,32 @@ import numpy as np
|
|
| 8 |
from .audio_utils import save_audio
|
| 9 |
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
def generate_dj_voice(
|
| 12 |
text: str,
|
| 13 |
language_id: str,
|
|
@@ -37,11 +64,13 @@ def generate_dj_voice(
|
|
| 37 |
|
| 38 |
raise RuntimeError("Kokoro wrapper hook is present, but local TTS inference is not configured.")
|
| 39 |
except Exception as exc:
|
|
|
|
| 40 |
return {
|
| 41 |
-
"path":
|
| 42 |
-
"sample_rate":
|
| 43 |
-
"status": f"
|
| 44 |
"tts_succeeded": False,
|
|
|
|
| 45 |
"model_attempted": True,
|
| 46 |
"model_id": "hexgrad/Kokoro-82M",
|
| 47 |
}
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from pathlib import Path
|
| 4 |
+
import hashlib
|
| 5 |
import os
|
| 6 |
|
| 7 |
import numpy as np
|
|
|
|
| 9 |
from .audio_utils import save_audio
|
| 10 |
|
| 11 |
|
| 12 |
+
def generate_fallback_dj_signal(text: str, output_path: str) -> dict:
|
| 13 |
+
sr = 24000
|
| 14 |
+
words = max(4, min(28, len((text or "").split())))
|
| 15 |
+
duration = min(4.0, max(1.0, words * 0.16))
|
| 16 |
+
total = int(sr * duration)
|
| 17 |
+
t = np.arange(total) / sr
|
| 18 |
+
digest = hashlib.sha256((text or "timeline radio").encode("utf-8")).digest()
|
| 19 |
+
base = 150 + digest[0]
|
| 20 |
+
carrier = np.sin(2 * np.pi * base * t) * 0.045
|
| 21 |
+
radio = np.sin(2 * np.pi * (base * 1.5) * t) * 0.025
|
| 22 |
+
envelope = 0.6 + 0.4 * np.sin(2 * np.pi * (2.2 + digest[1] / 128) * t)
|
| 23 |
+
audio = (carrier + radio) * envelope
|
| 24 |
+
for index in range(words):
|
| 25 |
+
start = int((index / words) * total)
|
| 26 |
+
stop = min(total, start + int(sr * 0.055))
|
| 27 |
+
if stop > start:
|
| 28 |
+
audio[start:stop] += np.sin(2 * np.pi * (base + 120 + (index % 5) * 38) * t[: stop - start]) * 0.055
|
| 29 |
+
save_audio(output_path, audio.astype(np.float32), sr)
|
| 30 |
+
return {
|
| 31 |
+
"path": output_path,
|
| 32 |
+
"sample_rate": sr,
|
| 33 |
+
"tts_succeeded": False,
|
| 34 |
+
"fallback_audio": True,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
def generate_dj_voice(
|
| 39 |
text: str,
|
| 40 |
language_id: str,
|
|
|
|
| 64 |
|
| 65 |
raise RuntimeError("Kokoro wrapper hook is present, but local TTS inference is not configured.")
|
| 66 |
except Exception as exc:
|
| 67 |
+
fallback = generate_fallback_dj_signal(text, output_path)
|
| 68 |
return {
|
| 69 |
+
"path": fallback["path"],
|
| 70 |
+
"sample_rate": fallback["sample_rate"],
|
| 71 |
+
"status": f"Fallback DJ radio-signal intro audio generated. Kokoro spoken TTS unavailable or unsupported here: {exc}",
|
| 72 |
"tts_succeeded": False,
|
| 73 |
+
"fallback_audio": True,
|
| 74 |
"model_attempted": True,
|
| 75 |
"model_id": "hexgrad/Kokoro-82M",
|
| 76 |
}
|
tests/test_app_logic.py
CHANGED
|
@@ -76,6 +76,12 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
|
|
| 76 |
self.assertIn(result[11], app.DURATIONS)
|
| 77 |
self.assertIn("era-card", result[14])
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
def test_music_wrapper_attempts_model_by_default_then_falls_back(self):
|
| 80 |
result = generate_music(
|
| 81 |
"Create an original music clip. No vocals. No lyrics.",
|
|
@@ -91,6 +97,25 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
|
|
| 91 |
self.assertEqual(sr, 44100)
|
| 92 |
self.assertGreater(audio.shape[0], 0)
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def test_audio_model_calls_can_be_exercised_with_test_switch(self):
|
| 95 |
os.environ["TTM_TEST_MODEL_CALLS"] = "1"
|
| 96 |
music = generate_music(
|
|
@@ -137,7 +162,7 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
|
|
| 137 |
"Clean digital master",
|
| 138 |
"nostalgic and warm",
|
| 139 |
"English",
|
| 140 |
-
"
|
| 141 |
vocal_mode,
|
| 142 |
"Night drive",
|
| 143 |
1,
|
|
@@ -147,8 +172,9 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
|
|
| 147 |
final_path, music_path, intro_path, _intro_text, lyrics_text, prompt, _card, status = result[:8]
|
| 148 |
self.assertTrue(Path(final_path).exists())
|
| 149 |
self.assertTrue(Path(music_path).exists())
|
| 150 |
-
self.
|
| 151 |
self.assertIn("ACE-Step", status)
|
|
|
|
| 152 |
audio, sr = sf.read(final_path)
|
| 153 |
self.assertEqual(sr, 44100)
|
| 154 |
self.assertGreater(audio.shape[0], 0)
|
|
@@ -171,7 +197,7 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
|
|
| 171 |
"Clean digital master",
|
| 172 |
"nostalgic and warm",
|
| 173 |
"English",
|
| 174 |
-
"
|
| 175 |
"Original micro-lyrics",
|
| 176 |
"Night drive",
|
| 177 |
1,
|
|
|
|
| 76 |
self.assertIn(result[11], app.DURATIONS)
|
| 77 |
self.assertIn("era-card", result[14])
|
| 78 |
|
| 79 |
+
def test_prompt_mode_labels_are_plain_language(self):
|
| 80 |
+
self.assertEqual(
|
| 81 |
+
app.PROMPT_MODES,
|
| 82 |
+
["Best stability (English)", "Broadcast language", "English + broadcast language"],
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
def test_music_wrapper_attempts_model_by_default_then_falls_back(self):
|
| 86 |
result = generate_music(
|
| 87 |
"Create an original music clip. No vocals. No lyrics.",
|
|
|
|
| 97 |
self.assertEqual(sr, 44100)
|
| 98 |
self.assertGreater(audio.shape[0], 0)
|
| 99 |
|
| 100 |
+
def test_fallback_music_varies_by_prompt_route(self):
|
| 101 |
+
first = generate_music(
|
| 102 |
+
"Create an original music clip. Start from 1960s Motown-inspired soul. Transform it into 2000s filtered disco house.",
|
| 103 |
+
duration=1,
|
| 104 |
+
bpm=118,
|
| 105 |
+
seed=123,
|
| 106 |
+
output_path="outputs/test_route_one.wav",
|
| 107 |
+
)
|
| 108 |
+
second = generate_music(
|
| 109 |
+
"Create an original music clip. Start from 2020s lo-fi chill. Transform it into 1970s funk.",
|
| 110 |
+
duration=1,
|
| 111 |
+
bpm=118,
|
| 112 |
+
seed=123,
|
| 113 |
+
output_path="outputs/test_route_two.wav",
|
| 114 |
+
)
|
| 115 |
+
audio_one, _ = sf.read(first["path"])
|
| 116 |
+
audio_two, _ = sf.read(second["path"])
|
| 117 |
+
self.assertGreater(float(abs(audio_one - audio_two).mean()), 0.001)
|
| 118 |
+
|
| 119 |
def test_audio_model_calls_can_be_exercised_with_test_switch(self):
|
| 120 |
os.environ["TTM_TEST_MODEL_CALLS"] = "1"
|
| 121 |
music = generate_music(
|
|
|
|
| 162 |
"Clean digital master",
|
| 163 |
"nostalgic and warm",
|
| 164 |
"English",
|
| 165 |
+
"Best stability (English)",
|
| 166 |
vocal_mode,
|
| 167 |
"Night drive",
|
| 168 |
1,
|
|
|
|
| 172 |
final_path, music_path, intro_path, _intro_text, lyrics_text, prompt, _card, status = result[:8]
|
| 173 |
self.assertTrue(Path(final_path).exists())
|
| 174 |
self.assertTrue(Path(music_path).exists())
|
| 175 |
+
self.assertTrue(Path(intro_path).exists())
|
| 176 |
self.assertIn("ACE-Step", status)
|
| 177 |
+
self.assertIn("Fallback DJ radio-signal intro audio generated", status)
|
| 178 |
audio, sr = sf.read(final_path)
|
| 179 |
self.assertEqual(sr, 44100)
|
| 180 |
self.assertGreater(audio.shape[0], 0)
|
|
|
|
| 197 |
"Clean digital master",
|
| 198 |
"nostalgic and warm",
|
| 199 |
"English",
|
| 200 |
+
"Best stability (English)",
|
| 201 |
"Original micro-lyrics",
|
| 202 |
"Night drive",
|
| 203 |
1,
|