ciaochris commited on
Commit
c2a3aa3
·
verified ·
1 Parent(s): 2f0b9e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +272 -96
app.py CHANGED
@@ -1,115 +1,291 @@
1
- # app.py
2
  import os
3
- import tempfile
4
- import base64
5
- import io
 
6
  import matplotlib.pyplot as plt
7
- from fastapi import FastAPI, UploadFile, File, Form
8
- from fastapi.responses import JSONResponse
9
- from fastapi.middleware.cors import CORSMiddleware
10
- from fastapi.staticfiles import StaticFiles
11
- import uvicorn
12
-
13
- # Hugging Face Spaces specific setup
14
- os.environ["HF_HOME"] = "/tmp/hf_cache"
15
- os.environ["SENTENCE_TRANSFORMERS_HOME"] = "/tmp/hf_cache"
16
- os.environ["TRANSFORMERS_CACHE"] = "/tmp/hf_cache"
17
-
18
- from rhythma import RhythmaSymphAICore, RhythmaModulationEngine
19
-
20
- app = FastAPI(title="Rhythma: The Living Modulation Engine")
21
-
22
- # Enable CORS for the frontend
23
- app.add_middleware(
24
- CORSMiddleware,
25
- allow_origins=["*"],
26
- allow_credentials=True,
27
- allow_methods=["*"],
28
- allow_headers=["*"],
29
- )
30
-
31
- # Mount static files so index.html is accessible
32
- app.mount("/static", StaticFiles(directory="."), name="static")
33
-
34
- # Initialize the core components
35
- symphai = RhythmaSymphAICore(use_groq=True)
36
-
37
- @app.post("/generate")
38
- async def generate(
39
- input_text: str = Form(""),
40
- audio: UploadFile = File(None),
41
- override_freq: float = Form(0.0),
42
- override_modulation: str = Form("sine"),
43
- override_rhythm: str = Form("auto"),
44
- duration: int = Form(10),
45
- ):
46
- audio_path = None
47
- if audio and audio.filename:
48
- # Save uploaded audio to temp file
49
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
50
- tmp.write(await audio.read())
51
- audio_path = tmp.name
52
 
53
- try:
54
- # Step 1: Analyze input using your SymphAI Core
55
- analysis = symphai.analyze_input(
56
- input_text.strip() or None,
57
- audio_path
58
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- # Step 2: Generate modulated experience
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  engine = RhythmaModulationEngine(
62
- base_freq=override_freq if override_freq > 0 else None,
63
- modulation_type=override_modulation,
64
- rhythm_pattern=override_rhythm if override_rhythm != "auto" else None,
65
- emotional_state=analysis.get("emotional_state")
66
  )
67
 
68
- # Generate audio file
69
- timestamp = int(os.times()[4] * 1000)
70
- audio_file = f"rhythma_{timestamp}.wav"
 
 
 
 
 
71
  saved_audio_path = engine.save_audio(duration, audio_file)
 
 
72
 
73
- # Generate visualizations
74
- waveform_pil = engine.get_waveform_image()
75
  fig = engine.visualize_waveform(duration)
76
 
77
- # Convert matplotlib figure to base64
78
- buf = io.BytesIO()
79
- fig.savefig(buf, format="png", bbox_inches="tight", dpi=220)
80
- buf.seek(0)
81
- plot_base64 = base64.b64encode(buf.read()).decode("utf-8")
82
- plt.close(fig)
83
-
84
- # Convert simple waveform PIL image to base64
85
- buf = io.BytesIO()
86
- waveform_pil.save(buf, format="PNG")
87
- buf.seek(0)
88
- simple_wave_base64 = base64.b64encode(buf.read()).decode("utf-8")
89
-
90
- # Return all data to the frontend
91
- return {
92
- "analysis_text": engine.get_complete_analysis(),
93
- "audio_base64": base64.b64encode(open(saved_audio_path, "rb").read()).decode("utf-8"),
94
- "plot_base64": plot_base64,
95
- "waveform_base64": simple_wave_base64,
96
- "symbolic_text": engine.get_symbolic_interpretation(),
97
- "transcription": analysis.get("transcription", ""),
98
- "emotional_state": analysis.get("emotional_state"),
99
- "rhythm_pattern": analysis.get("rhythm_pattern")
100
- }
101
 
102
  except Exception as e:
 
 
103
  import traceback
104
  traceback.print_exc()
105
- return JSONResponse(status_code=500, content={"error": str(e)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- finally:
108
- # Clean up temporary audio file
109
- if audio_path and os.path.exists(audio_path):
110
- os.unlink(audio_path)
 
 
 
 
111
 
 
112
 
113
- # For local testing only (Hugging Face uses the Dockerfile CMD)
114
  if __name__ == "__main__":
115
- uvicorn.run("app:app", host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import gradio as gr
3
+ import numpy as np
4
+ import matplotlib
5
+ matplotlib.use('Agg') # Set backend BEFORE importing pyplot
6
  import matplotlib.pyplot as plt
7
+ from PIL import Image
8
+ import soundfile as sf
9
+ import tempfile
10
+ import time
11
+ from rhythma import RhythmaModulationEngine, RhythmaSymphAICore # Assuming rhythma.py is in the same directory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # --- Environment Variable Check ---
14
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
15
+ use_groq = bool(GROQ_API_KEY) # True only if key exists and is not empty
16
+
17
+ if not use_groq:
18
+ print("*"*40)
19
+ print("⚠️ WARNING: GROQ_API_KEY not found or empty in environment variables.")
20
+ print(" Groq LLM analysis and audio transcription features will be disabled.")
21
+ print(" Falling back to local analysis methods.")
22
+ print("*"*40)
23
+ else:
24
+ print("✅ GROQ_API_KEY found. Enabling Groq features.")
25
+ # --- End Environment Variable Check ---
26
+
27
+
28
+ # --- Initialize Core Components ---
29
+ try:
30
+ # Pass the determined use_groq flag to the core
31
+ symphai_core = RhythmaSymphAICore(use_groq=use_groq)
32
+ except Exception as e:
33
+ print(f"❌ FATAL ERROR: Could not initialize RhythmaSymphAICore: {e}")
34
+ # Handle fatal error appropriately - maybe exit or disable functionality
35
+ symphai_core = None # Indicate failure
36
+ # --- End Initialization ---
37
+
38
+
39
+ # --- Core Functions ---
40
+ def analyze_input(input_text=None, audio_input=None):
41
+ """Analyze user input using the SymphAI Core."""
42
+ if symphai_core is None:
43
+ return {"error": "Analysis Core failed to initialize."}
44
+
45
+ # Ensure audio_input is a filepath string or None
46
+ audio_filepath = audio_input if isinstance(audio_input, str) else None
47
+
48
+ # Pass to SymphAI Core for analysis
49
+ # Add default empty string for input_text if None, as core expects string or None
50
+ return symphai_core.analyze_input(input_text or "", audio_filepath)
51
 
52
+
53
+ def generate_modulated_experience(analysis_result, base_freq=None, modulation_type="sine", rhythm_pattern=None, duration=5):
54
+ """Generate a complete modulated experience based on analysis and parameters."""
55
+ print(f"DEBUG: generate_modulated_experience received analysis: {analysis_result}")
56
+ print(f"DEBUG: Overrides - Freq: {base_freq}, Mod: {modulation_type}, Rhythm: {rhythm_pattern}, Dur: {duration}")
57
+
58
+ # --- Input Validation ---
59
+ if not isinstance(analysis_result, dict):
60
+ error_msg = "Internal Error: Analysis result is not in the expected format."
61
+ print(f"❌ {error_msg}")
62
+ return error_msg, None, None, None, None
63
+
64
+ if "error" in analysis_result and analysis_result["error"]:
65
+ error_msg = f"Analysis Error: {analysis_result['error']}"
66
+ print(f"❌ {error_msg}")
67
+ # Return the error message clearly for the analysis output
68
+ return error_msg, None, None, None, None
69
+
70
+ # Ensure required keys exist, even if defaults were used in analysis
71
+ emotional_state = analysis_result.get("emotional_state", "neutral") # Default if missing
72
+ rhythm_pattern_from_analysis = analysis_result.get("rhythm_pattern", "calm") # Default if missing
73
+
74
+ # --- Determine Final Parameters ---
75
+ # Use manual override if provided and valid, otherwise use analysis result
76
+ final_rhythm_pattern = rhythm_pattern if rhythm_pattern else rhythm_pattern_from_analysis
77
+ # Use manual frequency override ONLY if it's > 0
78
+ final_base_freq = base_freq if base_freq and base_freq > 0 else None # Pass None to let engine use emotion/default
79
+
80
+ print(f"DEBUG: Engine Params - Emotion: {emotional_state}, Freq Override: {final_base_freq}, Rhythm: {final_rhythm_pattern}, Mod: {modulation_type}")
81
+
82
+ try:
83
+ # --- Initialize the Rhythma Engine ---
84
  engine = RhythmaModulationEngine(
85
+ base_freq=final_base_freq, # Engine handles None: uses emotional_state or default
86
+ modulation_type=modulation_type,
87
+ rhythm_pattern=final_rhythm_pattern,
88
+ emotional_state=emotional_state if not final_base_freq else None # Pass emotion only if freq isn't overridden
89
  )
90
 
91
+ # --- Generate Outputs ---
92
+ timestamp = int(time.time())
93
+ temp_dir = tempfile.gettempdir()
94
+ # Ensure temp_dir exists (useful in some restricted environments)
95
+ os.makedirs(temp_dir, exist_ok=True)
96
+ audio_file = os.path.join(temp_dir, f"rhythma_{timestamp}.wav")
97
+
98
+ # Generate and save audio
99
  saved_audio_path = engine.save_audio(duration, audio_file)
100
+ if not saved_audio_path: # Check if saving failed
101
+ raise RuntimeError("Failed to save generated audio file.")
102
 
103
+ # Generate waveform visualization (Plot)
 
104
  fig = engine.visualize_waveform(duration)
105
 
106
+ # Get simple waveform image (PIL Image)
107
+ waveform_image = engine.get_waveform_image()
108
+
109
+ # Get complete analysis text from the engine's perspective
110
+ analysis_text = engine.get_complete_analysis()
111
+
112
+ # Get symbolic interpretation
113
+ symbolic = engine.get_symbolic_interpretation()
114
+
115
+ print("✅ Modulation experience generated successfully.")
116
+ return analysis_text, saved_audio_path, fig, waveform_image, symbolic
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  except Exception as e:
119
+ error_msg = f"Error during Rhythma generation: {e}"
120
+ print(f"❌ {error_msg}")
121
  import traceback
122
  traceback.print_exc()
123
+ # Return error message for analysis, and None for other outputs
124
+ return error_msg, None, None, None, None
125
+
126
+
127
+ def rhythma_experience(
128
+ input_text, audio_input,
129
+ override_freq=None,
130
+ override_modulation="sine",
131
+ override_rhythm=None,
132
+ duration=5
133
+ ):
134
+ """Complete Rhythma experience pipeline: Analysis -> Generation"""
135
+ print("\n--- Starting New Rhythma Experience ---")
136
+ # Clean up input text
137
+ input_text = input_text.strip() if input_text else ""
138
+
139
+ # --- Step 1: Analyze input ---
140
+ # Ensure override_freq is float or None
141
+ try:
142
+ freq_override_value = float(override_freq) if override_freq is not None else 0.0
143
+ except (ValueError, TypeError):
144
+ freq_override_value = 0.0 # Default to 0 if invalid input
145
+
146
+ analysis = analyze_input(input_text, audio_input)
147
+
148
+ # --- Step 2: Generate modulated experience ---
149
+ # Pass analysis results and overrides to the generation function
150
+ analysis_text, audio_file, fig, waveform_image, symbolic = generate_modulated_experience(
151
+ analysis,
152
+ base_freq=freq_override_value, # Pass the validated float/int
153
+ modulation_type=override_modulation,
154
+ rhythm_pattern=override_rhythm if override_rhythm else None, # Pass None if dropdown default is selected
155
+ duration=duration
156
+ )
157
+
158
+ # --- Step 3: Prepare Outputs ---
159
+ # Get transcription from analysis result (will be empty string if no audio/transcription)
160
+ transcription = analysis.get("transcription", "") if isinstance(analysis, dict) else ""
161
+ # If analysis itself failed, analysis_text will contain the error message from generate_modulated_experience
162
+ # If only transcription failed, it might be in the transcription field
163
+
164
+ # Handle potential None figure if generation failed
165
+ plot_output = fig if fig else None # Gradio handles None for Plot output
166
+
167
+ print("--- Rhythma Experience Complete ---")
168
+ # Return all outputs for Gradio interface
169
+ return analysis_text, audio_file, plot_output, waveform_image, symbolic, transcription
170
+
171
+ # --- Create the Gradio Interface ---
172
+ def create_interface():
173
+ with gr.Blocks(theme=gr.themes.Soft(), title="Rhythma: The Living Modulation Engine") as demo:
174
+ gr.Markdown("# Rhythma: The Living Modulation Engine")
175
+ gr.Markdown("### Dynamic rhythm-based sound modulation for wellbeing from Vers3Dynamics")
176
+
177
+ if not use_groq:
178
+ gr.Warning("Running with limited functionality: GROQ_API_KEY not found. "
179
+ "Advanced AI analysis and audio transcription are disabled.")
180
+
181
+ with gr.Row():
182
+ with gr.Column(scale=1):
183
+ gr.Markdown("**1. Describe Your State or Intention**")
184
+ input_text = gr.Textbox(
185
+ label="How are you feeling, or what is your intention?",
186
+ placeholder="e.g., 'feeling stressed about work', 'want to relax', 'need focus'...",
187
+ lines=3
188
+ )
189
+
190
+ gr.Markdown("**Optional: Use Your Voice (Requires Groq API Key)**")
191
+ audio_input = gr.Audio(
192
+ sources=["microphone"], # Prioritize microphone
193
+ type="filepath", # RhythmaSymphAICore expects a filepath
194
+ label="Record or Upload Audio" if use_groq else "Audio Input (Disabled)",
195
+ interactive=use_groq # Disable if Groq not available
196
+ )
197
+
198
+ with gr.Accordion("Advanced Settings (Optional Overrides)", open=False):
199
+ override_freq = gr.Slider(
200
+ minimum=0, maximum=1000, value=0, step=1,
201
+ label="Override Frequency (Hz)",
202
+ info="Leave at 0 to use automatic frequency based on analysis."
203
+ )
204
+ override_modulation = gr.Dropdown(
205
+ choices=["sine", "pulse", "chirp"],
206
+ value="sine",
207
+ label="Override Modulation Type"
208
+ )
209
+ # Get available patterns from the engine instance
210
+ available_patterns = list(RhythmaModulationEngine().rhythm_configs.keys())
211
+ override_rhythm = gr.Dropdown(
212
+ choices=[None] + available_patterns, # Add None option for automatic
213
+ value=None, # Default to automatic
214
+ label="Override Rhythm Pattern",
215
+ info="Leave blank to use automatic pattern based on analysis."
216
+ )
217
+ duration = gr.Slider(
218
+ minimum=3, maximum=60, value=10, step=1,
219
+ label="Duration (seconds)"
220
+ )
221
+
222
+ generate_button = gr.Button("Generate Rhythma Experience", variant="primary", scale=2)
223
+
224
+ with gr.Column(scale=2):
225
+ gr.Markdown("**2. Experience Your Rhythma Soundscape**")
226
+ analysis_output = gr.Textbox(label="Rhythma Analysis & Guidance", lines=8, interactive=False)
227
+ with gr.Row():
228
+ audio_output = gr.Audio(label="Modulated Audio", type="filepath", interactive=False)
229
+ waveform_simple = gr.Image(label="Base Waveform", interactive=False, height=100, width=200)
230
+ waveform_plot = gr.Plot(label="Detailed Waveform & Spectrogram")
231
+ symbolic_output = gr.Textbox(label="Symbolic Interpretation", interactive=False)
232
+ # Conditionally visible transcription output
233
+ transcription_output = gr.Textbox(
234
+ label="Transcribed Audio (If Provided)",
235
+ interactive=False,
236
+ visible=use_groq # Only show if Groq is potentially usable
237
+ )
238
+
239
+ # Define button action
240
+ generate_button.click(
241
+ fn=rhythma_experience,
242
+ inputs=[
243
+ input_text, audio_input,
244
+ override_freq, override_modulation, override_rhythm,
245
+ duration
246
+ ],
247
+ outputs=[
248
+ analysis_output, audio_output,
249
+ waveform_plot, waveform_simple, symbolic_output,
250
+ transcription_output
251
+ ]
252
+ )
253
+
254
+ # Add Examples
255
+ gr.Examples(
256
+ examples=[
257
+ ["I'm feeling anxious about my upcoming presentation.", None, 0, "sine", None, 10],
258
+ ["I feel at peace and grounded today.", None, 0, "sine", None, 15],
259
+ ["I need to focus on my work but keep getting distracted.", None, 0, "sine", None, 20],
260
+ ["Feeling overwhelmed with responsibilities.", None, 0, "sine", None, 10],
261
+ ["Excited about my vacation next week!", None, 0, "sine", None, 10],
262
+ ["Just want to relax after a long day.", None, 0, "sine", "relaxed", 30], # Example with override
263
+ ["Feeling sad and low energy.", None, 0, "sine", None, 15],
264
+ ],
265
+ inputs=[input_text, audio_input, override_freq, override_modulation, override_rhythm, duration],
266
+ outputs=[analysis_output, audio_output, waveform_plot, waveform_simple, symbolic_output, transcription_output],
267
+ fn=rhythma_experience, # Ensure examples also run the main function
268
+ cache_examples=False # Maybe disable caching during development
269
+ )
270
 
271
+ gr.Markdown("---")
272
+ gr.Markdown("""
273
+ ## About Rhythma
274
+ Rhythma creates personalized soundscapes using frequency modulation based on your described emotional state or intention.
275
+ It leverages AI analysis (enhanced with Groq if available) and principles of rhythmic sound design.
276
+ **Note:** This is an experimental tool. The frequencies and interpretations are based on various theories and are not medical advice.
277
+ © 2025 Vers3Dynamics
278
+ """)
279
 
280
+ return demo
281
 
282
+ # --- Run the Gradio App ---
283
  if __name__ == "__main__":
284
+ if symphai_core is None:
285
+ print("\n❌ Cannot launch Gradio app because RhythmaSymphAICore failed to initialize.\n")
286
+ else:
287
+ print("\n🚀 Launching Rhythma Gradio Interface...")
288
+ app_demo = create_interface()
289
+ # Set share=True if you need a public link (useful for testing deployment)
290
+ # Set debug=True for more verbose logs during development
291
+ app_demo.launch()#debug=True)