matthewbarberdev commited on
Commit
5615a16
·
verified ·
1 Parent(s): 2f2213b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -0
app.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import tempfile
3
+ import random
4
+ import json
5
+ import re
6
+ import pretty_midi
7
+ import subprocess
8
+ import os
9
+
10
+ # Audio playback support
11
+ try:
12
+ import pygame
13
+ pygame.mixer.init()
14
+ PYGAME_AVAILABLE = True
15
+ except Exception as e:
16
+ print(f"[WARNING] pygame mixer init failed: {e}")
17
+ PYGAME_AVAILABLE = False
18
+
19
+ # === LLM APIs ===
20
+ def query_llm(prompt, model_name=None):
21
+ if model_name and model_name != "OpenAI":
22
+ import requests
23
+ response = requests.post("http://localhost:11434/api/generate", json={"model": model_name, "prompt": prompt, "stream": False})
24
+ return response.json().get("response", "")
25
+ else:
26
+ import openai
27
+ openai.api_key = "your-api-key" # Replace or load from environment
28
+ response = openai.ChatCompletion.create(
29
+ model="gpt-4",
30
+ messages=[{"role": "user", "content": prompt}],
31
+ temperature=0.7
32
+ )
33
+ return response["choices"][0]["message"]["content"]
34
+
35
+ # === Step 1: Parse intent ===
36
+ def get_intent_from_prompt(prompt, model_name):
37
+ system_prompt = f"""
38
+ Extract the musical intent from this prompt.
39
+ Return JSON with keys: tempo (int), key (A-G#), scale (major/minor), genre (e.g., lo-fi, trap), emotion, instrument.
40
+ Prompt: '{prompt}'
41
+ """
42
+ response = query_llm(system_prompt, model_name)
43
+ match = re.search(r'\{.*\}', response, re.DOTALL)
44
+ if match:
45
+ try:
46
+ return json.loads(match.group(0))
47
+ except json.JSONDecodeError:
48
+ return {"tempo": 120, "key": "C", "scale": "major", "genre": "default", "emotion": "neutral", "instrument": "piano"}
49
+ return {"tempo": 120, "key": "C", "scale": "major", "genre": "default", "emotion": "neutral", "instrument": "piano"}
50
+
51
+ # === Step 2: Melody planning ===
52
+ def get_melody_from_intent(intent, model_name):
53
+ melody_prompt = f"""
54
+ You are a music composer.
55
+ Based on this musical intent:
56
+ {json.dumps(intent)}
57
+
58
+ Generate a melody plan using a list of 16 notes with pitch (A-G#), octave (3-6), and duration (0.25 to 1.0 seconds).
59
+ Output ONLY valid JSON like:
60
+ [
61
+ {{"note": "D", "octave": 4, "duration": 0.5}},
62
+ {{"note": "F", "octave": 4, "duration": 1.0}}
63
+ ]
64
+ """
65
+ response = query_llm(melody_prompt, model_name)
66
+ print(f"\n[DEBUG] LLM Response for melody:\n{response}\n")
67
+ match = re.search(r'\[.*\]', response, re.DOTALL)
68
+ if match:
69
+ try:
70
+ melody = json.loads(match.group(0))
71
+ if isinstance(melody, list) and len(melody) > 0:
72
+ return melody
73
+ except json.JSONDecodeError as e:
74
+ print(f"[ERROR] Melody JSON decode error: {e}")
75
+
76
+ print("[WARNING] Using fallback melody.")
77
+ return [
78
+ {"note": "C", "octave": 4, "duration": 0.5},
79
+ {"note": "E", "octave": 4, "duration": 0.5},
80
+ {"note": "G", "octave": 4, "duration": 0.5},
81
+ {"note": "B", "octave": 4, "duration": 0.5},
82
+ ]
83
+
84
+ # === Step 3: MIDI generation ===
85
+ def midi_from_plan(melody, tempo):
86
+ midi = pretty_midi.PrettyMIDI()
87
+ instrument = pretty_midi.Instrument(program=0)
88
+ time = 0.0
89
+ seconds_per_beat = 60.0 / tempo
90
+
91
+ note_map = {"C": 0, "C#": 1, "D": 2, "D#": 3, "E": 4, "F": 5, "F#": 6,
92
+ "G": 7, "G#": 8, "A": 9, "A#": 10, "B": 11}
93
+
94
+ for note_info in melody:
95
+ try:
96
+ pitch = 12 * (note_info["octave"] + 1) + note_map[note_info["note"].upper()]
97
+ duration = float(note_info["duration"])
98
+ start = time
99
+ end = time + duration
100
+ instrument.notes.append(pretty_midi.Note(
101
+ velocity=100, pitch=pitch, start=start, end=end
102
+ ))
103
+ time = end
104
+ except:
105
+ continue
106
+
107
+ midi.instruments.append(instrument)
108
+ return midi
109
+
110
+ # === Generate audio preview from MIDI ===
111
+ def midi_to_wav(midi_path):
112
+ try:
113
+ import tempfile
114
+ import subprocess
115
+ import os
116
+
117
+ # Convert MIDI to WAV using FluidSynth if installed, else fallback to empty
118
+ wav_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
119
+
120
+ # Use fluidsynth if available, else skip audio preview
121
+ fluidsynth_cmd = ["fluidsynth", "-ni", "/usr/share/sounds/sf2/FluidR3_GM.sf2", midi_path, "-F", wav_path, "-r", "44100"]
122
+ result = subprocess.run(fluidsynth_cmd, capture_output=True)
123
+ if result.returncode != 0:
124
+ print("[WARNING] FluidSynth conversion failed or is not installed.")
125
+ return None
126
+ return wav_path
127
+ except Exception as e:
128
+ print(f"[ERROR] midi_to_wav failed: {e}")
129
+ return None
130
+
131
+ # === Main function to generate MIDI and audio preview ===
132
+ def generate_midi_and_audio(prompt, model_name):
133
+ intent = get_intent_from_prompt(prompt, model_name)
134
+ melody = get_melody_from_intent(intent, model_name)
135
+ midi = midi_from_plan(melody, intent.get("tempo", 120))
136
+
137
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mid") as tmp:
138
+ midi.write(tmp.name)
139
+ midi_path = tmp.name
140
+
141
+ audio_path = None
142
+ if PYGAME_AVAILABLE:
143
+ audio_path = midi_path # We'll use pygame to play midi directly if possible
144
+
145
+ return midi_path, audio_path
146
+
147
+ # === Get Ollama models ===
148
+ def get_ollama_models():
149
+ try:
150
+ result = subprocess.run(["ollama", "list"], capture_output=True, text=True)
151
+ models = [line.split()[0] for line in result.stdout.strip().splitlines()[1:]]
152
+ return ["OpenAI"] + models
153
+ except Exception as e:
154
+ return ["OpenAI"]
155
+
156
+ # === Gradio UI ===
157
+ models = get_ollama_models()
158
+
159
+ demo = gr.Interface(
160
+ fn=generate_midi_and_audio,
161
+ inputs=[
162
+ gr.Textbox(label="Music Prompt"),
163
+ gr.Dropdown(choices=models, label="LLM Model", value=models[0])
164
+ ],
165
+ outputs=[
166
+ gr.File(label="🎵 Download MIDI File"),
167
+ gr.Audio(label="🎧 Audio Preview (MIDI Playback, if supported)", type="filepath")
168
+ ],
169
+ title="🎼 Music Command Prompt (MCP Agent)",
170
+ description="Describe your music idea and download a generated MIDI file. Choose from local or OpenAI LLMs."
171
+ )
172
+
173
+ demo.launch(mcp_server=True)