drenayaz commited on
Commit
15541f8
·
1 Parent(s): ecda75b

Replace pygame with AudioMixer using reachy_mini.media

Browse files

- Custom AudioMixer class mixing music + SFX via push_audio_sample
- WAV files in 16k/ and 44k/ for wireless/lite compatibility
- Auto-detect sample rate from robot
- Add /stop_music endpoint for clean exit
- Replace pygame dependency with soundfile

Files changed (48) hide show
  1. arcade/main.py +225 -64
  2. arcade/sounds/16k/boss.wav +3 -0
  3. arcade/sounds/16k/boss_hit.wav +3 -0
  4. arcade/sounds/16k/bounce.wav +3 -0
  5. arcade/sounds/16k/combo.wav +3 -0
  6. arcade/sounds/16k/death.wav +3 -0
  7. arcade/sounds/16k/duck_run.wav +3 -0
  8. arcade/sounds/16k/ennemy_death.wav +3 -0
  9. arcade/sounds/16k/flappy.wav +3 -0
  10. arcade/sounds/16k/fly.wav +3 -0
  11. arcade/sounds/16k/hit.wav +3 -0
  12. arcade/sounds/16k/jump.wav +3 -0
  13. arcade/sounds/16k/miss.wav +3 -0
  14. arcade/sounds/16k/perfect.wav +3 -0
  15. arcade/sounds/16k/phase.wav +3 -0
  16. arcade/sounds/16k/rythme.wav +3 -0
  17. arcade/sounds/16k/score.wav +3 -0
  18. arcade/sounds/16k/select.wav +3 -0
  19. arcade/sounds/16k/serve.wav +3 -0
  20. arcade/sounds/16k/shoot.wav +3 -0
  21. arcade/sounds/16k/spaceship.wav +3 -0
  22. arcade/sounds/16k/wave.wav +3 -0
  23. arcade/sounds/16k/win.wav +3 -0
  24. arcade/sounds/44k/boss.wav +3 -0
  25. arcade/sounds/44k/boss_hit.wav +3 -0
  26. arcade/sounds/44k/bounce.wav +3 -0
  27. arcade/sounds/44k/combo.wav +3 -0
  28. arcade/sounds/44k/death.wav +3 -0
  29. arcade/sounds/44k/duck_run.wav +3 -0
  30. arcade/sounds/44k/ennemy_death.wav +3 -0
  31. arcade/sounds/44k/flappy.wav +3 -0
  32. arcade/sounds/44k/fly.wav +3 -0
  33. arcade/sounds/44k/hit.wav +3 -0
  34. arcade/sounds/44k/jump.wav +3 -0
  35. arcade/sounds/44k/miss.wav +3 -0
  36. arcade/sounds/44k/perfect.wav +3 -0
  37. arcade/sounds/44k/phase.wav +3 -0
  38. arcade/sounds/44k/rythme.wav +3 -0
  39. arcade/sounds/44k/score.wav +3 -0
  40. arcade/sounds/44k/select.wav +3 -0
  41. arcade/sounds/44k/serve.wav +3 -0
  42. arcade/sounds/44k/shoot.wav +3 -0
  43. arcade/sounds/44k/spaceship.wav +3 -0
  44. arcade/sounds/44k/wave.wav +3 -0
  45. arcade/sounds/44k/win.wav +3 -0
  46. arcade/static/index.html +8 -7
  47. arcade/static/main.js +1 -1
  48. pyproject.toml +1 -1
arcade/main.py CHANGED
@@ -10,15 +10,201 @@ import time
10
  import json
11
  import queue
12
 
 
 
13
  import numpy as np
14
  from scipy.spatial.transform import Rotation as R
15
- import pygame.mixer
16
  from pydantic import BaseModel
17
  from reachy_mini import ReachyMini, ReachyMiniApp
18
  from fastapi import WebSocket, WebSocketDisconnect
19
  from fastapi.responses import FileResponse
20
  import asyncio
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # --- Configuration ---
23
  USE_ESP32 = None # Set by web UI at startup
24
  SERIAL_PORT = "/dev/ttyUSB0"
@@ -76,7 +262,7 @@ def save_scores(data: dict):
76
 
77
  class Arcade(ReachyMiniApp):
78
  custom_app_url: str | None = "http://0.0.0.0:8042"
79
- request_media_backend: str | None = "no_media"
80
 
81
  def __init__(self, *args, **kwargs):
82
  super().__init__(*args, **kwargs)
@@ -139,87 +325,41 @@ class Arcade(ReachyMiniApp):
139
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
140
  port = sys.argv[1] if len(sys.argv) > 1 else SERIAL_PORT
141
 
142
- # --- Sound engine ---
143
- sounds: dict[str, pygame.mixer.Sound] = {}
144
- current_music: str | None = None
145
  music_muted = False
146
  sfx_muted = False
147
-
148
- print("Init sons...")
149
- audio_available = False
150
- for attempt in range(10):
151
- try:
152
- pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=1024)
153
- pygame.mixer.set_num_channels(16)
154
- audio_available = True
155
- break
156
- except pygame.error as e:
157
- if attempt < 9:
158
- print(f" Audio busy, retry {attempt + 1}/10...")
159
- time.sleep(2)
160
- else:
161
- print(f" Audio non disponible: {e}")
162
- print(" L'app fonctionnera sans son.")
163
-
164
- if audio_available:
165
- for event_name, file_name in SFX_MAP.items():
166
- for ext in (".wav", ".ogg", ".mp3"):
167
- path = os.path.join(SOUNDS_DIR, file_name + ext)
168
- if os.path.exists(path):
169
- try:
170
- sounds[event_name] = pygame.mixer.Sound(path)
171
- print(f" SFX: {event_name} -> {file_name}{ext}")
172
- except Exception as e:
173
- print(f" SFX: {event_name} ERREUR: {e}")
174
- break
175
- if event_name not in sounds:
176
- print(f" SFX: {event_name} -> MANQUANT ({file_name}.*)")
177
- print(f" {len(sounds)}/{len(SFX_MAP)} sons charges")
178
 
179
  def play_sfx(name: str):
180
- if audio_available and not sfx_muted and name in sounds:
181
- sounds[name].play()
182
 
183
  def start_music(game_name: str):
184
- nonlocal current_music
185
- if not audio_available:
186
  return
187
  gn = game_name.lower()
188
  if gn in MUSIC_MAP:
189
- music_name = MUSIC_MAP[gn]
190
- for ext in (".mp3", ".wav", ".ogg"):
191
- path = os.path.join(SOUNDS_DIR, music_name + ext)
192
- if os.path.exists(path):
193
- try:
194
- pygame.mixer.music.load(path)
195
- if not music_muted:
196
- pygame.mixer.music.play(-1)
197
- current_music = gn
198
- print(f" [MUSIC] Playing: {music_name}{ext}")
199
- except Exception as e:
200
- print(f" [MUSIC] Erreur chargement {music_name}{ext}: {e}")
201
- return
202
- stop_music()
203
 
204
  def stop_music():
205
- nonlocal current_music
206
- if not audio_available:
207
  return
208
- if current_music:
209
- pygame.mixer.music.fadeout(500)
210
- print(" [MUSIC] Stopped")
211
- current_music = None
212
 
213
  def toggle_music():
214
  nonlocal music_muted
215
- if not audio_available:
216
  return
217
  music_muted = not music_muted
218
  if music_muted:
219
- pygame.mixer.music.pause()
220
  print(" [MUSIC] Muted")
221
  else:
222
- pygame.mixer.music.unpause()
223
  print(" [MUSIC] Unmuted")
224
 
225
  def toggle_sfx():
@@ -405,6 +545,12 @@ class Arcade(ReachyMiniApp):
405
  verified = ping_esp32(ser_ref[0])
406
  return {"verified": verified}
407
 
 
 
 
 
 
 
408
  # --- WebSocket ---
409
  @self.settings_app.websocket("/ws")
410
  async def ws_endpoint(websocket: WebSocket):
@@ -474,6 +620,20 @@ class Arcade(ReachyMiniApp):
474
  # Send all scores to ESP32 so both sides start in sync
475
  send_all_scores_to_esp()
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  # --- Calibration ---
478
  print("Calibration... garde la tete droite")
479
  roll_samples = []
@@ -574,7 +734,8 @@ class Arcade(ReachyMiniApp):
574
  if spent < interval:
575
  time.sleep(interval - spent)
576
  finally:
577
- pygame.mixer.quit()
 
578
  if ser:
579
  ser.close()
580
 
 
10
  import json
11
  import queue
12
 
13
+ import logging
14
+
15
  import numpy as np
16
  from scipy.spatial.transform import Rotation as R
17
+ import soundfile as sf
18
  from pydantic import BaseModel
19
  from reachy_mini import ReachyMini, ReachyMiniApp
20
  from fastapi import WebSocket, WebSocketDisconnect
21
  from fastapi.responses import FileResponse
22
  import asyncio
23
 
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ # ============================================================
28
+ # AudioMixer — mixes music + SFX via reachy_mini.media
29
+ # ============================================================
30
+ class AudioMixer:
31
+ """Single-threaded audio mixer using reachy_mini.media.push_audio_sample."""
32
+
33
+ CHUNK_SIZE = 1024
34
+
35
+ def __init__(self, reachy_mini: ReachyMini):
36
+ self.reachy_mini = reachy_mini
37
+ self._lock = threading.Lock()
38
+ self._stop_event = threading.Event()
39
+ self._thread: threading.Thread | None = None
40
+
41
+ try:
42
+ self._samplerate = reachy_mini.media.get_output_audio_samplerate()
43
+ logger.info("Robot audio sample rate: %d Hz", self._samplerate)
44
+ except Exception:
45
+ self._samplerate = 44100
46
+ logger.warning("Could not detect sample rate, defaulting to 44100Hz")
47
+
48
+ if self._samplerate <= 22050:
49
+ self._audio_folder = os.path.join(SOUNDS_DIR, "16k")
50
+ else:
51
+ self._audio_folder = os.path.join(SOUNDS_DIR, "44k")
52
+ logger.info("Using audio folder: %s", self._audio_folder)
53
+
54
+ self._chunk_duration = self.CHUNK_SIZE / self._samplerate
55
+ self._sounds: dict[str, np.ndarray] = {}
56
+ self._music_data: np.ndarray | None = None
57
+ self._music_pos: int = 0
58
+ self._music_loop: bool = False
59
+ self._music_name: str | None = None
60
+ self._active_sounds: list[list] = []
61
+
62
+ def _load_audio(self, filepath: str) -> np.ndarray | None:
63
+ if not os.path.exists(filepath):
64
+ logger.warning("Audio file not found: %s", filepath)
65
+ return None
66
+ try:
67
+ data, sr = sf.read(filepath, dtype="float32")
68
+ if data.ndim > 1:
69
+ data = np.mean(data, axis=1)
70
+ return np.ascontiguousarray(data, dtype=np.float32)
71
+ except Exception as e:
72
+ logger.warning("Failed to load audio %s: %s", filepath, e)
73
+ return None
74
+
75
+ def preload_sound(self, filename: str) -> bool:
76
+ if filename in self._sounds:
77
+ return True
78
+ data = self._load_audio(os.path.join(self._audio_folder, filename))
79
+ if data is not None:
80
+ with self._lock:
81
+ self._sounds[filename] = data
82
+ return True
83
+ return False
84
+
85
+ def play_sound(self, filename: str) -> bool:
86
+ with self._lock:
87
+ if filename not in self._sounds:
88
+ data = self._load_audio(os.path.join(self._audio_folder, filename))
89
+ if data is None:
90
+ return False
91
+ self._sounds[filename] = data
92
+ self._active_sounds.append([self._sounds[filename], 0])
93
+ return True
94
+
95
+ def play_music(self, filename: str, loop: bool = False) -> bool:
96
+ data = self._load_audio(os.path.join(self._audio_folder, filename))
97
+ if data is None:
98
+ return False
99
+ with self._lock:
100
+ self._music_data = data
101
+ self._music_pos = 0
102
+ self._music_loop = loop
103
+ self._music_name = filename
104
+ return True
105
+
106
+ def stop_music(self) -> None:
107
+ with self._lock:
108
+ self._music_data = None
109
+ self._music_pos = 0
110
+ self._music_name = None
111
+
112
+ def pause_music(self) -> None:
113
+ with self._lock:
114
+ self._music_paused_data = self._music_data
115
+ self._music_paused_pos = self._music_pos
116
+ self._music_data = None
117
+
118
+ def unpause_music(self) -> None:
119
+ with self._lock:
120
+ if hasattr(self, '_music_paused_data') and self._music_paused_data is not None:
121
+ self._music_data = self._music_paused_data
122
+ self._music_pos = self._music_paused_pos
123
+ self._music_paused_data = None
124
+
125
+ def start(self) -> None:
126
+ if self._thread is not None and self._thread.is_alive():
127
+ return
128
+ self._stop_event.clear()
129
+ self._thread = threading.Thread(target=self._mixer_loop, daemon=True)
130
+ self._thread.start()
131
+ logger.info("Audio mixer started")
132
+
133
+ def stop(self) -> None:
134
+ self._stop_event.set()
135
+ if self._thread is not None:
136
+ self._thread.join(timeout=2.0)
137
+ self._thread = None
138
+ try:
139
+ self.reachy_mini.media.stop_playing()
140
+ except Exception:
141
+ pass
142
+
143
+ def _mixer_loop(self) -> None:
144
+ try:
145
+ self.reachy_mini.media.start_playing()
146
+ except Exception as e:
147
+ logger.error("Failed to start audio playback: %s", e)
148
+ return
149
+
150
+ start_time = time.time()
151
+ chunks_pushed = 0
152
+
153
+ try:
154
+ while not self._stop_event.is_set():
155
+ output = np.zeros(self.CHUNK_SIZE, dtype=np.float32)
156
+
157
+ with self._lock:
158
+ # Mix music
159
+ if self._music_data is not None:
160
+ end = self._music_pos + self.CHUNK_SIZE
161
+ if end <= len(self._music_data):
162
+ output += self._music_data[self._music_pos:end]
163
+ self._music_pos = end
164
+ else:
165
+ remaining = len(self._music_data) - self._music_pos
166
+ if remaining > 0:
167
+ output[:remaining] += self._music_data[self._music_pos:]
168
+ if self._music_loop:
169
+ self._music_pos = self.CHUNK_SIZE - remaining
170
+ if self._music_pos > 0:
171
+ output[remaining:] += self._music_data[:self._music_pos]
172
+ else:
173
+ self._music_data = None
174
+ self._music_pos = 0
175
+ self._music_name = None
176
+
177
+ # Mix SFX
178
+ still_active = []
179
+ for entry in self._active_sounds:
180
+ data, pos = entry
181
+ end = pos + self.CHUNK_SIZE
182
+ if end <= len(data):
183
+ output += data[pos:end]
184
+ entry[1] = end
185
+ still_active.append(entry)
186
+ else:
187
+ remaining = len(data) - pos
188
+ if remaining > 0:
189
+ output[:remaining] += data[pos:]
190
+ self._active_sounds = still_active
191
+
192
+ np.clip(output, -1.0, 1.0, out=output)
193
+ self.reachy_mini.media.push_audio_sample(output)
194
+ chunks_pushed += 1
195
+
196
+ target_time = start_time + (chunks_pushed * self._chunk_duration)
197
+ sleep_time = target_time - time.time()
198
+ if sleep_time > 0:
199
+ time.sleep(sleep_time)
200
+ except Exception as e:
201
+ logger.error("Mixer loop error: %s", e)
202
+ finally:
203
+ try:
204
+ self.reachy_mini.media.stop_playing()
205
+ except Exception:
206
+ pass
207
+
208
  # --- Configuration ---
209
  USE_ESP32 = None # Set by web UI at startup
210
  SERIAL_PORT = "/dev/ttyUSB0"
 
262
 
263
  class Arcade(ReachyMiniApp):
264
  custom_app_url: str | None = "http://0.0.0.0:8042"
265
+ request_media_backend: str | None = "gstreamer_no_video"
266
 
267
  def __init__(self, *args, **kwargs):
268
  super().__init__(*args, **kwargs)
 
325
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
326
  port = sys.argv[1] if len(sys.argv) > 1 else SERIAL_PORT
327
 
328
+ # --- Sound engine (AudioMixer via reachy_mini.media) ---
 
 
329
  music_muted = False
330
  sfx_muted = False
331
+ mixer = None # initialized after mode selection
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  def play_sfx(name: str):
334
+ if mixer and not sfx_muted and name in SFX_MAP:
335
+ mixer.play_sound(SFX_MAP[name] + ".wav")
336
 
337
  def start_music(game_name: str):
338
+ if not mixer:
 
339
  return
340
  gn = game_name.lower()
341
  if gn in MUSIC_MAP:
342
+ wav_name = MUSIC_MAP[gn] + ".wav"
343
+ if not music_muted:
344
+ mixer.play_music(wav_name, loop=True)
345
+ print(f" [MUSIC] Playing: {wav_name}")
 
 
 
 
 
 
 
 
 
 
346
 
347
  def stop_music():
348
+ if not mixer:
 
349
  return
350
+ mixer.stop_music()
351
+ print(" [MUSIC] Stopped")
 
 
352
 
353
  def toggle_music():
354
  nonlocal music_muted
355
+ if not mixer:
356
  return
357
  music_muted = not music_muted
358
  if music_muted:
359
+ mixer.pause_music()
360
  print(" [MUSIC] Muted")
361
  else:
362
+ mixer.unpause_music()
363
  print(" [MUSIC] Unmuted")
364
 
365
  def toggle_sfx():
 
545
  verified = ping_esp32(ser_ref[0])
546
  return {"verified": verified}
547
 
548
+ @self.settings_app.post("/stop_music")
549
+ def api_stop_music():
550
+ """Stop music playback."""
551
+ stop_music()
552
+ return {"ok": True}
553
+
554
  # --- WebSocket ---
555
  @self.settings_app.websocket("/ws")
556
  async def ws_endpoint(websocket: WebSocket):
 
620
  # Send all scores to ESP32 so both sides start in sync
621
  send_all_scores_to_esp()
622
 
623
+ # --- Init audio mixer (after media is released by daemon) ---
624
+ print("Init sons...")
625
+ mixer = AudioMixer(reachy_mini)
626
+ loaded = 0
627
+ for event_name, file_name in SFX_MAP.items():
628
+ wav_name = file_name + ".wav"
629
+ if mixer.preload_sound(wav_name):
630
+ print(f" SFX: {event_name} -> {wav_name}")
631
+ loaded += 1
632
+ else:
633
+ print(f" SFX: {event_name} -> MANQUANT ({wav_name})")
634
+ print(f" {loaded}/{len(SFX_MAP)} sons charges")
635
+ mixer.start()
636
+
637
  # --- Calibration ---
638
  print("Calibration... garde la tete droite")
639
  roll_samples = []
 
734
  if spent < interval:
735
  time.sleep(interval - spent)
736
  finally:
737
+ if mixer:
738
+ mixer.stop()
739
  if ser:
740
  ser.close()
741
 
arcade/sounds/16k/boss.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80d1a683c0d2c8f0a3aaf51626ccd8aa28b5c8f18bf53452d4b16f8b0a267a54
3
+ size 5537166
arcade/sounds/16k/boss_hit.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6752ea7bbe74fe9d3187cfa80db54aa476546f9d86d1767f1ef72d09b91fbf68
3
+ size 3244
arcade/sounds/16k/bounce.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58dd3f4789c72d66ddbb46167b0eeee75fa71884b6d5b126e98a42674923e2dd
3
+ size 3244
arcade/sounds/16k/combo.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:827e6116e2a67c6499d8a703844f68a42011c2bca7c5dcef753234b1c7634629
3
+ size 9004
arcade/sounds/16k/death.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b5fb4052ade0d786b2067e38d944fb39f3e7ad53a045e63fc96f543fb3d06c6
3
+ size 12582
arcade/sounds/16k/duck_run.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:af044b6f9fdf66725e87209b63e26d3434674ad19a3c2286b8d3f80da43d2f8f
3
+ size 2744364
arcade/sounds/16k/ennemy_death.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e0d68bc7e65268eb5953a9124181adb2d82a00b1f1eb66a1f536c0309f793dde
3
+ size 20106
arcade/sounds/16k/flappy.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:84f3fcee96d6911ce6910b76833b7d80f2fd8e6a8872fb12103cd1ef8f22b525
3
+ size 1043270
arcade/sounds/16k/fly.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37ab4edd6075a65b6e68eff6e46c8d40d3c41e8c2d242b694b8fe99eb2e0e5c9
3
+ size 163628
arcade/sounds/16k/hit.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:511331d46c8ce44071cd45c5cd371515a14c9d4e70d7bd4edf66e51a9832c7e8
3
+ size 1964
arcade/sounds/16k/jump.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5aa07d16bd403069711b2c542ebdb7e58f6dc94521d45f3ad5d9a95dca1ff42d
3
+ size 9238
arcade/sounds/16k/miss.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a0456ddc6c34e54c3b0b247ff5c682883197a1e7df9f4df94a898bc60b0aabd
3
+ size 4842
arcade/sounds/16k/perfect.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f5731c5bcef49982a12e65c132585eab4ec5c736c9f07c6eeb490cb22a4099ea
3
+ size 5164
arcade/sounds/16k/phase.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:94857296a1d86a339f9e7f7ef5478afcc891d17a789741869915e9b0825928ef
3
+ size 12844
arcade/sounds/16k/rythme.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc7163180cb81c46f874e7ccc3dbfb8368e3ff3c5d83477042702810c6965a59
3
+ size 1966124
arcade/sounds/16k/score.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5276b4b6583bcd622fcd624aee12c0b547d2011f16bae9e4de6a8668aa7b4b8d
3
+ size 6444
arcade/sounds/16k/select.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5da7adfbea5526404ba5e314c9077d71aa5e1633670a13e683a886e5801cee46
3
+ size 3882
arcade/sounds/16k/serve.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4e7cd1b2202ad326eb497a2a41fa4b4b5aea43729fe384be9b96e745857c9990
3
+ size 3884
arcade/sounds/16k/shoot.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a92c7cc9bc71d0be4ed4fd3b5c4a84d73263430381f420fb74d43f4deb95137
3
+ size 2604
arcade/sounds/16k/spaceship.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a1fab4ddc45f7f310272e91b76c5faeb6cbaa3de4ea1af20cb796084c625616
3
+ size 6078762
arcade/sounds/16k/wave.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b7d2415db968b0bb98743715b6949950f5079822ff7a3e1a77683bbd9620a888
3
+ size 11242
arcade/sounds/16k/win.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:177c1154924935fce635cc17c82b338b3a8524025be7ebd1433a279d48b4c181
3
+ size 17642
arcade/sounds/44k/boss.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56ee028a6900a2f096eb37ea7ad964c3a99b82eff33519b51945ab5cdc20b93f
3
+ size 15261740
arcade/sounds/44k/boss_hit.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f60f45d49fb0a4eb53a098ee57837ef0c2e0bfa56815c22697a906fe1e1841be
3
+ size 8864
arcade/sounds/44k/bounce.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3d1c0078b200342770c0b43d650b8968988fa00ca105d847b6fb6f8dc521d80
3
+ size 8864
arcade/sounds/44k/combo.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43805be545b78a0f46f6d7caf010b5533a842fc54f6bee94e620a69b2b21405a
3
+ size 24740
arcade/sounds/44k/death.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3e9b7a3422665d4a5c5cb0df7e02dee7d88aecb95689037682bc233b9dff87fa
3
+ size 34604
arcade/sounds/44k/duck_run.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5d439c72171b79e22a0e836fb134cf2864b883611e6a6f43045bb4c0a388e483
3
+ size 7564076
arcade/sounds/44k/ennemy_death.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:85dc90b6b952c2d453dc5103c05fa06a225eab7762eb150b158dbd033434f743
3
+ size 55340
arcade/sounds/44k/flappy.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:388e1a892c1be85d0844944fb3907559aee5e93c67aaa8d5a3c38c1e6152caa2
3
+ size 2875436
arcade/sounds/44k/fly.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a7d8b49963394730b787b09e087ab992035b7db3b02dcd076ed41870a4813205
3
+ size 450922
arcade/sounds/44k/hit.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1d140554748ffed43b021790f0f09e96eaa32ad4556d443834a463cf43155ef6
3
+ size 5336
arcade/sounds/44k/jump.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9530d0e1c660875dff0ea530d253726bdbffaff0cec624a3a6364d819bf57f5
3
+ size 25388
arcade/sounds/44k/miss.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3db5ebd68a9b33442cad8d682b12cb34d7ae9a23562ee59dd27c6ef31b88f7d0
3
+ size 13272
arcade/sounds/44k/perfect.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:622d842c8d4850fc0de1d1b697f8a04ca4cfc7e09c6a41f84995ae92b1c1fd17
3
+ size 14156
arcade/sounds/44k/phase.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:93369e58985b6985c44f1e71f7758798df757d7e8a3f9f4c5ca2ee272ef9917d
3
+ size 35324
arcade/sounds/44k/rythme.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c7311785e30ce0f9c58cec2d0e53df4a5679f1d55a7fa12a3e4f989659bf799
3
+ size 5419052
arcade/sounds/44k/score.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fcd697de56e8e92e8767fe130b63476183b37bdc5b7e62f475d278e453296de4
3
+ size 17684
arcade/sounds/44k/select.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e0ccbd6673a11cb9afba1339fea9dde4060418fceca386de08b8430ed10d4a60
3
+ size 10624
arcade/sounds/44k/serve.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8586f543ceda5020a460c383f938e57ecdf4415c4c1e3a6c80b7b488884d2247
3
+ size 10628
arcade/sounds/44k/shoot.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a96fb4a5017851544f44fedfc5715b8d48c9d6db6d30e741293141e3bdd3bdb
3
+ size 7100
arcade/sounds/44k/spaceship.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce05d2a0525b013faa6f566974da88fe7c53db2f7548b21ae58ae72f7c31dfa2
3
+ size 16754514
arcade/sounds/44k/wave.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f2f613035beaa49786bc8ea5a42f141c99d9a234bf42fd317ca225e30b6ee6c5
3
+ size 30912
arcade/sounds/44k/win.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d11e4f84f31af78ad9d8e996d9b9c9da68d301d5a2897b8e29c2752ddafa65bc
3
+ size 48552
arcade/static/index.html CHANGED
@@ -250,7 +250,8 @@
250
  }
251
 
252
  function backToLanding() {
253
- window.location.reload();
 
254
  }
255
 
256
  function backToSetup() {
@@ -278,13 +279,13 @@
278
  method: 'POST',
279
  headers: {'Content-Type': 'application/json'},
280
  body: JSON.stringify({use_esp32: false, port: ''})
281
- }).then(() => {
282
- modeSet = true;
283
- window.ARCADE_CANVAS_ID = 'arcade-web';
284
- const s = document.createElement('script');
285
- s.src = '/static/main.js';
286
- document.body.appendChild(s);
287
  });
 
 
 
 
 
 
288
  }
289
  </script>
290
  </body>
 
250
  }
251
 
252
  function backToLanding() {
253
+ fetch('/stop_music', {method: 'POST'});
254
+ setTimeout(() => window.location.reload(), 200);
255
  }
256
 
257
  function backToSetup() {
 
279
  method: 'POST',
280
  headers: {'Content-Type': 'application/json'},
281
  body: JSON.stringify({use_esp32: false, port: ''})
 
 
 
 
 
 
282
  });
283
+ // Load game immediately (don't wait for /mode response — bridge may already be running)
284
+ modeSet = true;
285
+ window.ARCADE_CANVAS_ID = 'arcade-web';
286
+ const s = document.createElement('script');
287
+ s.src = '/static/main.js';
288
+ document.body.appendChild(s);
289
  }
290
  </script>
291
  </body>
arcade/static/main.js CHANGED
@@ -2988,4 +2988,4 @@ class ArcadeApp {
2988
  }
2989
 
2990
  // Boot — runs immediately (script is loaded dynamically after DOM is ready)
2991
- new ArcadeApp();
 
2988
  }
2989
 
2990
  // Boot — runs immediately (script is loaded dynamically after DOM is ready)
2991
+ window._arcadeApp = new ArcadeApp();
pyproject.toml CHANGED
@@ -13,7 +13,7 @@ dependencies = [
13
  "reachy-mini",
14
  "scipy",
15
  "pyserial",
16
- "pygame",
17
  "websockets",
18
  ]
19
  keywords = ["reachy-mini-app"]
 
13
  "reachy-mini",
14
  "scipy",
15
  "pyserial",
16
+ "soundfile",
17
  "websockets",
18
  ]
19
  keywords = ["reachy-mini-app"]