ciaochris commited on
Commit
b5f31f0
·
verified ·
1 Parent(s): 4ad3ee7

Update rhythma.py

Browse files
Files changed (1) hide show
  1. rhythma.py +281 -498
rhythma.py CHANGED
@@ -1,508 +1,291 @@
 
 
1
  import numpy as np
 
 
2
  import matplotlib.pyplot as plt
3
- from scipy import signal
4
- import pandas as pd
5
  from PIL import Image
6
- import io
7
- from sklearn.metrics.pairwise import cosine_similarity
8
  import soundfile as sf
9
- import os
 
 
10
 
11
- # Try to import optional dependencies
12
- try:
13
- from groq import Groq
14
- GROQ_AVAILABLE = True
15
- except ImportError:
16
- GROQ_AVAILABLE = False
17
- print("Groq package not installed. Falling back to local analysis.")
18
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  try:
20
- from sentence_transformers import SentenceTransformer
21
- SENTENCE_TRANSFORMER_AVAILABLE = True
22
- except ImportError:
23
- SENTENCE_TRANSFORMER_AVAILABLE = False
24
- print("SentenceTransformer not installed. Simple text matching will be used.")
25
-
26
- class RhythmaModulationEngine:
27
- """
28
- Rhythma: The Living Modulation Engine
29
- A dynamic rhythm-based audio modulation system that creates responsive
30
- sound experiences based on rhythm patterns and emotional states.
31
- """
32
-
33
- def __init__(self, base_freq=None, modulation_type="sine", rhythm_pattern=None, emotional_state=None):
34
- """
35
- Initialize the RhythmaModulationEngine.
36
-
37
- Args:
38
- base_freq (float, optional): The base frequency in Hz
39
- modulation_type (str): Type of modulation (sine, pulse, chirp)
40
- rhythm_pattern (str, optional): Pattern type (calm, active, focused, relaxed)
41
- emotional_state (str, optional): Emotional state (anxious, stressed, calm, etc.)
42
- """
43
- self.modulation_type = modulation_type
44
- self.sample_rate = 44100 # Standard audio sample rate
45
-
46
- # Define frequency mappings for emotional states
47
- self.emotional_frequencies = {
48
- "anxious": 396,
49
- "stressed": 528,
50
- "calm": 741,
51
- "sad": 417,
52
- "angry": 852,
53
- "fearful": 639,
54
- "confused": 285,
55
- "happy": 432
56
- }
57
-
58
- # Detailed information about emotional states
59
- self.emotional_info = {
60
- "anxious": {
61
- "name": "Liberating Guilt and Fear",
62
- "advice": "The 396 Hz frequency is associated with releasing fear and guilt."
63
- },
64
- "stressed": {
65
- "name": "Transformation and Miracles",
66
- "advice": "The 528 Hz frequency is known as the 'miracle tone' for transformation and DNA repair."
67
- },
68
- "calm": {
69
- "name": "Awakening Intuition",
70
- "advice": "The 741 Hz frequency is associated with awakening intuition and solving problems."
71
- },
72
- "sad": {
73
- "name": "Facilitating Change",
74
- "advice": "The 417 Hz frequency is believed to facilitate change and let go of negative energy."
75
- },
76
- "angry": {
77
- "name": "Returning to Spiritual Order",
78
- "advice": "The 852 Hz frequency is associated with returning to spiritual order and inner strength."
79
- },
80
- "fearful": {
81
- "name": "Connecting Relationships",
82
- "advice": "The 639 Hz frequency is linked to connecting relationships and understanding."
83
- },
84
- "confused": {
85
- "name": "Quantum Cognition",
86
- "advice": "The 285 Hz frequency is believed to influence energy fields and aid in healing."
87
- },
88
- "happy": {
89
- "name": "Harmonizing Vibrations",
90
- "advice": "The 432 Hz frequency is associated with harmonizing vibrations and promoting wellbeing."
91
- }
92
- }
93
-
94
- # Configure rhythm patterns
95
- self.rhythm_configs = {
96
- "calm": {
97
- "mod_depth": 0.15,
98
- "mod_freq": 0.5,
99
- "pulse_width": 0.7,
100
- "phase_shift": 0.1,
101
- "harmonics": [1.0, 0.5, 0.25, 0.125]
102
- },
103
- "active": {
104
- "mod_depth": 0.4,
105
- "mod_freq": 2.5,
106
- "pulse_width": 0.3,
107
- "phase_shift": 0.3,
108
- "harmonics": [1.0, 0.7, 0.5, 0.3]
109
- },
110
- "focused": {
111
- "mod_depth": 0.25,
112
- "mod_freq": 1.5,
113
- "pulse_width": 0.5,
114
- "phase_shift": 0.2,
115
- "harmonics": [1.0, 0.6, 0.3, 0.15]
116
- },
117
- "relaxed": {
118
- "mod_depth": 0.2,
119
- "mod_freq": 0.3,
120
- "pulse_width": 0.8,
121
- "phase_shift": 0.05,
122
- "harmonics": [1.0, 0.4, 0.2, 0.1]
123
- }
124
- }
125
-
126
- # Symbolic mapping for rhythm patterns
127
- self.symbolic_mapping = {
128
- "calm": "Resonating in the Circle Archetype: completion, wholeness, presence",
129
- "active": "Resonating in the Spiral Archetype: flow, transition, emergence",
130
- "focused": "Resonating in the Triangle Archetype: clarity, direction, purpose",
131
- "relaxed": "Resonating in the Wave Archetype: fluidity, acceptance, surrender"
132
- }
133
-
134
- # Set the base frequency based on emotional state if provided, otherwise use base_freq
135
- if emotional_state and emotional_state in self.emotional_frequencies:
136
- self.emotional_state = emotional_state
137
- self.base_freq = self.emotional_frequencies[emotional_state]
138
- else:
139
- self.emotional_state = None
140
- self.base_freq = base_freq or 440 # Default to A4 if no frequency or emotion provided
141
-
142
- # Set rhythm pattern
143
- self.rhythm_pattern = rhythm_pattern or "calm" # Default to calm if not provided
144
-
145
- # Get current rhythm config
146
- self.config = self.rhythm_configs.get(
147
- self.rhythm_pattern,
148
- self.rhythm_configs["calm"] # Default to calm if pattern not found
149
  )
150
 
151
- def _generate_base_wave(self, duration):
152
- """Generate the base carrier wave"""
153
- t = np.linspace(0, duration, int(self.sample_rate * duration), endpoint=False)
154
- return t, np.sin(2 * np.pi * self.base_freq * t)
155
-
156
- def _apply_sine_modulation(self, t, carrier):
157
- """Apply sine wave modulation"""
158
- mod_freq = self.config["mod_freq"]
159
- mod_depth = self.config["mod_depth"]
160
-
161
- # Create modulation envelope
162
- mod_env = 1.0 + mod_depth * np.sin(2 * np.pi * mod_freq * t + self.config["phase_shift"])
163
-
164
- # Apply modulation
165
- return carrier * mod_env
166
-
167
- def _apply_pulse_modulation(self, t, carrier):
168
- """Apply pulse modulation"""
169
- mod_freq = self.config["mod_freq"]
170
- mod_depth = self.config["mod_depth"]
171
- pulse_width = self.config["pulse_width"]
172
-
173
- # Create pulse modulation envelope
174
- pulse = signal.square(2 * np.pi * mod_freq * t, duty=pulse_width)
175
- mod_env = 1.0 + mod_depth * pulse
176
-
177
- # Apply modulation
178
- return carrier * mod_env
179
-
180
- def _apply_chirp_modulation(self, t, carrier):
181
- """Apply frequency chirp modulation"""
182
- mod_depth = self.config["mod_depth"]
183
-
184
- # Create chirp modulation with frequency that increases with time
185
- start_freq = self.base_freq * (1 - mod_depth/2)
186
- end_freq = self.base_freq * (1 + mod_depth/2)
187
-
188
- # Linear chirp
189
- chirp = signal.chirp(t, start_freq, t[-1], end_freq)
190
-
191
- # Apply modulation
192
- return chirp * carrier
193
-
194
- def _apply_harmonics(self, t, wave):
195
- """Apply harmonic overtones to enrich the sound"""
196
- harmonics = self.config["harmonics"]
197
- rich_wave = np.zeros_like(wave)
198
-
199
- for i, harmonic_amp in enumerate(harmonics):
200
- harmonic_freq = self.base_freq * (i + 1)
201
- rich_wave += harmonic_amp * np.sin(2 * np.pi * harmonic_freq * t)
202
-
203
- # Normalize
204
- rich_wave = rich_wave / np.max(np.abs(rich_wave))
205
- return rich_wave
206
-
207
- def generate_modulated_wave(self, duration):
208
- """
209
- Generate modulated audio wave based on current settings
210
-
211
- Args:
212
- duration (float): Duration of audio in seconds
213
-
214
- Returns:
215
- numpy.ndarray: The modulated audio waveform
216
- """
217
- t, carrier = self._generate_base_wave(duration)
218
-
219
- # Apply the selected modulation type
220
- if self.modulation_type == "sine":
221
- modulated = self._apply_sine_modulation(t, carrier)
222
- elif self.modulation_type == "pulse":
223
- modulated = self._apply_pulse_modulation(t, carrier)
224
- elif self.modulation_type == "chirp":
225
- modulated = self._apply_chirp_modulation(t, carrier)
226
- else:
227
- modulated = carrier # Default to unmodulated carrier
228
-
229
- # Apply harmonics for richer sound
230
- enriched = self._apply_harmonics(t, modulated)
231
-
232
- # Normalize to prevent clipping
233
- normalized = 0.8 * enriched / np.max(np.abs(enriched))
234
-
235
- return normalized
236
-
237
- def save_audio(self, duration, file_path=None):
238
- """Generate and save audio to a file"""
239
- audio = self.generate_modulated_wave(duration)
240
- file_path = file_path or f"rhythma_{self.base_freq}Hz_{self.rhythm_pattern}.wav"
241
- sf.write(file_path, audio, self.sample_rate)
242
- return file_path
243
-
244
- def visualize_waveform(self, duration):
245
- """
246
- Generate visualization of the modulated waveform
247
-
248
- Args:
249
- duration (float): Duration of audio in seconds
250
-
251
- Returns:
252
- matplotlib.figure.Figure: Figure containing the waveform visualization
253
- """
254
- # Generate a shorter segment for visualization
255
- t = np.linspace(0, min(duration, 2), int(self.sample_rate * min(duration, 2)), endpoint=False)
256
-
257
- # Generate modulated wave for plotting
258
- modulated = self.generate_modulated_wave(min(duration, 2))
259
-
260
- # Create visualization
261
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
262
-
263
- # Plot time domain
264
- ax1.plot(t[:1000], modulated[:1000])
265
- title = f'Rhythma Modulated Waveform: {self.rhythm_pattern} ({self.modulation_type})'
266
- if self.emotional_state:
267
- title += f' - {self.emotional_state.capitalize()} state'
268
- ax1.set_title(title)
269
- ax1.set_xlabel('Time (s)')
270
- ax1.set_ylabel('Amplitude')
271
-
272
- # Plot frequency domain (spectrogram)
273
- f, t, Sxx = signal.spectrogram(modulated, self.sample_rate)
274
- ax2.pcolormesh(t, f[:500], Sxx[:500], shading='gouraud')
275
- ax2.set_ylabel('Frequency (Hz)')
276
- ax2.set_xlabel('Time (s)')
277
- ax2.set_title(f'Spectrogram - Base Frequency: {self.base_freq} Hz')
278
-
279
- plt.tight_layout()
280
-
281
- # Add symbolic interpretation
282
- fig_text = self.get_symbolic_interpretation()
283
- if self.emotional_state:
284
- emotion_info = self.emotional_info.get(self.emotional_state, {})
285
- if emotion_info:
286
- fig_text += f"\n{self.base_freq} Hz - {emotion_info.get('name', '')}"
287
-
288
- fig.text(0.5, 0.01, fig_text, ha='center', fontsize=10, style='italic')
289
-
290
- return fig
291
-
292
- def get_waveform_image(self):
293
- """Generate waveform as a PIL Image"""
294
- duration = 0.1 # Short duration for visualization
295
- t = np.linspace(0, duration, int(self.sample_rate * duration), False)
296
- tone = np.sin(2 * np.pi * self.base_freq * t)
297
-
298
- plt.figure(figsize=(10, 4))
299
- plt.plot(t, tone)
300
- plt.title(f"Waveform of {self.base_freq} Hz Tone")
301
- plt.xlabel("Time (s)")
302
- plt.ylabel("Amplitude")
303
- plt.ylim(-1.1, 1.1)
304
- plt.grid(True)
305
-
306
- buf = io.BytesIO()
307
- plt.savefig(buf, format='png')
308
- buf.seek(0)
309
- plt.close()
310
-
311
- return Image.open(buf)
312
-
313
- def get_symbolic_interpretation(self):
314
- """Return the symbolic interpretation of the current rhythm pattern"""
315
- return self.symbolic_mapping.get(self.rhythm_pattern, "Unknown pattern")
316
-
317
- def get_emotional_advice(self):
318
- """Get advice based on emotional state if available"""
319
- if not self.emotional_state:
320
- return ""
321
-
322
- emotion_info = self.emotional_info.get(self.emotional_state, {})
323
- if not emotion_info:
324
- return ""
325
-
326
- return f"{emotion_info.get('advice', '')}"
327
-
328
- def get_complete_analysis(self):
329
- """Get a complete analysis including emotional and rhythmic information"""
330
- analysis = []
331
-
332
- if self.emotional_state:
333
- emotion_info = self.emotional_info.get(self.emotional_state, {})
334
- if emotion_info:
335
- analysis.append(f"Emotional State: {self.emotional_state.capitalize()}")
336
- analysis.append(f"Resonant Frequency: {self.base_freq} Hz - {emotion_info.get('name', '')}")
337
- analysis.append(f"Emotional Advice: {emotion_info.get('advice', '')}")
338
- else:
339
- analysis.append(f"Base Frequency: {self.base_freq} Hz")
340
-
341
- analysis.append(f"Rhythm Pattern: {self.rhythm_pattern.capitalize()}")
342
- analysis.append(f"Symbolic Interpretation: {self.get_symbolic_interpretation()}")
343
- analysis.append(f"Modulation Type: {self.modulation_type.capitalize()}")
344
-
345
- return "\n\n".join(analysis)
346
-
347
-
348
- class RhythmaSymphAICore:
349
- """
350
- SymphAI Core - The intelligent symbolic engine that interprets rhythm and state
351
- """
352
-
353
- def __init__(self, use_groq=True):
354
- """Initialize the SymphAI Core"""
355
- # Default emotional states that can be detected
356
- self.emotional_states = [
357
- "anxious", "stressed", "calm", "sad",
358
- "angry", "fearful", "confused", "happy"
359
- ]
360
-
361
- # Default rhythm patterns
362
- self.rhythm_patterns = [
363
- "calm", "active", "focused", "relaxed"
364
- ]
365
-
366
- # Initialize Groq client if available and requested
367
- self.use_groq = use_groq and GROQ_AVAILABLE
368
- if self.use_groq:
369
- try:
370
- self.groq_client = Groq(
371
- api_key=os.environ.get("GROQ_API_KEY"),
372
  )
373
- print("Groq client initialized successfully.")
374
- except Exception as e:
375
- print(f"Failed to initialize Groq client: {str(e)}")
376
- self.use_groq = False
377
-
378
- # Initialize sentence transformer for semantic matching if available
379
- if SENTENCE_TRANSFORMER_AVAILABLE:
380
- try:
381
- self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
382
- self.emotional_embeddings = {
383
- emotion: self.embedding_model.encode([emotion])
384
- for emotion in self.emotional_states
385
- }
386
- self.rhythm_embeddings = {
387
- pattern: self.embedding_model.encode([pattern])
388
- for pattern in self.rhythm_patterns
389
- }
390
- print("SentenceTransformer initialized successfully.")
391
- except Exception as e:
392
- print(f"Failed to initialize SentenceTransformer: {str(e)}")
393
- self.embedding_model = None
394
- self.emotional_embeddings = {}
395
- self.rhythm_embeddings = {}
396
- else:
397
- self.embedding_model = None
398
- self.emotional_embeddings = {}
399
- self.rhythm_embeddings = {}
400
-
401
- def detect_emotion_with_groq(self, input_text):
402
- """Use Groq LLM to detect emotion in text"""
403
- if not self.use_groq:
404
- return None
405
-
406
- prompt = f"""Analyze the following text and determine the primary emotion expressed.
407
- Choose the most appropriate emotion from this list: anxious, stressed, calm, sad, angry, fearful, confused, happy.
408
- Provide your answer as a single word that best describes the emotional state.
409
-
410
- Text: {input_text}
411
- Emotion:"""
412
-
413
- try:
414
- chat_completion = self.groq_client.chat.completions.create(
415
- messages=[
416
- {
417
- "role": "user",
418
- "content": prompt,
419
- }
420
- ],
421
- model="llama-3.3-70b-versatile",
422
- max_tokens=10,
423
- )
424
-
425
- detected_emotion = chat_completion.choices[0].message.content.strip().lower()
426
-
427
- # Validate the detected emotion
428
- if detected_emotion in self.emotional_states:
429
- return detected_emotion
430
- else:
431
- # If LLM returns something not in our list, find closest match
432
- return self.get_closest_emotional_state(detected_emotion)
433
-
434
- except Exception as e:
435
- print(f"Error using Groq for emotion detection: {str(e)}")
436
- return None
437
-
438
- def get_closest_emotional_state(self, input_text):
439
- """Map input text to the closest emotional state"""
440
- # First try simple word matching
441
- for emotion in self.emotional_states:
442
- if emotion in input_text.lower():
443
- return emotion
444
-
445
- # If sentence transformer is available, use semantic similarity
446
- if self.embedding_model:
447
- input_embedding = self.embedding_model.encode([input_text])
448
- similarities = {
449
- emotion: cosine_similarity(input_embedding, embedding)[0][0]
450
- for emotion, embedding in self.emotional_embeddings.items()
451
- }
452
- return max(similarities, key=similarities.get)
453
-
454
- # Default fallback
455
- return "calm"
456
-
457
- def get_closest_rhythm_pattern(self, input_text=None, emotional_state=None):
458
- """Map input text or emotional state to the closest rhythm pattern"""
459
- # If emotional state is provided, use direct mapping
460
- if emotional_state:
461
- # Map emotional states to rhythm patterns
462
- mapping = {
463
- "anxious": "active",
464
- "stressed": "active",
465
- "calm": "calm",
466
- "sad": "relaxed",
467
- "angry": "active",
468
- "fearful": "active",
469
- "confused": "focused",
470
- "happy": "calm"
471
- }
472
- return mapping.get(emotional_state, "calm")
473
-
474
- # If input text is provided and sentence transformer is available
475
- if input_text and self.embedding_model:
476
- input_embedding = self.embedding_model.encode([input_text])
477
- similarities = {
478
- pattern: cosine_similarity(input_embedding, embedding)[0][0]
479
- for pattern, embedding in self.rhythm_embeddings.items()
480
- }
481
- return max(similarities, key=similarities.get)
482
-
483
- # Default fallback
484
- return "calm"
485
-
486
- def transcribe_audio(self, audio_path):
487
- """Transcribe audio using Groq if available"""
488
- if not self.use_groq:
489
- return "Audio transcription requires Groq API. Please enter text instead."
490
-
491
- try:
492
- # Open and read the audio file
493
- with open(audio_path, "rb") as audio_file:
494
- audio_data = audio_file.read()
495
-
496
- # Transcribe the audio using Distil-Whisper
497
- transcription = self.groq_client.audio.transcriptions.create(
498
- file=(os.path.basename(audio_path), audio_data),
499
- model="distil-whisper-large-v3-en",
500
- response_format="verbose_json",
501
- )
502
-
503
- return transcription.text
504
- except Exception as e:
505
- return f"Error in transcription: {str(e)}"
506
-
507
- def analyze_input(self, input_text, audio_path=None):
508
- """Analyze input text and return appropriate emotional state and rhythm patterns"""
 
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")
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", interactive=False)
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
+ © 2024 Your Rhythma Project
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)