File size: 7,701 Bytes
05c8865
 
 
 
41089ff
 
 
f8bb6ec
 
 
 
 
 
 
 
 
 
 
 
05c8865
 
 
41089ff
 
05c8865
 
41089ff
f8bb6ec
 
 
05c8865
f8bb6ec
 
 
 
 
 
 
 
05c8865
41089ff
f8bb6ec
 
 
41089ff
f8bb6ec
 
05c8865
41089ff
 
f8bb6ec
41089ff
 
 
 
 
 
 
f8bb6ec
41089ff
f8bb6ec
41089ff
 
 
f8bb6ec
 
41089ff
f8bb6ec
 
41089ff
 
f8bb6ec
 
41089ff
f8bb6ec
 
41089ff
9d5b0ac
05c8865
f8bb6ec
9d5b0ac
05c8865
 
f8bb6ec
 
 
 
 
 
 
 
 
 
 
9d5b0ac
 
 
 
f8bb6ec
9d5b0ac
 
f8bb6ec
 
 
 
 
 
 
 
 
05c8865
f8bb6ec
 
 
 
05c8865
f8bb6ec
 
 
05c8865
f8bb6ec
 
 
 
05c8865
 
 
f8bb6ec
 
 
41089ff
 
 
05c8865
 
f8bb6ec
 
 
 
 
 
 
 
 
41089ff
05c8865
 
f8bb6ec
05c8865
f8bb6ec
05c8865
 
f8bb6ec
05c8865
 
f8bb6ec
 
 
 
05c8865
 
 
 
f8bb6ec
 
9d5b0ac
05c8865
 
 
 
 
 
f8bb6ec
05c8865
 
 
f8bb6ec
 
05c8865
 
 
 
 
 
 
41089ff
05c8865
 
 
f8bb6ec
41089ff
05c8865
 
 
 
f8bb6ec
 
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import gradio as gr
from midi2audio import FluidSynth
import os
import tempfile
import zipfile
import glob
import shutil
import logging

# --- Setup pretty logging ---
# This will make console logs much cleaner and more informative
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - [%(levelname)s] - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# --- End logging setup ---


# --- Configuration ---
SOUNDFONT_FILE = "timbres_of_heaven.sf2"
EXAMPLES_ZIP = "examples.zip"
EXAMPLES_DIR = "midi_examples"
# --- End Configuration ---

def check_fluidsynth_dependency():
    """
    Checks if FluidSynth is installed and returns a status message for the UI.
    """
    if shutil.which("fluidsynth") is None:
        logger.warning("'fluidsynth' command not found in system PATH.")
        logger.warning("This app will fail if not run in the provided Docker container.")
        logger.warning("If running locally, please install FluidSynth.")
        return ("⚠️ **Warning:** 'fluidsynth' command not found. "
                "The app may not work unless run via Docker.")
    else:
        logger.info("'fluidsynth' dependency check passed.")
        return "✅ FluidSynth dependency check passed."

def extract_examples():
    """
    Extracts examples.zip and returns a list of files and a status message.
    """
    if not os.path.exists(EXAMPLES_ZIP):
        logger.warning(f"'{EXAMPLES_ZIP}' not found. No examples will be loaded.")
        return [], f"⚠️ **Warning:** `{EXAMPLES_ZIP}` not found. No examples will be loaded."

    if not os.path.exists(EXAMPLES_DIR):
        os.makedirs(EXAMPLES_DIR)
        logger.info(f"Created examples directory: {EXAMPLES_DIR}")
    
    try:
        with zipfile.ZipFile(EXAMPLES_ZIP, 'r') as zip_ref:
            zip_ref.extractall(EXAMPLES_DIR)
        
        midi_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.midi"), recursive=True)
        kar_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.kar"), recursive=True)
        mid_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.mid"), recursive=True) # <-- Added this line
        
        example_list = midi_files + kar_files + mid_files # <-- Updated this line
        example_paths = [[example] for example in example_list]
        
        if not example_paths:
            logger.warning(f"No .midi, .kar, or .mid files found in '{EXAMPLES_ZIP}'.") # <-- Updated this line
            return [], f"⚠️ **Warning:** No `.midi`, `.kar`, or `.mid` files found in `{EXAMPLES_ZIP}`." # <-- Updated this line
            
        logger.info(f"Loaded {len(example_paths)} examples from {EXAMPLES_ZIP}.")
        return example_paths, f"✅ Loaded {len(example_paths)} examples from `{EXAMPLES_ZIP}`."
        
    except zipfile.BadZipFile:
        logger.error(f"'{EXAMPLES_ZIP}' is not a valid zip file.")
        return [], f"⛔ **Error:** `{EXAMPLES_ZIP}` is not a valid zip file."
    except Exception as e:
        logger.error(f"Error extracting examples: {e}", exc_info=True)
        return [], f"⛔ **Error:** Could not extract examples. See console logs."

def render_midi(midi_file_path, progress=gr.Progress(track_tqdm=True)):
    """
    Renders a MIDI/KAR file to audio, now with progress updates and feedback.
    The input `midi_file_path` is always a string filepath.
    """
    try:
        # --- 1. Validation and Setup (Progress: 0% -> 20%) ---
        progress(0, desc="Starting render...")
        
        if not os.path.exists(SOUNDFONT_FILE):
            logger.error(f"SoundFont file not found at '{SOUNDFONT_FILE}'")
            raise gr.Error(
                f"SoundFont file not found! The app is looking for '{SOUNDFONT_FILE}'. "
                f"Please add it and restart."
            )
        progress(0.1, desc="SoundFont found. Checking input...")

        # Simplified logic: Input is always a filepath string
        if isinstance(midi_file_path, str):
            input_midi_path = midi_file_path
            logger.info(f"Processing input file: {input_midi_path}")
        else:
            logger.error(f"Invalid input file type: {type(midi_file_path)}")
            raise gr.Error(f"Invalid input file type: {type(midi_file_path)}")
        progress(0.2, desc="Input file OK. Preparing output...")

        # --- 2. Create Temp File (Progress: 20% -> 30%) ---
        with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_out:
            output_wav_path = tmp_out.name
        progress(0.3, desc="Temporary output file created.")

        # --- 3. Run FluidSynth (Progress: 30% -> 90%) ---
        logger.info(f"Initializing FluidSynth with {SOUNDFONT_FILE}")
        fs = FluidSynth(SOUNDFONT_FILE)
        
        progress(0.5, desc="Rendering audio... (This may take a moment)")
        
        # This is the main rendering step
        fs.midi_to_audio(input_midi_path, output_wav_path)
        
        logger.info(f"Audio rendered successfully to {output_wav_path}")
        progress(0.9, desc="Audio rendered. Finalizing...")

        # --- 4. Success and Cleanup (Progress: 90% -> 100%) ---
        gr.Info("Render complete!") # Show a success pop-up
        progress(1.0, desc="Done!")
        
        return output_wav_path

    except Exception as e:
        # Log the full error to the console for debugging
        logger.error(f"Failed to render audio: {str(e)}", exc_info=True)
        # Show a user-friendly error in the UI
        raise gr.Error(f"Failed to render audio. Error: {str(e)}. "
                         "This can happen if FluidSynth is not installed "
                         "or if the MIDI file is corrupt.")


# --- Run Startup Checks ---
fluidsynth_status = check_fluidsynth_dependency()
example_list, example_status = extract_examples()
initial_status_markdown = f"""
**System Status:**
- {fluidsynth_status}
- {example_status}
"""
# --- End Startup Checks ---


# --- Create the Gradio Interface ---
with gr.Blocks() as app:  # <-- I've removed theme=gr.themes.Soft()
    gr.Markdown(
        """
        # 🎵 MIDI & KAR Audio Renderer
        Upload a `.midi` or `.kar` file to synthesize it into audio
        using the **Timbres of Heaven** SoundFont.
        """
    )
    
    # New Status Display
    with gr.Accordion("Show System Status", open=False):
        status_display = gr.Markdown(initial_status_markdown, elem_id="status-display")

    with gr.Row():
        with gr.Column(scale=1):
            file_input = gr.File(
                label="Upload .midi, .kar, or .mid file", # <-- Updated this line
                file_types=[".midi", ".kar", ".mid"], # <-- Updated this line
                type="filepath" # <-- Changed from "file" to "filepath"
            )
            submit_btn = gr.Button("Render Audio", variant="primary")

        with gr.Column(scale=2):
            audio_output = gr.Audio(
                label="Rendered Audio",
                type="filepath"
            )

    # Connect the button click to the render function
    # Gradio automatically sees the 'progress' argument in render_midi
    # and will provide the progress bar component to it.
    submit_btn.click(
        fn=render_midi,
        inputs=file_input,
        outputs=audio_output
    )

    gr.Examples(
        examples=example_list,
        inputs=file_input,
        outputs=audio_output,
        fn=render_midi,
        cache_examples=True,
        label="Click an example to render it!" if example_list else "No examples found."
    )

# --- Launch the App ---
if __name__ == "__main__":
    logger.info("Starting Gradio application...")
    app.launch(server_name="0.0.0.0", server_port=7860)