File size: 5,223 Bytes
b5a56ee
d171350
fa8dc8b
d171350
fa8dc8b
 
d171350
 
 
 
 
 
 
 
 
b405196
d171350
 
fa8dc8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d171350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b5a56ee
d171350
 
b5a56ee
d171350
b5a56ee
d171350
 
 
fa8dc8b
d171350
 
 
 
 
 
 
 
 
 
b5a56ee
d171350
 
 
 
fa8dc8b
 
d171350
fa8dc8b
d171350
 
 
 
 
 
 
 
fa8dc8b
 
 
 
 
 
d171350
 
b5a56ee
 
 
 
 
 
 
 
 
d171350
 
 
 
0a6e0ec
 
fa8dc8b
0a6e0ec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""midmid3: Guitar Hero Chart Generator Demo"""

import json
import os
from pathlib import Path

import gradio as gr

# ZeroGPU: import spaces if available (no-op locally)
try:
    import spaces
    ON_ZEROGPU = True
except ImportError:
    ON_ZEROGPU = False

from pipeline import generate_chart
from visualizer import build_visualizer_html

EXAMPLES_DIR = Path(__file__).parent / "examples"


def _load_example():
    """Load pre-computed demo - no GPU needed."""
    chart_path = EXAMPLES_DIR / "hot_mess.json"
    zip_path = EXAMPLES_DIR / "Hot Mess - Friday Pilots Club.zip"
    audio_path = EXAMPLES_DIR / "hot_mess_input.ogg"

    with open(chart_path) as f:
        chart_json = json.load(f)

    viz_html = build_visualizer_html(chart_json)
    return (
        str(audio_path),              # audio_input
        "Hot Mess",                   # title
        "Friday Pilots Club",         # artist
        "",                           # album
        "2022",                       # year
        "rock",                       # genre
        viz_html,                     # viz_output
        str(zip_path),                # zip_output
    )


PLACEHOLDER_HTML = """
<div style="font-family: system-ui, sans-serif; background: #111; border-radius: 12px;
    padding: 60px 20px; text-align: center; color: #666; max-width: 900px; margin: 0 auto;">
  <div style="font-size: 48px; margin-bottom: 12px;">🎸</div>
  <div style="font-size: 16px;">Upload a song and hit Generate to see your chart here</div>
</div>
"""


def _generate_wrapper(audio_path, title, artist, album, year, genre, progress=gr.Progress()):
    """Gradio-facing wrapper with validation and progress."""
    if not audio_path:
        raise gr.Error("Please upload an audio file.")
    if not title or not title.strip():
        raise gr.Error("Song title is required.")
    if not artist or not artist.strip():
        raise gr.Error("Artist name is required.")

    zip_path, chart_json = generate_chart(
        audio_path=audio_path,
        title=title.strip(),
        artist=artist.strip(),
        album=album.strip() if album else "",
        year=year.strip() if year else "",
        genre=genre.strip() if genre else "rock",
        progress_cb=progress,
    )

    html = build_visualizer_html(chart_json)
    return html, zip_path


# Apply ZeroGPU decorator if running on HF Spaces
if ON_ZEROGPU:
    _generate_wrapper = spaces.GPU(duration=180)(_generate_wrapper)


# --- UI ---
with gr.Blocks(
    title="midmid3: Guitar Hero Chart Generator",
) as demo:
    gr.Markdown(
        "# midmid3: Guitar Hero Chart Generator\n"
        "Upload a song, get a playable chart with 4 difficulty levels. "
        "Preview it here, then download the mod folder ready for GHWT:DE."
    )

    with gr.Row():
        with gr.Column(scale=1, min_width=280):
            audio_input = gr.Audio(
                label="Upload audio",
                type="filepath",
                sources=["upload"],
            )
            title_input = gr.Textbox(label="Song title *", placeholder="e.g. Through the Fire and Flames")
            artist_input = gr.Textbox(label="Artist *", placeholder="e.g. DragonForce")

            with gr.Row():
                album_input = gr.Textbox(label="Album", placeholder="(optional)")
                year_input = gr.Textbox(label="Year", placeholder="2026")

            genre_input = gr.Textbox(label="Genre", placeholder="rock", value="rock")

            generate_btn = gr.Button("Generate Chart", variant="primary", elem_id="generate-btn")
            example_btn = gr.Button("Load Example", variant="secondary", size="sm")
            zip_output = gr.File(label="Download song package (.zip)")

        with gr.Column(scale=3):
            viz_output = gr.HTML(value=PLACEHOLDER_HTML, label="Chart Preview")

    generate_btn.click(
        fn=_generate_wrapper,
        inputs=[audio_input, title_input, artist_input, album_input, year_input, genre_input],
        outputs=[viz_output, zip_output],
    )

    example_btn.click(
        fn=_load_example,
        inputs=[],
        outputs=[audio_input, title_input, artist_input, album_input, year_input, genre_input, viz_output, zip_output],
    )

    gr.Markdown(
        "---\n"
        "*Powered by [midmid3](https://huggingface.co/markury/midmid3-19m-0326) — "
        "a 19M-parameter masked-prediction transformer by Markury.*"
    )
    gr.HTML(
        '<div style="display:flex; justify-content:center; gap:8px; padding:8px 0;">'
        '<a href="https://github.com/markuryy/midmid"><img src="https://img.shields.io/badge/GitHub-midmid-181717?logo=github&logoColor=white" alt="GitHub"></a>'
        '<a href="https://huggingface.co/markury/midmid3-19m-0326"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Model-midmid3--19m--0326-yellow" alt="Model"></a>'
        '<a href="https://huggingface.co/spaces/markury/midmid3"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Demo-midmid3-blue" alt="Demo"></a>'
        '</div>'
    )


if __name__ == "__main__":
    demo.launch(
        theme=gr.themes.Base(primary_hue="purple", neutral_hue="gray"),
        css="#generate-btn { min-height: 48px; font-size: 16px; }",
    )