ciaochris commited on
Commit
79b0eba
·
verified ·
1 Parent(s): f7000b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -272
app.py CHANGED
@@ -1,291 +1,105 @@
 
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)
 
1
+ # main.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
+ from rhythma import RhythmaSymphAICore, RhythmaModulationEngine
14
+
15
+ app = FastAPI(title="Rhythma API")
16
+
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"],
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ # Serve the beautiful frontend
26
+ app.mount("/static", StaticFiles(directory="."), name="static")
27
+
28
+ symphai = RhythmaSymphAICore(use_groq=True)
29
+
30
+ @app.post("/generate")
31
+ async def generate(
32
+ input_text: str = Form(""),
33
+ audio: UploadFile = File(None),
34
+ override_freq: float = Form(0.0),
35
+ override_modulation: str = Form("sine"),
36
+ override_rhythm: str = Form("auto"),
37
+ duration: int = Form(10),
38
+ ):
39
+ audio_path = None
40
+ if audio and audio.filename:
41
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
42
+ tmp.write(await audio.read())
43
+ audio_path = tmp.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  try:
46
+ analysis = symphai.analyze_input(
47
+ input_text.strip() or None,
48
+ audio_path
 
 
 
49
  )
50
 
51
+ engine = RhythmaModulationEngine(
52
+ base_freq=override_freq if override_freq > 0 else None,
53
+ modulation_type=override_modulation,
54
+ rhythm_pattern=override_rhythm if override_rhythm != "auto" else None,
55
+ emotional_state=analysis.get("emotional_state")
56
+ )
57
 
58
+ # Audio
59
+ timestamp = int(os.times()[4] * 1000)
60
+ audio_file = f"rhythma_{timestamp}.wav"
61
  saved_audio_path = engine.save_audio(duration, audio_file)
 
 
 
 
 
62
 
63
+ # Waveform image (PIL)
64
+ waveform_pil = engine.get_waveform_image()
65
 
66
+ # Full plot
67
+ fig = engine.visualize_waveform(duration)
 
 
 
68
 
69
+ # Convert plot to base64
70
+ buf = io.BytesIO()
71
+ fig.savefig(buf, format="png", bbox_inches="tight", dpi=220)
72
+ buf.seek(0)
73
+ plot_base64 = base64.b64encode(buf.read()).decode("utf-8")
74
+ plt.close(fig)
75
+
76
+ # Convert simple waveform to base64
77
+ buf = io.BytesIO()
78
+ waveform_pil.save(buf, format="PNG")
79
+ buf.seek(0)
80
+ simple_wave_base64 = base64.b64encode(buf.read()).decode("utf-8")
81
+
82
+ return {
83
+ "analysis_text": engine.get_complete_analysis(),
84
+ "audio_base64": base64.b64encode(open(saved_audio_path, "rb").read()).decode("utf-8"),
85
+ "plot_base64": plot_base64,
86
+ "waveform_base64": simple_wave_base64,
87
+ "symbolic_text": engine.get_symbolic_interpretation(),
88
+ "transcription": analysis.get("transcription", ""),
89
+ "emotional_state": analysis.get("emotional_state"),
90
+ "rhythm_pattern": analysis.get("rhythm_pattern")
91
+ }
92
 
93
  except Exception as e:
 
 
94
  import traceback
95
  traceback.print_exc()
96
+ return JSONResponse(status_code=500, content={"error": str(e)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ finally:
99
+ if audio_path and os.path.exists(audio_path):
100
+ os.unlink(audio_path)
 
 
 
 
 
101
 
 
102
 
 
103
  if __name__ == "__main__":
104
+ print("🚀 Rhythma API + Beautiful Frontend running at http://localhost:8000")
105
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)