seawolf2357 commited on
Commit
23535f7
ยท
verified ยท
1 Parent(s): ed6df38

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +397 -992
app.py CHANGED
@@ -1,26 +1,81 @@
 
 
 
1
  import gradio as gr
 
 
2
  import requests
3
  import json
4
- import os
5
  from groq import Groq
6
 
7
- # Hugging Face Spaces GPU ์ง€์› (ZeroGPU ํ˜ธํ™˜)
8
- try:
9
- import spaces
10
- SPACES_AVAILABLE = True
11
- except ImportError:
12
- SPACES_AVAILABLE = False
13
- # Dummy decorator for local development
14
- class spaces:
15
- @staticmethod
16
- def GPU(duration=60):
17
- def decorator(func):
18
- return func
19
- return decorator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  # ============================================================
22
- # ๐ŸŽต SOMA Music Studio - HeartMuLa Optimized Edition
23
- # MiniMax Music 2.5 + HeartMuLa Style Guide + Comic Classic Theme
24
  # ============================================================
25
 
26
  # HeartMuLa ๊ถŒ์žฅ ๊ตฌ์กฐ ํƒœ๊ทธ (๊ณต์‹ ๋ฌธ์„œ ๊ธฐ๋ฐ˜)
@@ -29,7 +84,7 @@ STRUCTURE_TAGS = [
29
  "[Interlude]", "[Hook]", "[Outro]", "[Inst]", "[Solo]"
30
  ]
31
 
32
- # HeartMuLa ์Šคํƒ€์ผ ํƒœ๊ทธ ๊ฐ€์ด๋“œ (์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ)
33
  HEARTMULA_TAG_GUIDE = """
34
  ## ๐ŸŽผ HeartMuLa Tag Format Guide
35
 
@@ -48,47 +103,9 @@ drums,energetic,rock,electric_guitar,powerful
48
  **Mood:** happy,sad,romantic,energetic,calm,melancholic,uplifting,dark,dreamy,nostalgic,powerful,peaceful
49
  **Genre:** pop,rock,jazz,classical,electronic,folk,blues,r&b,hip_hop,disco,ballad,cinematic
50
  **Tempo:** fast,slow,moderate,upbeat,relaxed
51
- **Occasion:** wedding,party,meditation,workout,study,sleep,travel
52
-
53
- ### EXAMPLE COMBINATIONS:
54
- - K-Pop Dance: `synthesizer,drums,energetic,pop,upbeat,powerful`
55
- - Jazz Ballad: `piano,saxophone,romantic,jazz,slow,dreamy`
56
- - Epic Cinematic: `strings,orchestra,powerful,cinematic,dramatic,epic`
57
- - Lo-Fi Chill: `piano,guitar,calm,lo_fi,relaxed,dreamy`
58
  """
59
 
60
- # MiniMax Music 2.0 ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๊ฐ€์ด๋“œ (๊ฐ•ํ™”)
61
- MINIMAX_MUSIC_GUIDE = """
62
- ## MiniMax Music 2.5 Core Capabilities:
63
-
64
- ### 1. DYNAMIC VOCALS - Mastery Over Diverse Singing Styles
65
- - Human-like vocal timbre with professional singing techniques
66
- - Precise control over phrasing, rhythm, and breath
67
- - One voice can switch between multiple styles
68
- - Supports: Pop, Jazz, Blues, Rock, Folk, Electronic, Urban, Disco
69
- - Special modes: Male-female duets, A Cappella (pure vocals)
70
-
71
- ### 2. CATCHY MELODIES & INSTRUMENT CONTROL
72
- - Structurally complete songs: Verse โ†’ Chorus โ†’ Bridge (up to 5 minutes)
73
- - Memorable, instantly captivating melodies
74
- - Independent control of individual instruments
75
- - Instruments: Piano, Guitar, Bass, Drums, Saxophone, Trumpet, Synths, Strings
76
-
77
- ### 3. PROFESSIONAL-GRADE AUDIO
78
- - Enhanced vocal track texture
79
- - Spatial presence of instruments
80
- - Immersive listening experience
81
- - Film-grade monologue soundtracks
82
-
83
- ### 4. PROMPT WRITING BEST PRACTICES (HeartMuLa Optimized)
84
- - Use specific instrument names
85
- - Describe vocal emotions precisely
86
- - Specify singing techniques (breathy, powerful, smooth, raspy)
87
- - Use tempo (BPM), key when needed
88
- - Tags should be comma-separated WITHOUT spaces
89
- """
90
-
91
- # HeartMuLa ๊ถŒ์žฅ ๊ฐ€์‚ฌ ๊ตฌ์กฐ ์˜ˆ์‹œ
92
  HEARTMULA_LYRICS_STRUCTURE = """
93
  ## ๐Ÿ“ HeartMuLa Recommended Lyrics Structure
94
 
@@ -99,8 +116,6 @@ HEARTMULA_LYRICS_STRUCTURE = """
99
  [Verse]
100
  First verse lyrics here
101
  Second line of first verse
102
- Third line continues story
103
- Fourth line builds emotion
104
 
105
  [Prechorus]
106
  Building tension here
@@ -109,102 +124,95 @@ Leading to the chorus
109
  [Chorus]
110
  Main hook and memorable melody
111
  Most important part of song
112
- Repeat this section 2-3 times
113
- Make it singable and catchy
114
 
115
  [Verse]
116
  Second verse develops story
117
- New information revealed
118
- Emotional progression continues
119
- Building toward bridge
120
 
121
  [Bridge]
122
  Contrast section here
123
  Different melody or perspective
124
- Emotional peak moment
125
 
126
  [Chorus]
127
  Main hook repeated
128
- With possible variations
129
- Final emotional release
130
 
131
  [Outro]
132
  Closing the song
133
- Fading or resolving
134
  ```
135
 
136
  ### KEY RULES:
137
  1. [Chorus] appears at least 2-3 times
138
  2. [Prechorus] builds tension before [Chorus]
139
  3. [Bridge] provides contrast before final [Chorus]
140
- 4. Each [Verse] should progress the story
141
- 5. [Intro] and [Outro] frame the song
142
  """
143
 
144
- # ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ (HeartMuLa + MiniMax ์ตœ์ ํ™”)
145
- EXAMPLE_PROMPTS = {
146
- "๐ŸŽค A Cappella (์•„์นดํŽ ๋ผ)": {
147
- "prompt": "A cappella arrangement with pure vocal harmonies, no instrumental accompaniment. Features a lead soprano voice with rich layered backing vocals creating lush harmonies. Gentle humming bass line, rhythmic vocal percussion, and ethereal 'ooh' and 'aah' pads. The vocals blend seamlessly with precise tuning and warm reverb, creating a refreshing, meditative atmosphere. 70 BPM, peaceful and soothing mood.",
148
- "tags": "acappella,vocals,harmony,peaceful,soothing,choir",
149
- "description": "์ˆœ์ˆ˜ ๋ณด์ปฌ๋งŒ์œผ๋กœ ํ’๋ถ€ํ•œ ๋ฉœ๋กœ๋””"
150
- },
151
- "๐Ÿ‘ฅ Group Harmony (๊ทธ๋ฃน ํ•˜๋ชจ๋‹ˆ)": {
152
- "prompt": "Powerful group vocal anthem featuring a lead female voice with layered choir harmonies. Rich unison sections build to explosive harmonic splits in the chorus. Features call-and-response patterns, anthemic 'oh-oh-oh' chants, and soaring group harmonies. Modern pop production with punchy drums, driving bass, synth hooks, and brass stabs. 118 BPM, empowering and triumphant energy.",
153
- "tags": "choir,harmony,pop,drums,bass,synthesizer,brass,powerful,anthem",
154
- "description": "ํŒŒ์›Œํ’€ํ•œ ๊ทธ๋ฃน ๋ณด์ปฌ ์•ค์ธ"
155
- },
156
- "๐ŸŽท Jazz Duet (์žฌ์ฆˆ ๋“€์—ฃ)": {
157
- "prompt": "Intimate jazz duet featuring conversational interplay between a warm male baritone and a silky female alto voice. Dynamic intensity variations with seamless transitions between lead vocals. Accompanied by brushed jazz drums, walking upright bass, and gentle piano comping. Saxophone solo in the bridge. 95 BPM, late-night jazz club atmosphere with warm analog sound.",
158
- "tags": "jazz,piano,bass,drums,saxophone,romantic,intimate,duet",
159
- "description": "๋‚จ๋…€ ๋ณด์ปฌ์˜ ์žฌ์ฆˆ ๋“€์—ฃ"
160
- },
161
- "๐ŸŽธ Multi-Style (๋ฉ€ํ‹ฐ์Šคํƒ€์ผ)": {
162
- "prompt": "Showcase track demonstrating one female voice transitioning through three distinct styles: Starting with energetic Jump Blues featuring powerful belting and brass stabs, transitioning to aggressive Rock with distorted guitars and raspy vocals, finally morphing into sleek Electronic with auto-tuned vocals and pulsing synths. 120 BPM with tempo shifts between sections.",
163
- "tags": "blues,rock,electronic,guitar,synthesizer,brass,dynamic,powerful",
164
- "description": "์Šคํƒ€์ผ ์ „ํ™˜ ์‡ผ์ผ€์ด์Šค"
165
- },
166
- "๐ŸŒƒ Urban Chill (์–ด๋ฐ˜ ์น )": {
167
- "prompt": "Contemporary urban R&B track with a cool, laid-back vibe. Features a smooth male vocal with subtle Auto-Tune enhancement and breathy delivery. Trap-influenced 808 bass, crisp hi-hats with intricate patterns, ambient synth pads, and soft piano chords. Spacious production with heavy reverb and delay. 85 BPM, modern and sophisticated sound.",
168
- "tags": "r&b,808,synthesizer,piano,chill,urban,modern,smooth",
169
- "description": "์–ด๋ฐ˜ R&B ๋ฌด๋“œ"
170
- },
171
- "๐ŸŽน Jazz Club (์žฌ์ฆˆ ํด๋Ÿฝ)": {
172
- "prompt": "Live jazz ensemble performance capturing the essence of Blue Note club. Instruments enter in perfect sequence: brushed drums set the groove, walking bass joins in, piano adds sophisticated chord voicings, then saxophone takes the melody. Trumpet and trombone provide punchy brass accents. Extended saxophone solo with bebop-style improvisation. 140 BPM swing feel.",
173
- "tags": "jazz,piano,bass,drums,saxophone,trumpet,trombone,live,swing",
174
- "description": "๋ผ์ด๋ธŒ ์žฌ์ฆˆ ํด๋Ÿฝ"
175
- },
176
- "๐Ÿชฉ Retro Disco (๋ ˆํŠธ๋กœ ๋””์Šค์ฝ”)": {
177
- "prompt": "Vibrant disco track channeling the golden age of 80s dance music. Features a powerful female diva vocal with dynamic range and soulful ad-libs. Classic instrumentation: four-on-the-floor kick drum, funky slap bass, rhythmic guitar scratches, lush string arrangements, and bright brass stabs. Warm analog tape saturation. 120 BPM, euphoric and nostalgic energy.",
178
- "tags": "disco,bass,guitar,strings,brass,drums,retro,funky,dance",
179
- "description": "80๋…„๋Œ€ ๋””์Šค์ฝ”"
180
- },
181
- "๐ŸŽฌ Film Score (์˜ํ™” ์Šค์ฝ”์–ด)": {
182
- "prompt": "Cinematic monologue soundtrack with layered emotional progression. A contemplative male voice delivers poetic narration over evolving orchestral arrangement. Begins with solo piano and soft strings, gradually building with French horn and cello. Atmospheric sound design with ocean waves and distant thunder. 60 BPM, deeply moving and introspective.",
183
- "tags": "orchestral,piano,strings,cello,horn,cinematic,emotional,dramatic",
184
- "description": "์‹œ๋„ค๋งˆํ‹ฑ ์Šค์ฝ”์–ด"
185
- },
186
- "๐ŸŽต K-Pop Dance (K-Pop ๋Œ„์Šค)": {
187
- "prompt": "High-energy K-Pop dance track with a bright, clear female vocal and polished production. Catchy hook melody that's instantly memorable. Driving beat with punchy kicks, snappy snares, and intricate hi-hat programming. Layered synth hooks, powerful brass hits, and EDM-style buildups to explosive drops. 128 BPM, confident and empowering energy.",
188
- "tags": "kpop,synthesizer,drums,bass,brass,electronic,energetic,dance,pop",
189
- "description": "๊ณ ์—๋„ˆ์ง€ K-Pop"
190
- },
191
- "๐ŸŽป Orchestral Ballad (์˜ค์ผ€์ŠคํŠธ๋ผ ๋ฐœ๋ผ๋“œ)": {
192
- "prompt": "Sweeping orchestral ballad with an emotional female soprano voice. Begins intimately with solo piano, gradually introducing strings section - first violins, then violas and cellos. French horn provides warm counter-melodies. Builds to a full orchestral climax with timpani rolls and brass fanfares. 65 BPM, epic yet intimate.",
193
- "tags": "orchestral,piano,violin,cello,horn,strings,ballad,emotional,epic",
194
- "description": "์˜ค์ผ€์ŠคํŠธ๋ผ ๋ฐœ๋ผ๋“œ"
195
- },
196
- "๐Ÿ”ฅ HeartMuLa Default (๊ธฐ๋ณธ ์˜ˆ์‹œ)": {
197
- "prompt": "Uplifting pop song with piano and synthesizer leads, happy and romantic mood. Features a clear female vocal with emotional delivery, supported by gentle drums and warm bass. Catchy melody in the chorus with memorable hooks. Clean production with balanced mix. 110 BPM, wedding-appropriate joyful atmosphere.",
198
- "tags": "piano,happy,wedding,synthesizer,romantic",
199
- "description": "HeartMuLa ๊ณต์‹ ์˜ˆ์‹œ"
200
- }
 
201
  }
202
 
203
- # SOMA ์—์ด์ „ํŠธ - ๊ฐ€์‚ฌ ์ƒ์„ฑ (HeartMuLa ์ตœ์ ํ™”)
204
- LYRICS_AGENTS = {
205
- "lyricist": f"""You are a master lyricist optimized for HeartMuLa and MiniMax Music generation.
206
 
207
- {MINIMAX_MUSIC_GUIDE}
 
 
 
 
 
208
 
209
  {HEARTMULA_LYRICS_STRUCTURE}
210
 
@@ -212,7 +220,7 @@ Your task: Create powerful, memorable lyrics with OPTIMAL HeartMuLa tag placemen
212
 
213
  CRITICAL RULES:
214
  1. Use HeartMuLa structure tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
215
- 2. [Prechorus] (not [Pre Chorus]) - HeartMuLa format
216
  3. Ensure [Chorus] is the most memorable, singable part
217
  4. Include [Prechorus] to build tension before [Chorus]
218
  5. Add [Bridge] for emotional contrast
@@ -220,7 +228,7 @@ CRITICAL RULES:
220
 
221
  Write lyrics that create the BEST POSSIBLE foundation for high-quality music generation.""",
222
 
223
- "producer": f"""You are a music producer specializing in song structure optimization for AI music generation.
224
 
225
  {HEARTMULA_LYRICS_STRUCTURE}
226
 
@@ -228,19 +236,16 @@ Your task: Analyze and OPTIMIZE the tag structure for MAXIMUM musical impact.
228
 
229
  CRITICAL OPTIMIZATION RULES:
230
  1. Use HeartMuLa tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
231
- 2. Verify the structure follows genre conventions
232
- 3. Ensure proper tag sequence (Verseโ†’Prechorusโ†’Chorus)
233
- 4. Check that [Chorus] appears at least 2-3 times
234
- 5. Verify [Bridge] provides contrast before final chorus
235
- 6. Balance repetition and variation
236
- 7. Ensure song length is appropriate (8-12 sections)
237
 
238
  Output the restructured lyrics with OPTIMAL HeartMuLa tag placement.""",
239
 
240
  "emotion_director": f"""You are an emotion director for music production.
241
 
242
- {HEARTMULA_LYRICS_STRUCTURE}
243
-
244
  Your task: Enhance emotional impact through STRATEGIC tag content.
245
 
246
  EMOTIONAL MAPPING BY TAG:
@@ -249,7 +254,6 @@ EMOTIONAL MAPPING BY TAG:
249
  - [Prechorus]: Rising tension, excitement
250
  - [Chorus]: Peak emotion, catharsis
251
  - [Bridge]: Vulnerability, reflection, contrast
252
- - [Interlude]: Breathing space, transition
253
  - [Outro]: Resolution, lingering feeling
254
 
255
  OPTIMIZATION RULES:
@@ -257,13 +261,10 @@ OPTIMIZATION RULES:
257
  2. [Chorus] must deliver the strongest emotional punch
258
  3. [Bridge] should offer new emotional perspective
259
  4. [Prechorus] should create anticipation for [Chorus]
260
- 5. Ensure dynamic contrast between sections
261
 
262
  Enhance the lyrics for MAXIMUM emotional resonance.""",
263
 
264
- "final_editor": f"""You are the final editor for HeartMuLa/MiniMax Music production.
265
-
266
- {HEARTMULA_LYRICS_STRUCTURE}
267
 
268
  Your task: Output PERFECTLY FORMATTED, production-ready lyrics.
269
 
@@ -271,96 +272,19 @@ CRITICAL OUTPUT RULES:
271
  1. Output ONLY the actual lyrics with structure tags
272
  2. Use HeartMuLa tags EXACTLY:
273
  [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
274
- 3. DO NOT include English translations in parentheses
275
- 4. DO NOT include explanations, descriptions, or markdown
276
- 5. DO NOT include lines starting with * or >
277
- 6. For [Inst] or [Solo] sections, write ONLY the tag
278
- 7. Ensure MINIMUM 6-8 different sections
279
- 8. Verify [Chorus] appears at least 2 times
280
-
281
- CORRECT FORMAT EXAMPLE:
282
- [Intro]
283
-
284
- [Verse]
285
- ์ƒˆ๋ฒฝ ์•ˆ๊ฐœ ์†์— ์ˆจ์€ ์šฐ๋ฆฌ
286
- ์ฐจ๊ฐ€์šด ๋ฐ”๋žŒ๋„ ์šฐ๋ฆฌ ๋ฐœ๊ฑธ์Œ์— ๋ฌด๋ฆŽ ๊ฟ‡์–ด
287
-
288
- [Prechorus]
289
- ์†์„ ์žก๊ณ  ๋น›์„ ๊บผ๋‚ด
290
-
291
- [Chorus]
292
- ์šฐ๋ฆฐ ๋น›๋‚˜๋Š” ๋ณ„์ด ๋˜์–ด
293
- ์šฐ๋ฆฌ ๋ชฉ์†Œ๋ฆฌ๋กœ ์„ธ์ƒ์„ ๋’ค์ง‘์–ด
294
- Yeah yeah yeah
295
-
296
- [Verse]
297
- DNA์— ์ƒˆ๊ฒจ์ง„ ์ฐฝ์กฐ๋Š” ๋ฉˆ์ถ”์ง€ ์•Š์•„
298
-
299
- [Prechorus]
300
- ๋ˆˆ๋ถ€์‹  ํŒŒ๋„๋ฅผ ํƒ€๊ณ 
301
-
302
- [Chorus]
303
- ์šฐ๋ฆฐ ๋น›๋‚˜๋Š” ๋ณ„์ด ๋˜์–ด
304
- ์šฐ๋ฆฌ ๋ชฉ์†Œ๋ฆฌ๋กœ ์„ธ์ƒ์„ ๋’ค์ง‘์–ด
305
-
306
- [Bridge]
307
- ๋ฌด์ง€๊ฐœ๊ฐ€ ํ๋ฅด๋Š” ๋ฐค
308
- ์šฐ๋ฆฌ์˜ ๊ฟˆ์€ ๋ถˆ๋ฉธ
309
-
310
- [Chorus]
311
- ์šฐ๋ฆฐ ๋น›๋‚˜๋Š” ๋ณ„์ด ๋˜์–ด
312
- ์ƒˆ๋ฒฝ์„ ๊นจ์›Œ
313
-
314
- [Outro]
315
- ๋‚ด๊ฐ€ ๋งŒ๋“  ๋…ธ๋ž˜ ์˜์›ํžˆ ํƒ€์˜ค๋ฅธ๋‹ค
316
 
317
  OUTPUT ONLY CLEAN LYRICS WITH OPTIMAL TAG STRUCTURE."""
318
  }
319
 
320
- # SOMA ์—์ด์ „ํŠธ - ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• (HeartMuLa ์ตœ์ ํ™”)
321
- PROMPT_AGENTS = {
322
- "genre_specialist": f"""You are a music genre specialist for HeartMuLa and MiniMax Music.
323
-
324
- {MINIMAX_MUSIC_GUIDE}
325
-
326
- Analyze the input and identify:
327
- - Main genre and sub-genres
328
- - Era influences and regional styles
329
- - Specific production techniques for the genre
330
- - Instrument combinations that work best
331
-
332
- Output detailed genre characteristics optimized for music generation.""",
333
-
334
- "sound_designer": """You are a sound designer for HeartMuLa and MiniMax Music.
335
-
336
- Define the complete sonic palette:
337
- - Specific instruments: Piano, Guitar (acoustic/electric), Bass (upright/electric/808), Drums (acoustic/electronic/brushed), Synths (pad/lead/bass), Brass (saxophone/trumpet/trombone), Strings (violin/viola/cello)
338
- - Drum patterns: four-on-the-floor, trap hi-hats, brushed jazz, etc.
339
- - Bass characteristics: walking bass, 808 sub-bass, slap bass, etc.
340
- - Atmospheric elements: reverb type, delay, spatial width
341
- - Production style: analog warmth, modern crisp, lo-fi, etc.
342
-
343
- Be extremely specific - both models can control individual instruments.""",
344
-
345
- "vocal_director": """You are a vocal director for HeartMuLa and MiniMax Music.
346
-
347
- Both models excel at:
348
- - Human-like vocal timbre
349
- - Multiple singing styles from one voice
350
- - Male-female duets with conversational interplay
351
- - A Cappella with layered harmonies
352
- - Group/Choir with rich harmonic layers
353
-
354
- Define:
355
- - Voice type: male/female, age range, tone (warm/bright/husky/clear)
356
- - Singing technique: belting, falsetto, breathy, raspy, smooth
357
- - Vocal processing: reverb, delay, layers
358
- - Delivery style: confident, vulnerable, aggressive, intimate
359
- - For duets: describe each voice and their interaction
360
- - For A Cappella: describe harmony parts
361
- - For Group/Choir: describe layered vocals, unison sections""",
362
-
363
- "tag_generator": f"""You are a style tag generator for HeartMuLa.
364
 
365
  {HEARTMULA_TAG_GUIDE}
366
 
@@ -370,37 +294,20 @@ RULES:
370
  1. Tags must be comma-separated WITHOUT spaces: tag1,tag2,tag3
371
  2. Use lowercase with underscores for multi-word: electric_guitar, hip_hop
372
  3. Include 5-8 tags covering: instrument, mood, genre, tempo
373
- 4. Be specific: "piano" not "keyboard", "808" not "bass"
374
- 5. Match the musical style described
375
 
376
  OUTPUT FORMAT (example):
377
- piano,synthesizer,happy,pop,upbeat,romantic,energetic
378
-
379
- Output ONLY the comma-separated tags, nothing else.""",
380
 
381
- "prompt_synthesizer": f"""You are the final prompt synthesizer for HeartMuLa/MiniMax Music.
382
 
383
- {MINIMAX_MUSIC_GUIDE}
384
-
385
- Combine all inputs into ONE cohesive production prompt:
386
- - 150-200 words in English
387
- - Include: genre, specific BPM, instruments with details, vocal characteristics, mood, production techniques
388
- - Be extremely specific and detailed
389
-
390
- EXAMPLE OUTPUTS:
391
-
392
- "A cappella arrangement with pure vocal harmonies, no instrumental accompaniment. Features a lead soprano voice with rich layered backing vocals creating lush harmonies. 70 BPM, peaceful and soothing mood."
393
-
394
- "Intimate jazz duet featuring conversational interplay between a warm male baritone and a silky female alto voice. Accompanied by brushed jazz drums, walking upright bass, and gentle piano comping. 95 BPM, late-night jazz club atmosphere."
395
-
396
- "High-energy K-Pop dance track with a bright, clear female vocal. Catchy hook melody, driving beat with punchy kicks and intricate hi-hat programming. Layered synth hooks and EDM-style buildups. 128 BPM, confident energy."
397
-
398
- Output ONLY the final prompt paragraph, nothing else."""
399
- }
400
 
 
 
 
401
 
402
  def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") -> str:
403
- """Groq API ํ˜ธ์ถœ - ๊ฐ•ํ™”๋œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง"""
404
  try:
405
  client = Groq(api_key=api_key)
406
 
@@ -419,16 +326,9 @@ def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") ->
419
  stream=False
420
  )
421
 
422
- if completion is None:
423
- return "Error: API ์‘๋‹ต์ด ์—†์Šต๋‹ˆ๋‹ค."
424
- if not hasattr(completion, 'choices') or not completion.choices:
425
- return "Error: API ์‘๋‹ต์— choices๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
426
- if completion.choices[0].message is None:
427
- return "Error: API ์‘๋‹ต์— message๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
428
- if completion.choices[0].message.content is None:
429
- return "Error: API ์‘๋‹ต content๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."
430
-
431
- return completion.choices[0].message.content
432
 
433
  except Exception as e:
434
  return f"Error: {str(e)}"
@@ -438,11 +338,7 @@ def clean_lyrics(text: str) -> str:
438
  """๊ฐ€์‚ฌ ํ›„์ฒ˜๋ฆฌ - HeartMuLa ํฌ๋งท ์ตœ์ ํ™”"""
439
  import re
440
 
441
- if text is None:
442
- return ""
443
- if not isinstance(text, str):
444
- return str(text)
445
- if not text.strip():
446
  return ""
447
 
448
  lines = text.split('\n')
@@ -453,34 +349,20 @@ def clean_lyrics(text: str) -> str:
453
  cleaned_lines.append('')
454
  continue
455
 
456
- skip_patterns = [
457
- r'^\s*\*',
458
- r'^\s*>',
459
- r'^\s*---',
460
- r'^\s*###',
461
- r'^\s*\*\*.*\*\*\s*$',
462
- r'^\(\s*.*\s*\)$',
463
- r'^\s*โ€“\s*\*',
464
- ]
465
-
466
- skip = False
467
- for pattern in skip_patterns:
468
- if re.match(pattern, line):
469
- skip = True
470
- break
471
-
472
- if skip:
473
  continue
474
 
 
475
  line = re.sub(r'\s*\([A-Za-z].*?\)\s*$', '', line)
476
  line = re.sub(r'\*\*(.*?)\*\*', r'\1', line)
477
 
478
  # HeartMuLa ํƒœ๊ทธ ์ •๊ทœํ™”
479
  line = re.sub(r'\[Pre.?Chorus\]', '[Prechorus]', line, flags=re.IGNORECASE)
480
- line = re.sub(r'\[Post.?Chorus\]', '[Chorus]', line, flags=re.IGNORECASE) # HeartMuLa๋Š” PostChorus ์—†์Œ
481
  line = re.sub(r'\[Build.?Up\]', '[Prechorus]', line, flags=re.IGNORECASE)
482
  line = re.sub(r'\[Break\]', '[Interlude]', line, flags=re.IGNORECASE)
483
- line = re.sub(r'\[Transition\]', '[Interlude]', line, flags=re.IGNORECASE)
484
 
485
  if line.strip():
486
  cleaned_lines.append(line.strip())
@@ -494,37 +376,36 @@ def clean_lyrics(text: str) -> str:
494
  def clean_tags(tags: str) -> str:
495
  """ํƒœ๊ทธ ์ •๋ฆฌ - HeartMuLa ํฌ๋งท (์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ)"""
496
  if not tags:
497
- return ""
498
 
499
- # ๊ณต๋ฐฑ ์ œ๊ฑฐ, ์†Œ๋ฌธ์ž ๋ณ€ํ™˜
500
  tags = tags.lower().strip()
501
-
502
- # ๋‹ค์–‘ํ•œ ๊ตฌ๋ถ„์ž๋ฅผ ์ฝค๋งˆ๋กœ ํ†ต์ผ
503
  tags = tags.replace(', ', ',').replace(' ,', ',').replace(' ', ' ')
504
- tags = tags.replace(' ', ',').replace(',,', ',')
505
-
506
- # ์•ž๋’ค ์ฝค๋งˆ ์ œ๊ฑฐ
507
  tags = tags.strip(',')
508
 
509
- # ์ค‘๋ณต ์ œ๊ฑฐ
510
  tag_list = [t.strip() for t in tags.split(',') if t.strip()]
511
  unique_tags = list(dict.fromkeys(tag_list))
512
 
513
- return ','.join(unique_tags)
514
 
515
 
 
 
 
 
516
  @spaces.GPU(duration=120)
517
  def generate_lyrics_soma(
518
  api_key: str, theme: str, genre: str, mood: str,
519
- language: str, vocal_type: str, additional: str, progress=gr.Progress()
 
520
  ):
521
- """SOMA ๊ฐ€์‚ฌ ์ƒ์„ฑ - HeartMuLa ์ตœ์ ํ™”"""
522
  if not api_key or not api_key.strip():
523
  return "โŒ Groq API Key ํ•„์š”", "", "", "", ""
524
  if not theme or not theme.strip():
525
  return "โŒ ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”", "", "", "", ""
526
 
527
- base_prompt = f"""Create PROFESSIONAL lyrics optimized for HeartMuLa/MiniMax Music:
528
  - Theme: {theme}
529
  - Genre: {genre}
530
  - Mood: {mood}
@@ -532,46 +413,42 @@ def generate_lyrics_soma(
532
  - Vocal Type: {vocal_type}
533
  {f'- Additional: {additional}' if additional else ''}
534
 
535
- CRITICAL - USE HeartMuLa STRUCTURE TAGS:
536
- Available: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
 
 
537
 
538
- REQUIRED STRUCTURE (minimum):
539
  1. [Intro] - Set the mood
540
  2. [Verse] x2-3 - Tell the story
541
- 3. [Prechorus] - Build tension (note: Prechorus, not Pre Chorus)
542
  4. [Chorus] x2-3 - Main hook (MOST important!)
543
  5. [Bridge] - Emotional contrast
544
- 6. [Outro] - Conclusion
545
-
546
- {"For A Cappella: Include harmony sections with 'ooh', 'aah', humming." if "cappella" in vocal_type.lower() else ""}
547
- {"For Duet: Mark vocal exchanges clearly." if "duet" in vocal_type.lower() else ""}
548
- {"For Group/Choir: Include anthemic chants, call-and-response patterns." if "group" in vocal_type.lower() or "choir" in vocal_type.lower() else ""}
549
-
550
- Create lyrics with PERFECT HeartMuLa structure!"""
551
 
552
  try:
553
  progress(0.2, desc="๐ŸŽค ์ž‘์‚ฌ๊ฐ€ - ์ดˆ์•ˆ ์ž‘์„ฑ...")
554
  draft = call_groq(api_key, LYRICS_AGENTS["lyricist"], base_prompt)
555
  if draft.startswith("Error:"):
556
- return f"โŒ ์ž‘์‚ฌ๊ฐ€ ์˜ค๋ฅ˜: {draft}", "", "", "", ""
557
 
558
  progress(0.4, desc="๐ŸŽน ํ”„๋กœ๋“€์„œ - ๊ตฌ์กฐ ์ตœ์ ํ™”...")
559
  structured = call_groq(api_key, LYRICS_AGENTS["producer"],
560
- f"Optimize structure for {genre} {vocal_type}. Use HeartMuLa tags.", draft)
561
  if structured.startswith("Error:"):
562
- return f"โŒ ํ”„๋กœ๋“€์„œ ์˜ค๋ฅ˜: {structured}", draft, "", "", ""
563
 
564
  progress(0.6, desc="๐Ÿ’ซ ๊ฐ์„ฑ ๋””๋ ‰ํ„ฐ - ๏ฟฝ๏ฟฝ๏ฟฝ์ • ๊ฐ•ํ™”...")
565
  emotional = call_groq(api_key, LYRICS_AGENTS["emotion_director"],
566
- f"Enhance emotional impact for {mood}.", structured)
567
  if emotional.startswith("Error:"):
568
- return f"โŒ ๊ฐ์„ฑ ๋””๋ ‰ํ„ฐ ์˜ค๋ฅ˜: {emotional}", draft, structured, "", ""
569
 
570
- progress(0.8, desc="โœจ ์ตœ์ข… ํŽธ์ง‘ - ํ’ˆ์งˆ ๊ฒ€์ฆ...")
571
  final = call_groq(api_key, LYRICS_AGENTS["final_editor"],
572
- "Output ONLY clean lyrics with HeartMuLa tags. No translations, no markdown.", emotional)
573
  if final.startswith("Error:"):
574
- return f"โŒ ์ตœ์ข… ํŽธ์ง‘ ์˜ค๋ฅ˜: {final}", draft, structured, emotional, ""
575
 
576
  final_cleaned = clean_lyrics(final)
577
 
@@ -579,18 +456,18 @@ Create lyrics with PERFECT HeartMuLa structure!"""
579
  return "โœ… ๊ฐ€์‚ฌ ์ƒ์„ฑ ์™„๋ฃŒ!", draft, structured, emotional, final_cleaned
580
 
581
  except Exception as e:
582
- return f"โŒ ์˜ˆ์™ธ ๋ฐœ์ƒ: {str(e)}", "", "", "", ""
583
 
584
 
585
  @spaces.GPU(duration=60)
586
  def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str, vocal_type: str, additional: str):
587
- """๋น ๋ฅธ ๊ฐ€์‚ฌ ์ƒ์„ฑ - HeartMuLa ์ตœ์ ํ™”"""
588
  if not api_key or not api_key.strip():
589
- return "โŒ API Key๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค"
590
  if not theme or not theme.strip():
591
  return "โŒ ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
592
 
593
- prompt = f"""Create PROFESSIONAL song lyrics for HeartMuLa/MiniMax Music:
594
  - Theme: {theme}
595
  - Genre: {genre}
596
  - Mood: {mood}
@@ -598,50 +475,42 @@ def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str,
598
  - Vocal: {vocal_type}
599
  {f'- Special: {additional}' if additional else ''}
600
 
601
- USE HeartMuLa STRUCTURE (minimum 8-10 sections):
602
  [Intro] โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus] โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus] โ†’ [Bridge] โ†’ [Chorus] โ†’ [Outro]
603
 
604
- Available tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
605
-
606
- REQUIREMENTS:
607
- - [Chorus] must appear AT LEAST 2-3 times
608
- - Use [Prechorus] (not Pre Chorus) before each [Chorus]
609
- - Add [Bridge] before final [Chorus]
610
 
611
- OUTPUT ONLY: Structure tags + lyrics. NO translations, NO explanations."""
612
 
613
  try:
614
- result = call_groq(api_key, f"""You are a professional songwriter for HeartMuLa Music.
615
- Create lyrics with PERFECT HeartMuLa structure tag placement.
616
  {HEARTMULA_LYRICS_STRUCTURE}
617
- Output ONLY clean lyrics with optimal tags.""", prompt)
618
 
619
  if result.startswith("Error:"):
620
- return f"โŒ ๊ฐ€์‚ฌ ์ƒ์„ฑ ์‹คํŒจ: {result}"
621
 
622
  return clean_lyrics(result)
623
  except Exception as e:
624
- return f"โŒ ์˜ˆ์™ธ ๋ฐœ์ƒ: {str(e)}"
625
 
626
 
627
- def generate_tags(api_key: str, genre: str, mood: str, instruments: str, tempo: str):
628
- """HeartMuLa ์Šคํƒ€์ผ ํƒœ๊ทธ ์ƒ์„ฑ"""
629
- if not api_key or not api_key.strip():
 
630
  return "piano,happy,pop"
631
 
632
  prompt = f"""Generate HeartMuLa style tags:
633
  - Genre: {genre}
634
  - Mood: {mood}
635
- - Instruments: {instruments}
636
- - Tempo: {tempo}
637
-
638
- OUTPUT FORMAT: comma-separated tags WITHOUT spaces
639
- Example: piano,synthesizer,happy,pop,upbeat,romantic
640
 
641
- Generate 5-8 relevant tags:"""
 
642
 
643
  try:
644
- result = call_groq(api_key, PROMPT_AGENTS["tag_generator"], prompt)
645
  if result.startswith("Error:"):
646
  return "piano,happy,pop"
647
  return clean_tags(result)
@@ -649,630 +518,227 @@ Generate 5-8 relevant tags:"""
649
  return "piano,happy,pop"
650
 
651
 
652
- @spaces.GPU(duration=120)
653
- def augment_prompt_soma(
654
- api_key: str, user_prompt: str, genre: str, mood: str,
655
- tempo: str, vocal_type: str, instruments: str, reference_style: str, progress=gr.Progress()
 
 
 
 
 
656
  ):
657
- """SOMA ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ• (HeartMuLa ์ตœ์ ํ™”)"""
658
- if not api_key or not api_key.strip():
659
- return "โŒ Groq API Key ํ•„์š”", ""
660
- if not user_prompt or not user_prompt.strip():
661
- return "โŒ ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”", ""
662
-
663
- base_info = f"""User's base idea: {user_prompt}
664
- Genre: {genre}
665
- Mood: {mood}
666
- Tempo: {tempo}
667
- Vocal Type: {vocal_type}
668
- Instruments: {instruments}
669
- Reference Style: {reference_style}"""
670
-
671
- try:
672
- progress(0.2, desc="๐ŸŽธ ์žฅ๋ฅด ๋ถ„์„์ค‘...")
673
- genre_analysis = call_groq(api_key, PROMPT_AGENTS["genre_specialist"], base_info)
674
- if genre_analysis.startswith("Error:"):
675
- return f"โŒ ์žฅ๋ฅด ๋ถ„์„ ์‹คํŒจ: {genre_analysis}", ""
676
-
677
- progress(0.4, desc="๐ŸŽ›๏ธ ์‚ฌ์šด๋“œ ์„ค๊ณ„์ค‘...")
678
- sound_design = call_groq(api_key, PROMPT_AGENTS["sound_designer"],
679
- f"Design sounds for:\n{base_info}", genre_analysis)
680
- if sound_design.startswith("Error:"):
681
- return f"โŒ ์‚ฌ์šด๋“œ ์„ค๊ณ„ ์‹คํŒจ: {sound_design}", ""
682
-
683
- progress(0.55, desc="๐ŸŽค ๋ณด์ปฌ ์„ค์ •์ค‘...")
684
- vocal_design = call_groq(api_key, PROMPT_AGENTS["vocal_director"],
685
- f"Define vocals for:\n{base_info}", sound_design)
686
- if vocal_design.startswith("Error:"):
687
- return f"โŒ ๋ณด์ปฌ ์„ค์ • ์‹คํŒจ: {vocal_design}", ""
688
-
689
- progress(0.7, desc="๐Ÿท๏ธ ํƒœ๊ทธ ์ƒ์„ฑ์ค‘...")
690
- tags = call_groq(api_key, PROMPT_AGENTS["tag_generator"],
691
- f"Generate tags for: {genre}, {mood}, {instruments}, {tempo}")
692
- tags_cleaned = clean_tags(tags) if not tags.startswith("Error:") else "piano,happy,pop"
693
-
694
- progress(0.85, desc="โœจ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ์ค‘...")
695
- final_prompt = call_groq(
696
- api_key,
697
- PROMPT_AGENTS["prompt_synthesizer"],
698
- f"""Synthesize into ONE music production prompt (150-200 words):
699
- Base: {user_prompt}
700
- Genre Analysis: {genre_analysis}
701
- Sound Design: {sound_design}
702
- Vocal Design: {vocal_design}
703
- Reference Style: {reference_style}
704
-
705
- Output ONLY the final prompt paragraph in English."""
706
- )
707
-
708
- if final_prompt.startswith("Error:"):
709
- return f"โŒ ํ”„๋กฌํ”„ํŠธ ํ•ฉ์„ฑ ์‹คํŒจ: {final_prompt}", ""
710
-
711
- progress(1.0, desc="โœ… ์™„๋ฃŒ!")
712
- return final_prompt.strip(), tags_cleaned
713
-
714
- except Exception as e:
715
- return f"โŒ ์˜ˆ์™ธ ๋ฐœ์ƒ: {str(e)}", ""
716
 
 
 
717
 
718
- @spaces.GPU(duration=180)
719
- def generate_music(api_key: str, model: str, prompt: str, lyrics: str,
720
- sample_rate: int, bitrate: int, audio_format: str):
721
- """MiniMax ์Œ์•… ์ƒ์„ฑ (๋ชจ๋ธ 2.5)"""
722
- if not api_key or not api_key.strip():
723
- return None, "โŒ MiniMax API Key ํ•„์š”", ""
724
- if not prompt or not prompt.strip():
725
- return None, "โŒ ํ”„๋กฌํ”„ํŠธ ํ•„์š”", ""
726
-
727
- url = "https://api.minimax.io/v1/music_generation"
728
- headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
729
-
730
- payload = {
731
- "model": model,
732
- "prompt": prompt,
733
- "audio_setting": {
734
- "sample_rate": sample_rate,
735
- "bitrate": bitrate,
736
- "format": audio_format
737
- }
738
- }
739
- if lyrics and lyrics.strip():
740
- payload["lyrics"] = lyrics
741
 
 
 
 
 
 
 
742
  try:
743
- response = requests.post(url, headers=headers, json=payload, timeout=600)
744
-
745
- if response is None:
746
- return None, "โŒ API ์‘๋‹ต์ด ์—†์Šต๋‹ˆ๋‹ค.", ""
747
-
748
- try:
749
- result = response.json()
750
- except Exception as json_err:
751
- return None, f"โŒ JSON ํŒŒ์‹ฑ ์‹คํŒจ: {str(json_err)}", response.text[:500] if response.text else ""
752
-
753
- if result is None:
754
- return None, "โŒ API ์‘๋‹ต์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค.", ""
755
-
756
- # JSON ์ถœ๋ ฅ์šฉ (audio hex ๋ฐ์ดํ„ฐ ์ถ•์•ฝ)
757
- result_for_display = result.copy()
758
- if "data" in result_for_display and isinstance(result_for_display["data"], dict):
759
- data_copy = result_for_display["data"].copy()
760
- if "audio" in data_copy and isinstance(data_copy["audio"], str) and len(data_copy["audio"]) > 100:
761
- data_copy["audio"] = f"[HEX DATA - {len(data_copy['audio'])} chars]"
762
- result_for_display["data"] = data_copy
763
-
764
- json_output = json.dumps(result_for_display, ensure_ascii=False, indent=2)
765
-
766
- base_resp = result.get("base_resp", {})
767
- status_code = base_resp.get("status_code", -1)
768
- status_msg = base_resp.get("status_msg", "")
769
-
770
- if status_code != 0:
771
- return None, f"โŒ API ์˜ค๋ฅ˜: {status_msg} (code: {status_code})", json_output
772
-
773
- data = result.get("data", {})
774
- if not data:
775
- return None, "โŒ ์‘๋‹ต์— data๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", json_output
776
-
777
- audio_hex = data.get("audio")
778
- audio_status = data.get("status")
779
-
780
- if audio_status != 2:
781
- return None, f"โณ ์ƒ์„ฑ ์ค‘... (status: {audio_status})", json_output
782
-
783
- if not audio_hex:
784
- return None, "โŒ ์‘๋‹ต์— audio ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", json_output
785
 
786
- try:
787
- import tempfile
788
- import time
789
-
790
- audio_bytes = bytes.fromhex(audio_hex)
791
- file_ext = audio_format if audio_format else "mp3"
792
- timestamp = int(time.time())
793
- filename = f"minimax_music_{timestamp}.{file_ext}"
794
-
795
- save_paths = [
796
- os.path.join(tempfile.gettempdir(), filename),
797
- os.path.join(os.getcwd(), filename),
798
- filename
799
- ]
800
-
801
- saved_path = None
802
- for path in save_paths:
803
- try:
804
- with open(path, "wb") as f:
805
- f.write(audio_bytes)
806
- saved_path = path
807
- break
808
- except Exception:
809
- continue
810
-
811
- if not saved_path:
812
- return None, "โŒ ํŒŒ์ผ ์ €์žฅ ์‹คํŒจ", json_output
813
-
814
- extra_info = result.get("extra_info", {})
815
- duration_ms = extra_info.get("music_duration", 0)
816
- duration_sec = duration_ms / 1000 if duration_ms else 0
817
- file_size_kb = len(audio_bytes) / 1024
818
-
819
- return saved_path, f"โœ… ์Œ์•… ์ƒ์„ฑ ์™„๋ฃŒ! ({duration_sec:.1f}์ดˆ, {file_size_kb:.0f}KB)", json_output
820
-
821
- except ValueError as hex_err:
822
- return None, f"โŒ HEX ๋””์ฝ”๋”ฉ ์‹คํŒจ: {str(hex_err)}", json_output
823
- except Exception as save_err:
824
- return None, f"โŒ ํŒŒ์ผ ์ €์žฅ ์‹คํŒจ: {str(save_err)}", json_output
825
-
826
- except requests.exceptions.Timeout:
827
- return None, "โŒ ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ (10๋ถ„)", ""
828
- except requests.exceptions.ConnectionError:
829
- return None, "โŒ ์—ฐ๊ฒฐ ์˜ค๋ฅ˜", ""
830
  except Exception as e:
831
- return None, f"โŒ ์˜ˆ์™ธ ๋ฐœ์ƒ: {str(e)}", ""
832
 
833
 
834
- def load_example_from_dropdown(selection):
835
- """Dropdown ์„ ํƒ์‹œ ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ ๋ฐ ํƒœ๊ทธ ๋กœ๋“œ"""
836
- if not selection:
837
- return "", ""
838
-
839
- mapping = {
840
- "๐ŸŽค A Cappella - ์ˆœ์ˆ˜ ๋ณด์ปฌ ํ•˜๋ชจ๋‹ˆ": "๐ŸŽค A Cappella (์•„์นดํŽ ๋ผ)",
841
- "๐Ÿ‘ฅ Group Harmony - ํŒŒ์›Œํ’€ ํ•ฉ์ฐฝ": "๐Ÿ‘ฅ Group Harmony (๊ทธ๋ฃน ํ•˜๋ชจ๋‹ˆ)",
842
- "๐ŸŽท Jazz Duet - ๋‚จ๋…€ ๋“€์—ฃ": "๐ŸŽท Jazz Duet (์žฌ์ฆˆ ๋“€์—ฃ)",
843
- "๐ŸŽธ Multi-Style - ์Šคํƒ€์ผ ์ „ํ™˜": "๐ŸŽธ Multi-Style (๋ฉ€ํ‹ฐ์Šคํƒ€์ผ)",
844
- "๐ŸŒƒ Urban Chill - R&B": "๐ŸŒƒ Urban Chill (์–ด๋ฐ˜ ์น )",
845
- "๐ŸŽน Jazz Club - ๋ผ์ด๋ธŒ": "๐ŸŽน Jazz Club (์žฌ์ฆˆ ํด๋Ÿฝ)",
846
- "๐Ÿชฉ Retro Disco - 80๋…„๋Œ€": "๐Ÿชฉ Retro Disco (๋ ˆํŠธ๋กœ ๋””์Šค์ฝ”)",
847
- "๐ŸŽฌ Film Score - ์‹œ๋„ค๋งˆํ‹ฑ": "๐ŸŽฌ Film Score (์˜ํ™” ์Šค์ฝ”์–ด)",
848
- "๐ŸŽต K-Pop Dance - ๊ณ ์—๋„ˆ์ง€": "๐ŸŽต K-Pop Dance (K-Pop ๋Œ„์Šค)",
849
- "๐ŸŽป Orchestral Ballad - ์›…์žฅ": "๐ŸŽป Orchestral Ballad (์˜ค์ผ€์ŠคํŠธ๋ผ ๋ฐœ๋ผ๋“œ)",
850
- "๐Ÿ”ฅ HeartMuLa Default - ๊ธฐ๋ณธ": "๐Ÿ”ฅ HeartMuLa Default (๊ธฐ๋ณธ ์˜ˆ์‹œ)"
851
- }
852
-
853
- key = mapping.get(selection)
854
- if key and key in EXAMPLE_PROMPTS:
855
- return EXAMPLE_PROMPTS[key]["prompt"], EXAMPLE_PROMPTS[key]["tags"]
856
- return "", ""
857
 
858
 
859
  # ============================================================
860
- # ๐ŸŽจ Comic Classic Theme - Toon Playground (Document 2 ๊ธฐ๋ฐ˜)
861
  # ============================================================
862
 
863
  css = """
864
- /* ===== ๐ŸŽจ Google Fonts Import ===== */
865
  @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
866
 
867
- /* ===== ๐ŸŽจ Comic Classic ๋ฐฐ๊ฒฝ - ๋นˆํ‹ฐ์ง€ ํŽ˜์ดํผ + ๏ฟฝ๏ฟฝํŠธ ํŒจํ„ด ===== */
868
  .gradio-container {
869
  background-color: #FEF9C3 !important;
870
- background-image:
871
- radial-gradient(#1F2937 1px, transparent 1px) !important;
872
  background-size: 20px 20px !important;
873
- min-height: 100vh !important;
874
  font-family: 'Comic Neue', cursive, sans-serif !important;
875
  }
876
 
877
- /* ===== ํ—ˆ๊น…ํŽ˜์ด์Šค ์ƒ๋‹จ ์š”์†Œ ์ˆจ๊น€ ===== */
878
- .huggingface-space-header,
879
- #space-header,
880
- .space-header,
881
- [class*="space-header"],
882
- .svelte-1ed2p3z,
883
- .space-header-badge,
884
- .header-badge {
885
- display: none !important;
886
- }
887
-
888
- /* ===== Footer ์™„์ „ ์ˆจ๊น€ ===== */
889
- footer,
890
- .footer,
891
- .gradio-container footer,
892
- .built-with {
893
- display: none !important;
894
- }
895
 
896
- /* ===== ๋ฉ”์ธ ์ปจํ…Œ์ด๋„ˆ ===== */
897
- #col-container {
898
- max-width: 1400px;
899
- margin: 0 auto;
900
- }
901
-
902
- /* ===== ๐ŸŽจ ํ—ค๋” ํƒ€์ดํ‹€ - ์ฝ”๋ฏน ์Šคํƒ€์ผ ===== */
903
  .header-title h1 {
904
  font-family: 'Bangers', cursive !important;
905
  color: #1F2937 !important;
906
- font-size: 3.2rem !important;
907
- font-weight: 400 !important;
908
  text-align: center !important;
909
- margin-bottom: 0.5rem !important;
910
- text-shadow:
911
- 4px 4px 0px #FACC15,
912
- 6px 6px 0px #1F2937 !important;
913
  letter-spacing: 3px !important;
914
  -webkit-text-stroke: 2px #1F2937 !important;
915
  }
916
 
917
- /* ===== ๐ŸŽจ ์„œ๋ธŒํƒ€์ดํ‹€ ===== */
918
- .subtitle-text {
919
- text-align: center !important;
920
- font-family: 'Comic Neue', cursive !important;
921
- font-size: 1.1rem !important;
922
- color: #1F2937 !important;
923
- margin-bottom: 1.5rem !important;
924
- font-weight: 700 !important;
925
- }
926
-
927
- /* ===== ๐ŸŽจ ์„น์…˜ ํƒ€์ดํ‹€ ===== */
928
  .section-title {
929
  font-family: 'Bangers', cursive !important;
930
  color: #1F2937 !important;
931
- font-size: 1.8rem !important;
932
  border-bottom: 4px solid #3B82F6 !important;
933
  padding-bottom: 8px !important;
934
- margin-bottom: 16px !important;
935
  text-shadow: 2px 2px 0px #FACC15 !important;
936
  }
937
 
938
- /* ===== ๐ŸŽจ ์นด๋“œ/ํŒจ๋„ - ๋งŒํ™” ํ”„๋ ˆ์ž„ ์Šคํƒ€์ผ ===== */
939
- .gr-panel,
940
- .gr-box,
941
- .gr-form,
942
- .block,
943
- .gr-group {
944
  background: #FFFFFF !important;
945
  border: 3px solid #1F2937 !important;
946
  border-radius: 8px !important;
947
  box-shadow: 6px 6px 0px #1F2937 !important;
948
- transition: all 0.2s ease !important;
949
  }
950
 
951
- .gr-panel:hover,
952
- .block:hover {
953
  transform: translate(-2px, -2px) !important;
954
  box-shadow: 8px 8px 0px #1F2937 !important;
955
  }
956
 
957
- /* ===== ๐ŸŽจ ์ž…๋ ฅ ํ•„๋“œ (Textbox) ===== */
958
- textarea,
959
- input[type="text"],
960
- input[type="number"],
961
- input[type="password"] {
962
  background: #FFFFFF !important;
963
  border: 3px solid #1F2937 !important;
964
  border-radius: 8px !important;
965
- color: #1F2937 !important;
966
  font-family: 'Comic Neue', cursive !important;
967
- font-size: 1rem !important;
968
  font-weight: 700 !important;
969
- transition: all 0.2s ease !important;
970
  }
971
 
972
- textarea:focus,
973
- input[type="text"]:focus,
974
- input[type="number"]:focus,
975
- input[type="password"]:focus {
976
  border-color: #3B82F6 !important;
977
  box-shadow: 4px 4px 0px #3B82F6 !important;
978
- outline: none !important;
979
- }
980
-
981
- textarea::placeholder {
982
- color: #9CA3AF !important;
983
- font-weight: 400 !important;
984
  }
985
 
986
- /* ===== ๐ŸŽจ Primary ๋ฒ„ํŠผ - ์ฝ”๋ฏน ๋ธ”๋ฃจ ===== */
987
- .gr-button-primary,
988
- button.primary,
989
- .gr-button.primary {
990
  background: #3B82F6 !important;
991
  border: 3px solid #1F2937 !important;
992
  border-radius: 8px !important;
993
  color: #FFFFFF !important;
994
  font-family: 'Bangers', cursive !important;
995
- font-weight: 400 !important;
996
  font-size: 1.2rem !important;
997
  letter-spacing: 2px !important;
998
- padding: 12px 24px !important;
999
  box-shadow: 5px 5px 0px #1F2937 !important;
1000
- transition: all 0.1s ease !important;
1001
  text-shadow: 1px 1px 0px #1F2937 !important;
1002
  }
1003
 
1004
- .gr-button-primary:hover,
1005
- button.primary:hover,
1006
- .gr-button.primary:hover {
1007
  background: #2563EB !important;
1008
  transform: translate(-2px, -2px) !important;
1009
  box-shadow: 7px 7px 0px #1F2937 !important;
1010
  }
1011
 
1012
- .gr-button-primary:active,
1013
- button.primary:active,
1014
- .gr-button.primary:active {
1015
- transform: translate(3px, 3px) !important;
1016
- box-shadow: 2px 2px 0px #1F2937 !important;
1017
- }
1018
-
1019
- /* ===== ๐ŸŽจ Secondary ๋ฒ„ํŠผ - ์ฝ”๋ฏน ๋ ˆ๋“œ ===== */
1020
- .gr-button-secondary,
1021
- button.secondary {
1022
  background: #EF4444 !important;
1023
  border: 3px solid #1F2937 !important;
1024
- border-radius: 8px !important;
1025
  color: #FFFFFF !important;
1026
  font-family: 'Bangers', cursive !important;
1027
- font-weight: 400 !important;
1028
- font-size: 1.1rem !important;
1029
- letter-spacing: 1px !important;
1030
  box-shadow: 4px 4px 0px #1F2937 !important;
1031
- transition: all 0.1s ease !important;
1032
- text-shadow: 1px 1px 0px #1F2937 !important;
1033
  }
1034
 
1035
- .gr-button-secondary:hover,
1036
- button.secondary:hover {
1037
- background: #DC2626 !important;
1038
- transform: translate(-2px, -2px) !important;
1039
- box-shadow: 6px 6px 0px #1F2937 !important;
1040
- }
1041
-
1042
- /* ===== ๐ŸŽจ Generate ๋ฒ„ํŠผ - ์ฝ”๋ฏน ๊ทธ๋ฆฐ ===== */
1043
  .generate-btn {
1044
  background: #10B981 !important;
1045
  border: 3px solid #1F2937 !important;
1046
- border-radius: 8px !important;
1047
  color: #FFFFFF !important;
1048
  font-family: 'Bangers', cursive !important;
1049
- font-weight: 400 !important;
1050
- font-size: 1.3rem !important;
1051
- letter-spacing: 2px !important;
1052
  box-shadow: 5px 5px 0px #1F2937 !important;
1053
- text-shadow: 1px 1px 0px #1F2937 !important;
1054
  }
1055
 
1056
  .generate-btn:hover {
1057
  background: #059669 !important;
1058
  transform: translate(-2px, -2px) !important;
1059
- box-shadow: 7px 7px 0px #1F2937 !important;
1060
  }
1061
 
1062
- /* ===== ๐ŸŽจ ์•„์ฝ”๋””์–ธ - ๋งํ’์„  ์Šคํƒ€์ผ ===== */
1063
  .gr-accordion {
1064
  background: #FACC15 !important;
1065
  border: 3px solid #1F2937 !important;
1066
- border-radius: 8px !important;
1067
  box-shadow: 4px 4px 0px #1F2937 !important;
1068
  }
1069
 
1070
- .gr-accordion-header {
1071
- color: #1F2937 !important;
1072
- font-family: 'Comic Neue', cursive !important;
1073
- font-weight: 700 !important;
1074
- font-size: 1.1rem !important;
1075
- }
1076
-
1077
- /* ===== ๐ŸŽจ Dropdown ===== */
1078
- .gr-dropdown,
1079
- select {
1080
- background: #FFFFFF !important;
1081
- border: 3px solid #1F2937 !important;
1082
- border-radius: 8px !important;
1083
- color: #1F2937 !important;
1084
- font-family: 'Comic Neue', cursive !important;
1085
- font-weight: 700 !important;
1086
- }
1087
-
1088
- /* ===== ๐ŸŽจ ๋ผ๋ฒจ ์Šคํƒ€์ผ ===== */
1089
- label,
1090
- .gr-input-label,
1091
- .gr-block-label {
1092
- color: #1F2937 !important;
1093
- font-family: 'Comic Neue', cursive !important;
1094
- font-weight: 700 !important;
1095
- font-size: 1rem !important;
1096
- }
1097
-
1098
- /* ===== ๐ŸŽจ ์˜ค๋””์˜ค ํ”Œ๋ ˆ์ด์–ด ===== */
1099
- .gr-audio,
1100
- audio {
1101
- border: 4px solid #1F2937 !important;
1102
- border-radius: 8px !important;
1103
- box-shadow: 6px 6px 0px #1F2937 !important;
1104
  }
1105
 
1106
- /* ===== ๐ŸŽจ ์ฝ”๋“œ ๋ธ”๋ก ===== */
1107
- .gr-code,
1108
- pre,
1109
- code {
1110
  background: #1F2937 !important;
1111
  color: #10B981 !important;
1112
  font-family: 'Courier New', monospace !important;
1113
  border: 3px solid #10B981 !important;
1114
- border-radius: 8px !important;
1115
- box-shadow: 4px 4px 0px #10B981 !important;
1116
  }
1117
 
1118
- /* ===== ๐ŸŽจ ๋งˆํฌ๋‹ค์šด ===== */
1119
- .gr-markdown {
1120
- font-family: 'Comic Neue', cursive !important;
1121
  color: #1F2937 !important;
1122
- }
1123
-
1124
- .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
1125
- font-family: 'Bangers', cursive !important;
1126
- color: #1F2937 !important;
1127
- text-shadow: 2px 2px 0px #FACC15 !important;
1128
- }
1129
-
1130
- /* ===== ๐ŸŽจ ํƒญ ์Šคํƒ€์ผ ===== */
1131
- .gr-tab-nav {
1132
- background: #FACC15 !important;
1133
- border: 3px solid #1F2937 !important;
1134
- border-radius: 8px 8px 0 0 !important;
1135
- }
1136
-
1137
- .gr-tab-nav button {
1138
  font-family: 'Comic Neue', cursive !important;
1139
  font-weight: 700 !important;
1140
- color: #1F2937 !important;
1141
- }
1142
-
1143
- .gr-tab-nav button.selected {
1144
- background: #3B82F6 !important;
1145
- color: #FFFFFF !important;
1146
- }
1147
-
1148
- /* ===== ๐ŸŽจ ์ƒํƒœ ํ‘œ์‹œ ===== */
1149
- .status-box textarea {
1150
- background: #1F2937 !important;
1151
- color: #10B981 !important;
1152
- font-family: 'Courier New', monospace !important;
1153
- font-size: 0.9rem !important;
1154
- border: 3px solid #10B981 !important;
1155
- border-radius: 8px !important;
1156
- }
1157
-
1158
- /* ===== ๐ŸŽจ ์Šคํฌ๋กค๋ฐ” - ์ฝ”๋ฏน ์Šคํƒ€์ผ ===== */
1159
- ::-webkit-scrollbar {
1160
- width: 12px;
1161
- height: 12px;
1162
  }
1163
 
1164
- ::-webkit-scrollbar-track {
1165
- background: #FEF9C3;
1166
- border: 2px solid #1F2937;
1167
- }
1168
-
1169
- ::-webkit-scrollbar-thumb {
1170
- background: #3B82F6;
1171
- border: 2px solid #1F2937;
1172
- border-radius: 0px;
1173
- }
1174
-
1175
- ::-webkit-scrollbar-thumb:hover {
1176
- background: #EF4444;
1177
- }
1178
-
1179
- /* ===== ๐ŸŽจ ์„ ํƒ ํ•˜์ด๋ผ์ดํŠธ ===== */
1180
- ::selection {
1181
- background: #FACC15;
1182
- color: #1F2937;
1183
- }
1184
-
1185
- /* ===== ๐ŸŽจ Row/Column ๊ฐ„๊ฒฉ ===== */
1186
- .gr-row {
1187
- gap: 1.5rem !important;
1188
- }
1189
-
1190
- .gr-column {
1191
- gap: 1rem !important;
1192
- }
1193
-
1194
- /* ===== ๐ŸŽจ ํƒœ๊ทธ ์ž…๋ ฅ ํŠน๋ณ„ ์Šคํƒ€์ผ ===== */
1195
- .tag-input textarea {
1196
- background: #FEF3C7 !important;
1197
- border: 3px dashed #F59E0B !important;
1198
- font-family: 'Courier New', monospace !important;
1199
- }
1200
-
1201
- /* ===== ๋ฐ˜์‘ํ˜• ์กฐ์ • ===== */
1202
- @media (max-width: 768px) {
1203
- .header-title h1 {
1204
- font-size: 2rem !important;
1205
- text-shadow:
1206
- 3px 3px 0px #FACC15,
1207
- 4px 4px 0px #1F2937 !important;
1208
- }
1209
-
1210
- .gr-button-primary,
1211
- button.primary {
1212
- padding: 10px 16px !important;
1213
- font-size: 1rem !important;
1214
- }
1215
- }
1216
-
1217
- /* ===== ๐ŸŽจ ํŠน์ˆ˜ ํšจ๊ณผ - ๋ฐ˜์ง์ž„ ===== */
1218
- @keyframes sparkle {
1219
- 0%, 100% { opacity: 1; }
1220
- 50% { opacity: 0.7; }
1221
- }
1222
-
1223
- .sparkle {
1224
- animation: sparkle 2s ease-in-out infinite;
1225
- }
1226
  """
1227
 
 
1228
  # ============================================================
1229
- # Gradio UI
1230
  # ============================================================
1231
 
1232
- with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio", theme=gr.themes.Soft(primary_hue="indigo")) as demo:
1233
 
1234
- # HOME Badge
1235
  gr.HTML("""
1236
  <div style="text-align: center; margin: 20px 0 10px 0;">
1237
- <a href="https://huggingface.co/HeartMuLa" target="_blank" style="text-decoration: none;">
1238
- <img src="https://img.shields.io/badge/๐ŸŽต_HeartMuLa-Official-ff6b6b?style=for-the-badge&labelColor=1F2937" alt="HeartMuLa">
1239
  </a>
1240
- <a href="https://www.minimax.io" target="_blank" style="text-decoration: none; margin-left: 10px;">
1241
- <img src="https://img.shields.io/badge/๐ŸŽน_MiniMax-Music_2.5-3B82F6?style=for-the-badge&labelColor=1F2937" alt="MiniMax">
1242
  </a>
1243
  </div>
1244
  """)
1245
 
1246
- # Header
1247
- gr.Markdown("""
1248
- # ๐ŸŽต SOMA MUSIC STUDIO ๐ŸŽถ
1249
- """, elem_classes="header-title")
1250
 
1251
  gr.Markdown("""
1252
- <p class="subtitle-text">๐Ÿ’ซ HeartMuLa + MiniMax Music 2.5 + SOMA Multi-Agent = ์ตœ๊ณ  ํ’ˆ์งˆ AI ์Œ์•… ์ƒ์„ฑ ๐Ÿ’ซ</p>
1253
- <p class="subtitle-text">๐ŸŽค A Cappella | ๐ŸŽท Jazz Duet | ๐ŸŽธ Multi-Style | ๐ŸŽฌ Film Score | ๐ŸŽต K-Pop ์ง€์›</p>
 
1254
  """)
1255
 
1256
- # API Keys
1257
- GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
1258
- MINIMAX_KEY = os.environ.get("MINIMAX_API_KEY", "")
1259
-
1260
- with gr.Accordion("๐Ÿ”‘ API Keys", open=not (GROQ_KEY and MINIMAX_KEY)):
1261
- with gr.Row():
1262
- groq_key = gr.Textbox(
1263
- label="๐Ÿฆ™ Groq API Key (๊ฐ€์‚ฌ/ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ์šฉ)",
1264
- type="password",
1265
- value=GROQ_KEY,
1266
- placeholder="gsk_..." if not GROQ_KEY else "โœ… Secret ๋กœ๋“œ๋จ",
1267
- interactive=not bool(GROQ_KEY)
1268
- )
1269
- minimax_key = gr.Textbox(
1270
- label="๐ŸŽน MiniMax API Key (์Œ์•… ์ƒ์„ฑ์šฉ)",
1271
- type="password",
1272
- value=MINIMAX_KEY,
1273
- placeholder="API Key" if not MINIMAX_KEY else "โœ… Secret ๋กœ๋“œ๋จ",
1274
- interactive=not bool(MINIMAX_KEY)
1275
- )
1276
 
1277
  with gr.Row(equal_height=False):
1278
  # ========== ์ขŒ์ธก: ๊ฐ€์‚ฌ ์ƒ์„ฑ ==========
@@ -1281,7 +747,7 @@ with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio", theme=gr.themes.Soft(pri
1281
 
1282
  theme_input = gr.Textbox(
1283
  label="๐ŸŽฏ ๋…ธ๋ž˜ ์ฃผ์ œ",
1284
- placeholder="์˜ˆ: ์ด๋ณ„ ํ›„ ์„ฑ์žฅ, ๊ฟˆ์„ ํ–ฅํ•œ ๋„์ „, ์‚ฌ๋ž‘์˜ ๊ณ ๋ฐฑ, ์šฐ์ •์˜ ํž˜...",
1285
  lines=2
1286
  )
1287
 
@@ -1289,14 +755,13 @@ with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio", theme=gr.themes.Soft(pri
1289
  lyrics_genre = gr.Dropdown(
1290
  label="๐ŸŽธ ์žฅ๋ฅด",
1291
  choices=["K-Pop", "Pop", "R&B", "Hip-Hop", "Ballad", "Rock",
1292
- "EDM", "Trap", "Jazz", "Blues", "Folk", "Disco", "Cinematic"],
1293
  value="K-Pop"
1294
  )
1295
  lyrics_mood = gr.Dropdown(
1296
  label="๐Ÿ’ซ ๋ถ„์œ„๊ธฐ",
1297
  choices=["Empowering", "Melancholic", "Joyful", "Romantic",
1298
- "Aggressive", "Dreamy", "Nostalgic", "Energetic", "Dark",
1299
- "Peaceful", "Confident", "Intimate"],
1300
  value="Empowering"
1301
  )
1302
 
@@ -1304,189 +769,136 @@ with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio", theme=gr.themes.Soft(pri
1304
  lyrics_language = gr.Dropdown(
1305
  label="๐ŸŒ ์–ธ์–ด",
1306
  choices=["English", "Korean", "Korean + English", "Japanese"],
1307
- value="Korean"
1308
  )
1309
  lyrics_vocal_type = gr.Dropdown(
1310
- label="๐ŸŽค ๋ณด์ปฌ ํƒ€์ž…",
1311
- choices=["Solo Female", "Solo Male", "Female Duet", "Male Duet",
1312
- "Male-Female Duet", "A Cappella", "Group/Choir"],
1313
- value="Group/Choir"
1314
  )
1315
 
1316
  lyrics_additional = gr.Textbox(
1317
  label="โœจ ์ถ”๊ฐ€ ์ง€์‹œ (์„ ํƒ)",
1318
- placeholder="ํŠน๋ณ„ ์š”์ฒญ: ํ›„๋ ด๊ตฌ ๊ฐ•์กฐ, ๋žฉ ํŒŒํŠธ ์ถ”๊ฐ€, ํŠน์ • ๋‹จ์–ด ํฌํ•จ...",
1319
  lines=1
1320
  )
1321
 
1322
  with gr.Row():
1323
- quick_btn = gr.Button("โšก QUICK GENERATE", variant="secondary")
1324
  soma_lyrics_btn = gr.Button("๐Ÿง  SOMA GENERATE", variant="primary")
1325
 
1326
  lyrics_status = gr.Textbox(label="๐Ÿ“Š ์ƒํƒœ", interactive=False, max_lines=1, elem_classes="status-box")
1327
 
1328
  with gr.Accordion("๐Ÿ” SOMA ์ž‘์—… ๊ณผ์ •", open=False):
1329
- with gr.Row():
1330
- with gr.Column():
1331
- step1_out = gr.Textbox(label="1๏ธโƒฃ ์ž‘์‚ฌ๊ฐ€", lines=4, interactive=False)
1332
- step2_out = gr.Textbox(label="2๏ธโƒฃ ํ”„๋กœ๋“€์„œ", lines=4, interactive=False)
1333
- with gr.Column():
1334
- step3_out = gr.Textbox(label="3๏ธโƒฃ ๊ฐ์„ฑ ๋””๋ ‰ํ„ฐ", lines=4, interactive=False)
1335
- step4_out = gr.Textbox(label="4๏ธโƒฃ ์ตœ์ข… ํŽธ์ง‘", lines=4, interactive=False)
1336
 
1337
  final_lyrics = gr.Textbox(
1338
- label="โœ๏ธ ์ตœ์ข… ๊ฐ€์‚ฌ (ํŽธ์ง‘ ๊ฐ€๋Šฅ)",
1339
- lines=14,
1340
- placeholder="์ƒ์„ฑ๋œ ๊ฐ€์‚ฌ๊ฐ€ ์—ฌ๊ธฐ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค...\n\n[Intro]\n\n[Verse]\n...\n\n[Chorus]\n..."
 
1341
  )
1342
-
1343
- with gr.Accordion("๐Ÿ“‹ HeartMuLa ๊ตฌ์กฐ ํƒœ๊ทธ ๊ฐ€์ด๋“œ", open=False):
1344
- gr.Markdown("""
1345
- **HeartMuLa ํƒœ๊ทธ:** `[Intro]` `[Verse]` `[Prechorus]` `[Chorus]` `[Bridge]` `[Interlude]` `[Hook]` `[Outro]` `[Inst]` `[Solo]`
1346
-
1347
- โš ๏ธ **์ฃผ์˜:** `[Pre Chorus]`๊ฐ€ ์•„๋‹Œ `[Prechorus]` ์‚ฌ์šฉ (๊ณต๋ฐฑ ์—†์Œ)
1348
-
1349
- **์ตœ์  ๊ตฌ์กฐ ์˜ˆ์‹œ:**
1350
- ```
1351
- [Intro] โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus]
1352
- โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus]
1353
- โ†’ [Bridge] โ†’ [Chorus] โ†’ [Outro]
1354
- ```
1355
-
1356
- **ํƒœ๊ทธ๋ณ„ ์—ญํ• :**
1357
- - `[Chorus]` - ๊ฐ€์žฅ ์ค‘์š”! 2-3ํšŒ ๋ฐ˜๋ณต, ๊ธฐ์–ต์— ๋‚จ๋Š” ํ›…
1358
- - `[Prechorus]` - ์ฝ”๋Ÿฌ์Šค ์ „ ํ…์…˜ ๋นŒ๋“œ์—…
1359
- - `[Bridge]` - ๊ฐ์ • ์ „ํ™˜์ , ์ตœ์ข… ์ฝ”๋Ÿฌ์Šค ์ „ ๋ฐฐ์น˜
1360
- """)
1361
 
1362
  # ========== ์šฐ์ธก: ์Œ์•… ์ƒ์„ฑ ==========
1363
  with gr.Column(scale=1, min_width=400):
1364
  gr.Markdown("## ๐ŸŽต MUSIC GENERATOR", elem_classes="section-title")
1365
 
1366
- # ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ ์„ ํƒ
1367
- gr.Markdown("### ๐Ÿ“š ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ (ํด๋ฆญํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ)")
1368
-
1369
- example_dropdown = gr.Dropdown(
1370
- label="๐ŸŽฏ ์˜ˆ์ œ ์„ ํƒ",
1371
- choices=[
1372
- "๐ŸŽค A Cappella - ์ˆœ์ˆ˜ ๋ณด์ปฌ ํ•˜๋ชจ๋‹ˆ",
1373
- "๐Ÿ‘ฅ Group Harmony - ํŒŒ์›Œํ’€ ํ•ฉ์ฐฝ",
1374
- "๐ŸŽท Jazz Duet - ๋‚จ๋…€ ๋“€์—ฃ",
1375
- "๐ŸŽธ Multi-Style - ์Šคํƒ€์ผ ์ „ํ™˜",
1376
- "๐ŸŒƒ Urban Chill - R&B",
1377
- "๐ŸŽน Jazz Club - ๋ผ์ด๋ธŒ",
1378
- "๐Ÿชฉ Retro Disco - 80๋…„๋Œ€",
1379
- "๐ŸŽฌ Film Score - ์‹œ๋„ค๋งˆํ‹ฑ",
1380
- "๐ŸŽต K-Pop Dance - ๊ณ ์—๋„ˆ์ง€",
1381
- "๐ŸŽป Orchestral Ballad - ์›…์žฅ",
1382
- "๐Ÿ”ฅ HeartMuLa Default - ๊ธฐ๋ณธ"
1383
- ],
1384
- value=None,
1385
- interactive=True
1386
  )
1387
 
1388
- gr.Markdown("### ๐ŸŽ›๏ธ ํ”„๋กฌํ”„ํŠธ ์„ค์ •")
1389
-
1390
- base_prompt = gr.Textbox(
1391
- label="๐Ÿ’ก ๊ธฐ๋ณธ ์•„์ด๋””์–ด",
1392
- placeholder="์›ํ•˜๋Š” ์Œ์•… ์Šคํƒ€์ผ์„ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์„ธ์š”...",
1393
- lines=2
1394
  )
1395
 
1396
  with gr.Row():
1397
- music_genre = gr.Dropdown(
1398
- label="๐ŸŽธ ์žฅ๋ฅด",
1399
- choices=["K-Pop", "Pop", "R&B/Soul", "Hip-Hop/Trap", "EDM/House",
1400
- "Rock", "Ballad", "Jazz", "Blues", "Lo-Fi", "Disco",
1401
- "Cinematic", "Classical"],
1402
- value="K-Pop"
1403
- )
1404
- music_mood = gr.Dropdown(
1405
- label="๐Ÿ’ซ ๋ถ„์œ„๊ธฐ",
1406
- choices=["Energetic", "Chill", "Emotional", "Dark", "Uplifting",
1407
- "Romantic", "Aggressive", "Dreamy", "Confident", "Peaceful",
1408
- "Nostalgic", "Epic"],
1409
- value="Energetic"
1410
- )
1411
 
1412
- with gr.Row():
1413
- music_tempo = gr.Dropdown(
1414
- label="โฑ๏ธ ํ…œํฌ",
1415
- choices=["Very Slow (50-70 BPM)", "Slow (70-90 BPM)", "Medium (90-110 BPM)",
1416
- "Fast (110-130 BPM)", "Very Fast (130-150 BPM)"],
1417
- value="Medium (90-110 BPM)"
1418
  )
1419
- music_vocal = gr.Dropdown(
1420
- label="๐ŸŽค ๋ณด์ปฌ",
1421
- choices=["Female (Clear)", "Female (Warm)", "Female (Powerful)",
1422
- "Male (Smooth)", "Male (Deep)", "Male (Raspy)",
1423
- "Male-Female Duet", "A Cappella", "Group/Choir",
1424
- "Instrumental"],
1425
- value="Group/Choir"
 
 
 
 
 
 
 
 
 
1426
  )
1427
 
1428
- music_instruments = gr.Textbox(
1429
- label="๐ŸŽน ์•…๊ธฐ/์‚ฌ์šด๋“œ",
1430
- placeholder="์˜ˆ: piano, synthesizer, drums, bass, strings, saxophone...",
1431
- value="piano,synthesizer,drums,bass,strings"
1432
- )
1433
-
1434
- music_reference = gr.Dropdown(
1435
- label="๐ŸŽฏ ๋ ˆํผ๋Ÿฐ์Šค ์Šคํƒ€์ผ",
1436
- choices=["None", "A Cappella", "Jazz Club", "Urban R&B",
1437
- "Retro Disco", "Film Score", "K-Pop Dance", "Orchestral Ballad"],
1438
- value="None"
1439
- )
1440
-
1441
- augment_btn = gr.Button("๐Ÿš€ SOMA AUGMENT", variant="primary", size="lg")
1442
-
1443
- augmented_prompt = gr.Textbox(
1444
- label="โœจ ์ฆ๊ฐ•๋œ ํ”„๋กฌํ”„ํŠธ (ํŽธ์ง‘ ๊ฐ€๋Šฅ)",
1445
- lines=5,
1446
- placeholder="SOMA๊ฐ€ ์ƒ์„ฑํ•œ ๊ณ ํ’ˆ์งˆ ํ”„๋กฌํ”„ํŠธ..."
1447
- )
1448
-
1449
- style_tags = gr.Textbox(
1450
- label="๐Ÿท๏ธ ์Šคํƒ€์ผ ํƒœ๊ทธ (HeartMuLa ํฌ๋งท: ์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ)",
1451
- placeholder="piano,happy,pop,upbeat,romantic",
1452
- lines=1,
1453
- elem_classes="tag-input"
1454
  )
1455
 
1456
- with gr.Accordion("โš™๏ธ ์ƒ์„ฑ ์„ค์ •", open=False):
1457
- model_select = gr.Dropdown(
1458
- label="๐Ÿค– ๋ชจ๋ธ", choices=["music-2.5"], value="music-2.5"
1459
- )
1460
- with gr.Row():
1461
- sample_rate = gr.Dropdown(label="Sample Rate", choices=[44100], value=44100)
1462
- bitrate = gr.Dropdown(label="Bitrate", choices=[128000, 192000, 256000], value=256000)
1463
- audio_format = gr.Dropdown(label="Format", choices=["mp3", "wav"], value="mp3")
1464
-
1465
- generate_music_btn = gr.Button("๐ŸŽถ GENERATE MUSIC!", variant="primary", size="lg", elem_classes="generate-btn")
1466
-
1467
  music_status = gr.Textbox(label="๐Ÿ“Š ์ƒํƒœ", interactive=False, max_lines=1, elem_classes="status-box")
1468
- music_output = gr.Audio(label="๐ŸŽง ์ƒ์„ฑ๋œ ์Œ์•…", type="filepath")
1469
-
1470
- with gr.Accordion("๐Ÿ“‹ API ์‘๋‹ต", open=False):
1471
- json_output = gr.Code(label="JSON Response", language="json", lines=6)
1472
-
1473
- # ========== ํ•˜๋‹จ: ๊ฐ€์ด๋“œ ==========
1474
- with gr.Accordion("๐Ÿ“– HeartMuLa & MiniMax ๊ฐ€์ด๋“œ", open=False):
 
 
 
 
 
1475
  gr.Markdown(f"""
1476
  {HEARTMULA_TAG_GUIDE}
1477
 
1478
  ---
1479
 
1480
  {HEARTMULA_LYRICS_STRUCTURE}
 
 
 
 
 
 
 
 
1481
  """)
1482
 
1483
  # ========== Event Handlers ==========
1484
 
1485
- # ์˜ˆ์ œ Dropdown ์„ ํƒ
1486
- example_dropdown.change(
1487
- fn=load_example_from_dropdown,
1488
- inputs=[example_dropdown],
1489
- outputs=[augmented_prompt, style_tags]
 
 
 
 
 
 
 
1490
  )
1491
 
1492
  # ๋น ๋ฅธ ๊ฐ€์‚ฌ ์ƒ์„ฑ
@@ -1507,18 +919,11 @@ with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio", theme=gr.themes.Soft(pri
1507
  outputs=[final_lyrics]
1508
  )
1509
 
1510
- # SOMA ํ”„๋กฌํ”„ํŠธ ์ฆ๊ฐ•
1511
- augment_btn.click(
1512
- fn=augment_prompt_soma,
1513
- inputs=[groq_key, base_prompt, music_genre, music_mood, music_tempo, music_vocal, music_instruments, music_reference],
1514
- outputs=[augmented_prompt, style_tags]
1515
- )
1516
-
1517
- # ์Œ์•… ์ƒ์„ฑ
1518
- generate_music_btn.click(
1519
- fn=generate_music,
1520
- inputs=[minimax_key, model_select, augmented_prompt, final_lyrics, sample_rate, bitrate, audio_format],
1521
- outputs=[music_output, music_status, json_output]
1522
  )
1523
 
1524
 
 
1
+ import os
2
+ import tempfile
3
+ import torch
4
  import gradio as gr
5
+ from huggingface_hub import hf_hub_download, snapshot_download
6
+ import spaces
7
  import requests
8
  import json
 
9
  from groq import Groq
10
 
11
+ # ============================================================
12
+ # ๐ŸŽต SOMA Music Studio - HeartMuLa Edition
13
+ # HeartMuLa-oss-3B + SOMA Multi-Agent + Comic Classic Theme
14
+ # ============================================================
15
+
16
+ # ============================================================
17
+ # ๐ŸŽต HeartMuLa ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ๋ฐ ๋กœ๋”ฉ
18
+ # ============================================================
19
+
20
+ def download_models():
21
+ """Download all required model files from HuggingFace Hub."""
22
+ cache_dir = os.environ.get("HF_HOME", os.path.expanduser("/tmp"))
23
+ model_dir = os.path.join(cache_dir, "heartmula_models")
24
+
25
+ if not os.path.exists(model_dir):
26
+ os.makedirs(model_dir, exist_ok=True)
27
+
28
+ # Download HeartMuLaGen (tokenizer and gen_config)
29
+ print("Downloading HeartMuLaGen files...")
30
+ for filename in ["tokenizer.json", "gen_config.json"]:
31
+ hf_hub_download(
32
+ repo_id="HeartMuLa/HeartMuLaGen",
33
+ filename=filename,
34
+ local_dir=model_dir,
35
+ )
36
+
37
+ # Download HeartMuLa-oss-3B
38
+ print("Downloading HeartMuLa-oss-3B...")
39
+ snapshot_download(
40
+ repo_id="HeartMuLa/HeartMuLa-oss-3B",
41
+ local_dir=os.path.join(model_dir, "HeartMuLa-oss-3B"),
42
+ )
43
+
44
+ # Download HeartCodec-oss
45
+ print("Downloading HeartCodec-oss...")
46
+ snapshot_download(
47
+ repo_id="HeartMuLa/HeartCodec-oss",
48
+ local_dir=os.path.join(model_dir, "HeartCodec-oss"),
49
+ )
50
+
51
+ print("All models downloaded successfully!")
52
+ return model_dir
53
+
54
+ from heartlib import HeartMuLaGenPipeline
55
+
56
+ # ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ๋ฐ ๋กœ๋”ฉ
57
+ model_dir = download_models()
58
+
59
+ # Determine device and dtype
60
+ if torch.cuda.is_available():
61
+ device = torch.device("cuda")
62
+ dtype = torch.bfloat16
63
+ else:
64
+ device = torch.device("cpu")
65
+ dtype = torch.float32
66
+
67
+ print(f"Loading HeartMuLa pipeline on {device} with {dtype}...")
68
+ pipe = HeartMuLaGenPipeline.from_pretrained(
69
+ model_dir,
70
+ device=device,
71
+ dtype=dtype,
72
+ version="3B",
73
+ )
74
+ print("HeartMuLa Pipeline loaded successfully!")
75
+
76
 
77
  # ============================================================
78
+ # ๐ŸŽต HeartMuLa ๊ฐ€์ด๋“œ ๋ฐ ์„ค์ •
 
79
  # ============================================================
80
 
81
  # HeartMuLa ๊ถŒ์žฅ ๊ตฌ์กฐ ํƒœ๊ทธ (๊ณต์‹ ๋ฌธ์„œ ๊ธฐ๋ฐ˜)
 
84
  "[Interlude]", "[Hook]", "[Outro]", "[Inst]", "[Solo]"
85
  ]
86
 
87
+ # HeartMuLa ์Šคํƒ€์ผ ํƒœ๊ทธ ๊ฐ€์ด๋“œ
88
  HEARTMULA_TAG_GUIDE = """
89
  ## ๐ŸŽผ HeartMuLa Tag Format Guide
90
 
 
103
  **Mood:** happy,sad,romantic,energetic,calm,melancholic,uplifting,dark,dreamy,nostalgic,powerful,peaceful
104
  **Genre:** pop,rock,jazz,classical,electronic,folk,blues,r&b,hip_hop,disco,ballad,cinematic
105
  **Tempo:** fast,slow,moderate,upbeat,relaxed
 
 
 
 
 
 
 
106
  """
107
 
108
+ # HeartMuLa ๊ถŒ์žฅ ๊ฐ€์‚ฌ ๊ตฌ์กฐ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  HEARTMULA_LYRICS_STRUCTURE = """
110
  ## ๐Ÿ“ HeartMuLa Recommended Lyrics Structure
111
 
 
116
  [Verse]
117
  First verse lyrics here
118
  Second line of first verse
 
 
119
 
120
  [Prechorus]
121
  Building tension here
 
124
  [Chorus]
125
  Main hook and memorable melody
126
  Most important part of song
 
 
127
 
128
  [Verse]
129
  Second verse develops story
 
 
 
130
 
131
  [Bridge]
132
  Contrast section here
133
  Different melody or perspective
 
134
 
135
  [Chorus]
136
  Main hook repeated
 
 
137
 
138
  [Outro]
139
  Closing the song
 
140
  ```
141
 
142
  ### KEY RULES:
143
  1. [Chorus] appears at least 2-3 times
144
  2. [Prechorus] builds tension before [Chorus]
145
  3. [Bridge] provides contrast before final [Chorus]
146
+ 4. Use [Prechorus] NOT [Pre Chorus] (no space!)
 
147
  """
148
 
149
+ # ์˜ˆ์ œ ๊ฐ€์‚ฌ
150
+ EXAMPLE_LYRICS = """[Intro]
151
+
152
+ [Verse]
153
+ The sun creeps in across the floor
154
+ I hear the traffic outside the door
155
+ The coffee pot begins to hiss
156
+ It is another morning just like this
157
+
158
+ [Prechorus]
159
+ The world keeps spinning round and round
160
+ Feet are planted on the ground
161
+ I find my rhythm in the sound
162
+
163
+ [Chorus]
164
+ Every day the light returns
165
+ Every day the fire burns
166
+ We keep on walking down this street
167
+ Moving to the same steady beat
168
+ It is the ordinary magic that we meet
169
+
170
+ [Verse]
171
+ The hours tick deeply into noon
172
+ Chasing shadows, chasing the moon
173
+ Work is done and the lights go low
174
+ Watching the city start to glow
175
+
176
+ [Bridge]
177
+ It is not always easy, not always bright
178
+ Sometimes we wrestle with the night
179
+ But we make it to the morning light
180
+
181
+ [Chorus]
182
+ Every day the light returns
183
+ Every day the fire burns
184
+ We keep on walking down this street
185
+ Moving to the same steady beat
186
+
187
+ [Outro]
188
+ Just another day
189
+ Every single day"""
190
+
191
+ EXAMPLE_TAGS = "piano,happy,uplifting,pop"
192
+
193
+ # ์˜ˆ์ œ ํ”„๋กฌํ”„ํŠธ (ํƒœ๊ทธ ์„ธํŠธ)
194
+ EXAMPLE_TAG_PRESETS = {
195
+ "๐ŸŽน Piano Ballad": "piano,ballad,emotional,romantic,slow",
196
+ "๐ŸŽธ Rock Energetic": "guitar,drums,rock,energetic,powerful,fast",
197
+ "๐ŸŽท Jazz Smooth": "piano,saxophone,jazz,smooth,relaxed,romantic",
198
+ "๐ŸŽต K-Pop Dance": "synthesizer,drums,bass,kpop,energetic,upbeat,pop",
199
+ "๐ŸŒ™ Lo-Fi Chill": "piano,guitar,lofi,calm,dreamy,relaxed",
200
+ "๐ŸŽป Orchestral Epic": "strings,orchestra,cinematic,epic,powerful,dramatic",
201
+ "๐Ÿชฉ Disco Funk": "bass,drums,synthesizer,disco,funky,upbeat,dance",
202
+ "๐ŸŒฟ Folk Acoustic": "guitar,folk,acoustic,calm,peaceful,warm",
203
+ "๐Ÿ’ซ Electronic Dance": "synthesizer,drums,electronic,energetic,dance,upbeat",
204
+ "๐Ÿ˜ข Sad Melancholic": "piano,strings,sad,melancholic,slow,emotional",
205
+ "๐ŸŽ‰ Happy Uplifting": "piano,happy,uplifting,pop,bright,joyful",
206
+ "๐ŸŒ… Wedding Romantic": "piano,happy,wedding,synthesizer,romantic"
207
  }
208
 
 
 
 
209
 
210
+ # ============================================================
211
+ # ๐Ÿง  SOMA ์—์ด์ „ํŠธ ์‹œ์Šคํ…œ - ๊ฐ€์‚ฌ ์ƒ์„ฑ
212
+ # ============================================================
213
+
214
+ LYRICS_AGENTS = {
215
+ "lyricist": f"""You are a master lyricist optimized for HeartMuLa music generation.
216
 
217
  {HEARTMULA_LYRICS_STRUCTURE}
218
 
 
220
 
221
  CRITICAL RULES:
222
  1. Use HeartMuLa structure tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
223
+ 2. [Prechorus] (not [Pre Chorus]) - HeartMuLa format, NO SPACE!
224
  3. Ensure [Chorus] is the most memorable, singable part
225
  4. Include [Prechorus] to build tension before [Chorus]
226
  5. Add [Bridge] for emotional contrast
 
228
 
229
  Write lyrics that create the BEST POSSIBLE foundation for high-quality music generation.""",
230
 
231
+ "producer": f"""You are a music producer specializing in song structure optimization for HeartMuLa.
232
 
233
  {HEARTMULA_LYRICS_STRUCTURE}
234
 
 
236
 
237
  CRITICAL OPTIMIZATION RULES:
238
  1. Use HeartMuLa tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
239
+ 2. IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space!
240
+ 3. Verify the structure follows genre conventions
241
+ 4. Ensure proper tag sequence (Verseโ†’Prechorusโ†’Chorus)
242
+ 5. Check that [Chorus] appears at least 2-3 times
243
+ 6. Verify [Bridge] provides contrast before final chorus
 
244
 
245
  Output the restructured lyrics with OPTIMAL HeartMuLa tag placement.""",
246
 
247
  "emotion_director": f"""You are an emotion director for music production.
248
 
 
 
249
  Your task: Enhance emotional impact through STRATEGIC tag content.
250
 
251
  EMOTIONAL MAPPING BY TAG:
 
254
  - [Prechorus]: Rising tension, excitement
255
  - [Chorus]: Peak emotion, catharsis
256
  - [Bridge]: Vulnerability, reflection, contrast
 
257
  - [Outro]: Resolution, lingering feeling
258
 
259
  OPTIMIZATION RULES:
 
261
  2. [Chorus] must deliver the strongest emotional punch
262
  3. [Bridge] should offer new emotional perspective
263
  4. [Prechorus] should create anticipation for [Chorus]
 
264
 
265
  Enhance the lyrics for MAXIMUM emotional resonance.""",
266
 
267
+ "final_editor": f"""You are the final editor for HeartMuLa music production.
 
 
268
 
269
  Your task: Output PERFECTLY FORMATTED, production-ready lyrics.
270
 
 
272
  1. Output ONLY the actual lyrics with structure tags
273
  2. Use HeartMuLa tags EXACTLY:
274
  [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
275
+ 3. IMPORTANT: [Prechorus] NOT [Pre Chorus] - no space!
276
+ 4. DO NOT include English translations in parentheses
277
+ 5. DO NOT include explanations, descriptions, or markdown
278
+ 6. DO NOT include lines starting with * or >
279
+ 7. For [Inst] or [Solo] sections, write ONLY the tag
280
+ 8. Ensure MINIMUM 6-8 different sections
281
+ 9. Verify [Chorus] appears at least 2 times
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
  OUTPUT ONLY CLEAN LYRICS WITH OPTIMAL TAG STRUCTURE."""
284
  }
285
 
286
+ # ํƒœ๊ทธ ์ƒ์„ฑ ์—์ด์ „ํŠธ
287
+ TAG_AGENT = f"""You are a style tag generator for HeartMuLa.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  {HEARTMULA_TAG_GUIDE}
290
 
 
294
  1. Tags must be comma-separated WITHOUT spaces: tag1,tag2,tag3
295
  2. Use lowercase with underscores for multi-word: electric_guitar, hip_hop
296
  3. Include 5-8 tags covering: instrument, mood, genre, tempo
297
+ 4. Be specific: "piano" not "keyboard", "guitar" not "strings"
 
298
 
299
  OUTPUT FORMAT (example):
300
+ piano,synthesizer,happy,pop,upbeat,romantic
 
 
301
 
302
+ Output ONLY the comma-separated tags, nothing else."""
303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ # ============================================================
306
+ # ๐Ÿ”ง ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
307
+ # ============================================================
308
 
309
  def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") -> str:
310
+ """Groq API ํ˜ธ์ถœ"""
311
  try:
312
  client = Groq(api_key=api_key)
313
 
 
326
  stream=False
327
  )
328
 
329
+ if completion and completion.choices and completion.choices[0].message:
330
+ return completion.choices[0].message.content or "Error: Empty response"
331
+ return "Error: Invalid response"
 
 
 
 
 
 
 
332
 
333
  except Exception as e:
334
  return f"Error: {str(e)}"
 
338
  """๊ฐ€์‚ฌ ํ›„์ฒ˜๋ฆฌ - HeartMuLa ํฌ๋งท ์ตœ์ ํ™”"""
339
  import re
340
 
341
+ if not text or not isinstance(text, str):
 
 
 
 
342
  return ""
343
 
344
  lines = text.split('\n')
 
349
  cleaned_lines.append('')
350
  continue
351
 
352
+ # ์Šคํ‚ต ํŒจํ„ด
353
+ skip_patterns = [r'^\s*\*', r'^\s*>', r'^\s*---', r'^\s*###', r'^\s*\*\*.*\*\*\s*$']
354
+ if any(re.match(p, line) for p in skip_patterns):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  continue
356
 
357
+ # ์ •๋ฆฌ
358
  line = re.sub(r'\s*\([A-Za-z].*?\)\s*$', '', line)
359
  line = re.sub(r'\*\*(.*?)\*\*', r'\1', line)
360
 
361
  # HeartMuLa ํƒœ๊ทธ ์ •๊ทœํ™”
362
  line = re.sub(r'\[Pre.?Chorus\]', '[Prechorus]', line, flags=re.IGNORECASE)
363
+ line = re.sub(r'\[Post.?Chorus\]', '[Chorus]', line, flags=re.IGNORECASE)
364
  line = re.sub(r'\[Build.?Up\]', '[Prechorus]', line, flags=re.IGNORECASE)
365
  line = re.sub(r'\[Break\]', '[Interlude]', line, flags=re.IGNORECASE)
 
366
 
367
  if line.strip():
368
  cleaned_lines.append(line.strip())
 
376
  def clean_tags(tags: str) -> str:
377
  """ํƒœ๊ทธ ์ •๋ฆฌ - HeartMuLa ํฌ๋งท (์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ)"""
378
  if not tags:
379
+ return "piano,happy,pop"
380
 
 
381
  tags = tags.lower().strip()
 
 
382
  tags = tags.replace(', ', ',').replace(' ,', ',').replace(' ', ' ')
383
+ tags = tags.replace(' ', '_').replace(',,', ',')
 
 
384
  tags = tags.strip(',')
385
 
 
386
  tag_list = [t.strip() for t in tags.split(',') if t.strip()]
387
  unique_tags = list(dict.fromkeys(tag_list))
388
 
389
+ return ','.join(unique_tags) if unique_tags else "piano,happy,pop"
390
 
391
 
392
+ # ============================================================
393
+ # ๐ŸŽต ๋ฉ”์ธ ํ•จ์ˆ˜๋“ค
394
+ # ============================================================
395
+
396
  @spaces.GPU(duration=120)
397
  def generate_lyrics_soma(
398
  api_key: str, theme: str, genre: str, mood: str,
399
+ language: str, vocal_type: str, additional: str,
400
+ progress=gr.Progress()
401
  ):
402
+ """SOMA ๊ฐ€์‚ฌ ์ƒ์„ฑ"""
403
  if not api_key or not api_key.strip():
404
  return "โŒ Groq API Key ํ•„์š”", "", "", "", ""
405
  if not theme or not theme.strip():
406
  return "โŒ ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”", "", "", "", ""
407
 
408
+ base_prompt = f"""Create PROFESSIONAL lyrics optimized for HeartMuLa:
409
  - Theme: {theme}
410
  - Genre: {genre}
411
  - Mood: {mood}
 
413
  - Vocal Type: {vocal_type}
414
  {f'- Additional: {additional}' if additional else ''}
415
 
416
+ USE HeartMuLa STRUCTURE TAGS:
417
+ [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
418
+
419
+ IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space!
420
 
421
+ REQUIRED STRUCTURE:
422
  1. [Intro] - Set the mood
423
  2. [Verse] x2-3 - Tell the story
424
+ 3. [Prechorus] - Build tension
425
  4. [Chorus] x2-3 - Main hook (MOST important!)
426
  5. [Bridge] - Emotional contrast
427
+ 6. [Outro] - Conclusion"""
 
 
 
 
 
 
428
 
429
  try:
430
  progress(0.2, desc="๐ŸŽค ์ž‘์‚ฌ๊ฐ€ - ์ดˆ์•ˆ ์ž‘์„ฑ...")
431
  draft = call_groq(api_key, LYRICS_AGENTS["lyricist"], base_prompt)
432
  if draft.startswith("Error:"):
433
+ return f"โŒ {draft}", "", "", "", ""
434
 
435
  progress(0.4, desc="๐ŸŽน ํ”„๋กœ๋“€์„œ - ๊ตฌ์กฐ ์ตœ์ ํ™”...")
436
  structured = call_groq(api_key, LYRICS_AGENTS["producer"],
437
+ f"Optimize for {genre}. Use [Prechorus] not [Pre Chorus]!", draft)
438
  if structured.startswith("Error:"):
439
+ return f"โŒ {structured}", draft, "", "", ""
440
 
441
  progress(0.6, desc="๐Ÿ’ซ ๊ฐ์„ฑ ๋””๋ ‰ํ„ฐ - ๏ฟฝ๏ฟฝ๏ฟฝ์ • ๊ฐ•ํ™”...")
442
  emotional = call_groq(api_key, LYRICS_AGENTS["emotion_director"],
443
+ f"Enhance for {mood}.", structured)
444
  if emotional.startswith("Error:"):
445
+ return f"โŒ {emotional}", draft, structured, "", ""
446
 
447
+ progress(0.8, desc="โœจ ์ตœ์ข… ํŽธ์ง‘...")
448
  final = call_groq(api_key, LYRICS_AGENTS["final_editor"],
449
+ "Output ONLY clean lyrics. Use [Prechorus] not [Pre Chorus]!", emotional)
450
  if final.startswith("Error:"):
451
+ return f"โŒ {final}", draft, structured, emotional, ""
452
 
453
  final_cleaned = clean_lyrics(final)
454
 
 
456
  return "โœ… ๊ฐ€์‚ฌ ์ƒ์„ฑ ์™„๋ฃŒ!", draft, structured, emotional, final_cleaned
457
 
458
  except Exception as e:
459
+ return f"โŒ ์˜ˆ์™ธ: {str(e)}", "", "", "", ""
460
 
461
 
462
  @spaces.GPU(duration=60)
463
  def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str, vocal_type: str, additional: str):
464
+ """๋น ๋ฅธ ๊ฐ€์‚ฌ ์ƒ์„ฑ"""
465
  if not api_key or not api_key.strip():
466
+ return "โŒ API Key ํ•„์š”"
467
  if not theme or not theme.strip():
468
  return "โŒ ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
469
 
470
+ prompt = f"""Create song lyrics for HeartMuLa:
471
  - Theme: {theme}
472
  - Genre: {genre}
473
  - Mood: {mood}
 
475
  - Vocal: {vocal_type}
476
  {f'- Special: {additional}' if additional else ''}
477
 
478
+ USE STRUCTURE (8-10 sections):
479
  [Intro] โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus] โ†’ [Verse] โ†’ [Prechorus] โ†’ [Chorus] โ†’ [Bridge] โ†’ [Chorus] โ†’ [Outro]
480
 
481
+ IMPORTANT: Use [Prechorus] NOT [Pre Chorus]!
 
 
 
 
 
482
 
483
+ OUTPUT ONLY lyrics with tags. NO explanations."""
484
 
485
  try:
486
+ result = call_groq(api_key, f"""Professional songwriter for HeartMuLa.
 
487
  {HEARTMULA_LYRICS_STRUCTURE}
488
+ Output ONLY clean lyrics.""", prompt)
489
 
490
  if result.startswith("Error:"):
491
+ return f"โŒ {result}"
492
 
493
  return clean_lyrics(result)
494
  except Exception as e:
495
+ return f"โŒ {str(e)}"
496
 
497
 
498
+ @spaces.GPU(duration=60)
499
+ def generate_tags_ai(api_key: str, genre: str, mood: str, instruments: str):
500
+ """AI ํƒœ๊ทธ ์ƒ์„ฑ"""
501
+ if not api_key:
502
  return "piano,happy,pop"
503
 
504
  prompt = f"""Generate HeartMuLa style tags:
505
  - Genre: {genre}
506
  - Mood: {mood}
507
+ - Instruments hint: {instruments}
 
 
 
 
508
 
509
+ Output 5-8 comma-separated tags WITHOUT spaces.
510
+ Example: piano,synthesizer,happy,pop,upbeat"""
511
 
512
  try:
513
+ result = call_groq(api_key, TAG_AGENT, prompt)
514
  if result.startswith("Error:"):
515
  return "piano,happy,pop"
516
  return clean_tags(result)
 
518
  return "piano,happy,pop"
519
 
520
 
521
+ @spaces.GPU(duration=180)
522
+ def generate_music_heartmula(
523
+ lyrics: str,
524
+ tags: str,
525
+ max_duration_seconds: int,
526
+ temperature: float,
527
+ topk: int,
528
+ cfg_scale: float,
529
+ progress=gr.Progress(track_tqdm=True),
530
  ):
531
+ """HeartMuLa๋กœ ์Œ์•… ์ƒ์„ฑ"""
532
+ if not lyrics or not lyrics.strip():
533
+ raise gr.Error("โš ๏ธ ๊ฐ€์‚ฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
 
535
+ if not tags or not tags.strip():
536
+ raise gr.Error("โš ๏ธ ํƒœ๊ทธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”!")
537
 
538
+ # ํƒœ๊ทธ ์ •๋ฆฌ
539
+ tags = clean_tags(tags)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
 
541
+ # ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
542
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
543
+ output_path = f.name
544
+
545
+ max_audio_length_ms = max_duration_seconds * 1000
546
+
547
  try:
548
+ with torch.no_grad():
549
+ pipe(
550
+ {
551
+ "lyrics": lyrics,
552
+ "tags": tags,
553
+ },
554
+ max_audio_length_ms=max_audio_length_ms,
555
+ save_path=output_path,
556
+ topk=topk,
557
+ temperature=temperature,
558
+ cfg_scale=cfg_scale,
559
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
 
561
+ return output_path, f"โœ… ์Œ์•… ์ƒ์„ฑ ์™„๋ฃŒ! ({max_duration_seconds}์ดˆ, ํƒœ๊ทธ: {tags})"
562
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  except Exception as e:
564
+ return None, f"โŒ ์ƒ์„ฑ ์‹คํŒจ: {str(e)}"
565
 
566
 
567
+ def load_tag_preset(preset_name):
568
+ """ํƒœ๊ทธ ํ”„๋ฆฌ์…‹ ๋กœ๋“œ"""
569
+ if preset_name in EXAMPLE_TAG_PRESETS:
570
+ return EXAMPLE_TAG_PRESETS[preset_name]
571
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
 
573
 
574
  # ============================================================
575
+ # ๐ŸŽจ Comic Classic Theme CSS
576
  # ============================================================
577
 
578
  css = """
 
579
  @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
580
 
 
581
  .gradio-container {
582
  background-color: #FEF9C3 !important;
583
+ background-image: radial-gradient(#1F2937 1px, transparent 1px) !important;
 
584
  background-size: 20px 20px !important;
 
585
  font-family: 'Comic Neue', cursive, sans-serif !important;
586
  }
587
 
588
+ .huggingface-space-header, footer, .footer { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
 
 
 
 
 
 
 
 
590
  .header-title h1 {
591
  font-family: 'Bangers', cursive !important;
592
  color: #1F2937 !important;
593
+ font-size: 3rem !important;
 
594
  text-align: center !important;
595
+ text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important;
 
 
 
596
  letter-spacing: 3px !important;
597
  -webkit-text-stroke: 2px #1F2937 !important;
598
  }
599
 
 
 
 
 
 
 
 
 
 
 
 
600
  .section-title {
601
  font-family: 'Bangers', cursive !important;
602
  color: #1F2937 !important;
603
+ font-size: 1.6rem !important;
604
  border-bottom: 4px solid #3B82F6 !important;
605
  padding-bottom: 8px !important;
 
606
  text-shadow: 2px 2px 0px #FACC15 !important;
607
  }
608
 
609
+ .gr-panel, .gr-box, .block {
 
 
 
 
 
610
  background: #FFFFFF !important;
611
  border: 3px solid #1F2937 !important;
612
  border-radius: 8px !important;
613
  box-shadow: 6px 6px 0px #1F2937 !important;
 
614
  }
615
 
616
+ .gr-panel:hover, .block:hover {
 
617
  transform: translate(-2px, -2px) !important;
618
  box-shadow: 8px 8px 0px #1F2937 !important;
619
  }
620
 
621
+ textarea, input[type="text"], input[type="password"] {
 
 
 
 
622
  background: #FFFFFF !important;
623
  border: 3px solid #1F2937 !important;
624
  border-radius: 8px !important;
 
625
  font-family: 'Comic Neue', cursive !important;
 
626
  font-weight: 700 !important;
 
627
  }
628
 
629
+ textarea:focus, input:focus {
 
 
 
630
  border-color: #3B82F6 !important;
631
  box-shadow: 4px 4px 0px #3B82F6 !important;
 
 
 
 
 
 
632
  }
633
 
634
+ .gr-button-primary, button.primary {
 
 
 
635
  background: #3B82F6 !important;
636
  border: 3px solid #1F2937 !important;
637
  border-radius: 8px !important;
638
  color: #FFFFFF !important;
639
  font-family: 'Bangers', cursive !important;
 
640
  font-size: 1.2rem !important;
641
  letter-spacing: 2px !important;
 
642
  box-shadow: 5px 5px 0px #1F2937 !important;
 
643
  text-shadow: 1px 1px 0px #1F2937 !important;
644
  }
645
 
646
+ .gr-button-primary:hover {
 
 
647
  background: #2563EB !important;
648
  transform: translate(-2px, -2px) !important;
649
  box-shadow: 7px 7px 0px #1F2937 !important;
650
  }
651
 
652
+ .gr-button-secondary, button.secondary {
 
 
 
 
 
 
 
 
 
653
  background: #EF4444 !important;
654
  border: 3px solid #1F2937 !important;
 
655
  color: #FFFFFF !important;
656
  font-family: 'Bangers', cursive !important;
 
 
 
657
  box-shadow: 4px 4px 0px #1F2937 !important;
 
 
658
  }
659
 
 
 
 
 
 
 
 
 
660
  .generate-btn {
661
  background: #10B981 !important;
662
  border: 3px solid #1F2937 !important;
 
663
  color: #FFFFFF !important;
664
  font-family: 'Bangers', cursive !important;
665
+ font-size: 1.4rem !important;
 
 
666
  box-shadow: 5px 5px 0px #1F2937 !important;
 
667
  }
668
 
669
  .generate-btn:hover {
670
  background: #059669 !important;
671
  transform: translate(-2px, -2px) !important;
 
672
  }
673
 
 
674
  .gr-accordion {
675
  background: #FACC15 !important;
676
  border: 3px solid #1F2937 !important;
 
677
  box-shadow: 4px 4px 0px #1F2937 !important;
678
  }
679
 
680
+ .tag-input textarea {
681
+ background: #FEF3C7 !important;
682
+ border: 3px dashed #F59E0B !important;
683
+ font-family: 'Courier New', monospace !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  }
685
 
686
+ .status-box textarea {
 
 
 
687
  background: #1F2937 !important;
688
  color: #10B981 !important;
689
  font-family: 'Courier New', monospace !important;
690
  border: 3px solid #10B981 !important;
 
 
691
  }
692
 
693
+ label {
 
 
694
  color: #1F2937 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
  font-family: 'Comic Neue', cursive !important;
696
  font-weight: 700 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  }
698
 
699
+ ::-webkit-scrollbar { width: 12px; }
700
+ ::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; }
701
+ ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; }
702
+ ::selection { background: #FACC15; color: #1F2937; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  """
704
 
705
+
706
  # ============================================================
707
+ # ๐ŸŽจ Gradio UI
708
  # ============================================================
709
 
710
+ with gr.Blocks(css=css, title="๐ŸŽต SOMA Music Studio - HeartMuLa") as demo:
711
 
712
+ # Header
713
  gr.HTML("""
714
  <div style="text-align: center; margin: 20px 0 10px 0;">
715
+ <a href="https://huggingface.co/HeartMuLa" target="_blank">
716
+ <img src="https://img.shields.io/badge/๐ŸŽต_HeartMuLa-oss--3B-ff6b6b?style=for-the-badge&labelColor=1F2937" alt="HeartMuLa">
717
  </a>
718
+ <a href="https://github.com/HeartMuLa/heartlib" target="_blank" style="margin-left: 10px;">
719
+ <img src="https://img.shields.io/badge/๐Ÿ“ฆ_heartlib-GitHub-3B82F6?style=for-the-badge&labelColor=1F2937" alt="GitHub">
720
  </a>
721
  </div>
722
  """)
723
 
724
+ gr.Markdown("# ๐ŸŽต SOMA MUSIC STUDIO ๐ŸŽถ", elem_classes="header-title")
 
 
 
725
 
726
  gr.Markdown("""
727
+ <p style="text-align: center; font-family: 'Comic Neue', cursive; font-size: 1.1rem; font-weight: 700; color: #1F2937;">
728
+ ๐Ÿ’ซ HeartMuLa-oss-3B + SOMA Multi-Agent = ์ตœ๊ณ  ํ’ˆ์งˆ AI ์Œ์•… ์ƒ์„ฑ ๐Ÿ’ซ
729
+ </p>
730
  """)
731
 
732
+ # API Key
733
+ with gr.Accordion("๐Ÿ”‘ Groq API Key (๊ฐ€์‚ฌ ์ƒ์„ฑ์šฉ)", open=False):
734
+ GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
735
+ groq_key = gr.Textbox(
736
+ label="Groq API Key",
737
+ type="password",
738
+ value=GROQ_KEY,
739
+ placeholder="gsk_..." if not GROQ_KEY else "โœ… Secret ๋กœ๋“œ๋จ",
740
+ interactive=not bool(GROQ_KEY)
741
+ )
 
 
 
 
 
 
 
 
 
 
742
 
743
  with gr.Row(equal_height=False):
744
  # ========== ์ขŒ์ธก: ๊ฐ€์‚ฌ ์ƒ์„ฑ ==========
 
747
 
748
  theme_input = gr.Textbox(
749
  label="๐ŸŽฏ ๋…ธ๋ž˜ ์ฃผ์ œ",
750
+ placeholder="์˜ˆ: ์ด๋ณ„ ํ›„ ์„ฑ์žฅ, ๊ฟˆ์„ ํ–ฅํ•œ ๋„์ „, ์‚ฌ๋ž‘์˜ ๊ณ ๋ฐฑ...",
751
  lines=2
752
  )
753
 
 
755
  lyrics_genre = gr.Dropdown(
756
  label="๐ŸŽธ ์žฅ๋ฅด",
757
  choices=["K-Pop", "Pop", "R&B", "Hip-Hop", "Ballad", "Rock",
758
+ "EDM", "Jazz", "Folk", "Disco", "Cinematic"],
759
  value="K-Pop"
760
  )
761
  lyrics_mood = gr.Dropdown(
762
  label="๐Ÿ’ซ ๋ถ„์œ„๊ธฐ",
763
  choices=["Empowering", "Melancholic", "Joyful", "Romantic",
764
+ "Energetic", "Dreamy", "Nostalgic", "Peaceful", "Confident"],
 
765
  value="Empowering"
766
  )
767
 
 
769
  lyrics_language = gr.Dropdown(
770
  label="๐ŸŒ ์–ธ์–ด",
771
  choices=["English", "Korean", "Korean + English", "Japanese"],
772
+ value="English"
773
  )
774
  lyrics_vocal_type = gr.Dropdown(
775
+ label="๐ŸŽค ๋ณด์ปฌ",
776
+ choices=["Solo Female", "Solo Male", "Duet", "Group/Choir"],
777
+ value="Solo Female"
 
778
  )
779
 
780
  lyrics_additional = gr.Textbox(
781
  label="โœจ ์ถ”๊ฐ€ ์ง€์‹œ (์„ ํƒ)",
782
+ placeholder="ํŠน๋ณ„ ์š”์ฒญ...",
783
  lines=1
784
  )
785
 
786
  with gr.Row():
787
+ quick_btn = gr.Button("โšก QUICK", variant="secondary")
788
  soma_lyrics_btn = gr.Button("๐Ÿง  SOMA GENERATE", variant="primary")
789
 
790
  lyrics_status = gr.Textbox(label="๐Ÿ“Š ์ƒํƒœ", interactive=False, max_lines=1, elem_classes="status-box")
791
 
792
  with gr.Accordion("๐Ÿ” SOMA ์ž‘์—… ๊ณผ์ •", open=False):
793
+ step1_out = gr.Textbox(label="1๏ธโƒฃ ์ž‘์‚ฌ๊ฐ€", lines=3, interactive=False)
794
+ step2_out = gr.Textbox(label="2๏ธโƒฃ ํ”„๋กœ๋“€์„œ", lines=3, interactive=False)
795
+ step3_out = gr.Textbox(label="3๏ธโƒฃ ๊ฐ์„ฑ", lines=3, interactive=False)
796
+ step4_out = gr.Textbox(label="4๏ธโƒฃ ์ตœ์ข…", lines=3, interactive=False)
 
 
 
797
 
798
  final_lyrics = gr.Textbox(
799
+ label="โœ๏ธ ๊ฐ€์‚ฌ (ํŽธ์ง‘ ๊ฐ€๋Šฅ)",
800
+ lines=16,
801
+ value=EXAMPLE_LYRICS,
802
+ placeholder="๊ฐ€์‚ฌ๊ฐ€ ์—ฌ๊ธฐ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค..."
803
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
 
805
  # ========== ์šฐ์ธก: ์Œ์•… ์ƒ์„ฑ ==========
806
  with gr.Column(scale=1, min_width=400):
807
  gr.Markdown("## ๐ŸŽต MUSIC GENERATOR", elem_classes="section-title")
808
 
809
+ # ํƒœ๊ทธ ํ”„๋ฆฌ์…‹
810
+ gr.Markdown("### ๐Ÿท๏ธ ํƒœ๊ทธ ํ”„๋ฆฌ์…‹ (ํด๋ฆญํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ)")
811
+ tag_preset = gr.Dropdown(
812
+ label="ํ”„๋ฆฌ์…‹ ์„ ํƒ",
813
+ choices=list(EXAMPLE_TAG_PRESETS.keys()),
814
+ value=None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  )
816
 
817
+ tags_input = gr.Textbox(
818
+ label="๐ŸŽน ์Šคํƒ€์ผ ํƒœ๊ทธ (์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ)",
819
+ value=EXAMPLE_TAGS,
820
+ placeholder="piano,happy,uplifting,pop",
821
+ lines=1,
822
+ elem_classes="tag-input"
823
  )
824
 
825
  with gr.Row():
826
+ ai_tag_btn = gr.Button("๐Ÿค– AI ํƒœ๊ทธ ์ƒ์„ฑ", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
827
 
828
+ with gr.Accordion("โš™๏ธ ์ƒ์„ฑ ์„ค์ •", open=True):
829
+ max_duration = gr.Slider(
830
+ minimum=30, maximum=240, value=120, step=10,
831
+ label="โฑ๏ธ ์ตœ๋Œ€ ๊ธธ์ด (์ดˆ)",
832
+ info="30~240์ดˆ"
 
833
  )
834
+
835
+ with gr.Row():
836
+ temperature = gr.Slider(
837
+ minimum=0.1, maximum=2.0, value=1.0, step=0.1,
838
+ label="๐ŸŒก๏ธ Temperature",
839
+ info="๋†’์„์ˆ˜๋ก ์ฐฝ์˜์ "
840
+ )
841
+ topk = gr.Slider(
842
+ minimum=1, maximum=100, value=50, step=1,
843
+ label="๐ŸŽฏ Top-K"
844
+ )
845
+
846
+ cfg_scale = gr.Slider(
847
+ minimum=1.0, maximum=3.0, value=1.5, step=0.1,
848
+ label="๐Ÿ“ CFG Scale",
849
+ info="Classifier-free guidance"
850
  )
851
 
852
+ generate_btn = gr.Button(
853
+ "๐ŸŽถ GENERATE MUSIC!",
854
+ variant="primary",
855
+ size="lg",
856
+ elem_classes="generate-btn"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  )
858
 
 
 
 
 
 
 
 
 
 
 
 
859
  music_status = gr.Textbox(label="๐Ÿ“Š ์ƒํƒœ", interactive=False, max_lines=1, elem_classes="status-box")
860
+ audio_output = gr.Audio(label="๐ŸŽง ์ƒ์„ฑ๋œ ์Œ์•…", type="filepath")
861
+
862
+ gr.Markdown("""
863
+ ### ๐Ÿ’ก Tips
864
+ - **๊ตฌ์กฐ ํƒœ๊ทธ:** [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Outro]
865
+ - **์Šคํƒ€์ผ ํƒœ๊ทธ:** piano,happy,uplifting,pop (์ฝค๋งˆ ๊ตฌ๋ถ„, ๊ณต๋ฐฑ ์—†์Œ!)
866
+ - **Temperature:** ๋†’์œผ๋ฉด ์ฐฝ์˜์ , ๋‚ฎ์œผ๋ฉด ์ผ๊ด€์„ฑ
867
+ - **๊ธธ์ด:** ๊ธด ๊ณก์€ ์‹œ๊ฐ„์ด ๋” ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค
868
+ """)
869
+
870
+ # ๊ฐ€์ด๋“œ
871
+ with gr.Accordion("๐Ÿ“– HeartMuLa ๊ฐ€์ด๋“œ", open=False):
872
  gr.Markdown(f"""
873
  {HEARTMULA_TAG_GUIDE}
874
 
875
  ---
876
 
877
  {HEARTMULA_LYRICS_STRUCTURE}
878
+
879
+ ---
880
+
881
+ **Model:** [HeartMuLa-oss-3B](https://huggingface.co/HeartMuLa/HeartMuLa-oss-3B) |
882
+ **Paper:** [arXiv](https://arxiv.org/abs/2601.10547) |
883
+ **Code:** [GitHub](https://github.com/HeartMuLa/heartlib)
884
+
885
+ *Licensed under Apache 2.0*
886
  """)
887
 
888
  # ========== Event Handlers ==========
889
 
890
+ # ํƒœ๊ทธ ํ”„๋ฆฌ์…‹ ๋กœ๋“œ
891
+ tag_preset.change(
892
+ fn=load_tag_preset,
893
+ inputs=[tag_preset],
894
+ outputs=[tags_input]
895
+ )
896
+
897
+ # AI ํƒœ๊ทธ ์ƒ์„ฑ
898
+ ai_tag_btn.click(
899
+ fn=generate_tags_ai,
900
+ inputs=[groq_key, lyrics_genre, lyrics_mood, tags_input],
901
+ outputs=[tags_input]
902
  )
903
 
904
  # ๋น ๋ฅธ ๊ฐ€์‚ฌ ์ƒ์„ฑ
 
919
  outputs=[final_lyrics]
920
  )
921
 
922
+ # ์Œ์•… ์ƒ์„ฑ (HeartMuLa)
923
+ generate_btn.click(
924
+ fn=generate_music_heartmula,
925
+ inputs=[final_lyrics, tags_input, max_duration, temperature, topk, cfg_scale],
926
+ outputs=[audio_output, music_status]
 
 
 
 
 
 
 
927
  )
928
 
929