MySafeCode commited on
Commit
2904a79
·
verified ·
1 Parent(s): de7f9a6

Upload 6 files

Browse files
Files changed (7) hide show
  1. .dockerignore.txt +4 -0
  2. .gitattributes +1 -0
  3. Dockerfile +25 -0
  4. app.py +605 -0
  5. local.py +611 -0
  6. requirements.txt +3 -0
  7. sound.mp3 +3 -0
.dockerignore.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ .git
4
+ README.md
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ sound.mp3 filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Install system dependencies for pygame
4
+ RUN apt-get update && apt-get install -y \
5
+ libsdl2-mixer-2.0-0 \
6
+ libsdl2-image-2.0-0 \
7
+ libsdl2-2.0-0 \
8
+ libsdl2-ttf-2.0-0 \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ WORKDIR /app
12
+
13
+ # Copy requirements first
14
+ COPY requirements.txt .
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy the rest of the application
18
+ COPY best.py app.py # Rename best.py to app.py for clarity
19
+ COPY sound.mp3 . # If you have a sound file
20
+
21
+ # Hugging Face uses port 7860
22
+ ENV PORT=7860
23
+
24
+ # Run the application
25
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+ import numpy as np
3
+ from flask import Flask, Response, render_template_string, request
4
+ import time
5
+ import os
6
+
7
+ # Initialize Pygame headlessly
8
+ os.environ['SDL_VIDEODRIVER'] = 'dummy'
9
+ pygame.init()
10
+ pygame.mixer.init(frequency=44100, size=-16, channels=2)
11
+
12
+ app = Flask(__name__)
13
+
14
+ class ShaderRenderer:
15
+ def __init__(self, width=640, height=480):
16
+ self.width = width
17
+ self.height = height
18
+ self.mouse_x = width // 2
19
+ self.mouse_y = height // 2
20
+ self.start_time = time.time()
21
+ self.surface = pygame.Surface((width, height))
22
+
23
+ # Sound sources
24
+ self.sound_source = 'none' # 'none', 'pygame', 'browser'
25
+ self.pygame_sound = None
26
+ self.pygame_playing = False
27
+ self.sound_amp = 0.0
28
+
29
+ # Load pygame sound if available
30
+ if os.path.exists('sound.mp3'):
31
+ try:
32
+ self.pygame_sound = pygame.mixer.Sound('sound.mp3')
33
+ print("✅ Pygame sound loaded")
34
+ except:
35
+ print("⚠️ Could not load sound.mp3")
36
+
37
+ def set_mouse(self, x, y):
38
+ self.mouse_x = max(0, min(self.width, x))
39
+ self.mouse_y = max(0, min(self.height, y))
40
+
41
+ def set_sound_source(self, source):
42
+ """Change sound source: none, pygame, browser"""
43
+ self.sound_source = source
44
+
45
+ # Handle pygame sound
46
+ if source == 'pygame':
47
+ if self.pygame_sound and not self.pygame_playing:
48
+ self.pygame_sound.play(loops=-1)
49
+ self.pygame_playing = True
50
+ self.sound_amp = 0.5
51
+ else:
52
+ if self.pygame_playing:
53
+ pygame.mixer.stop()
54
+ self.pygame_playing = False
55
+ self.sound_amp = 0.0
56
+
57
+ def render_frame(self):
58
+ """Render a simple pattern that shader will transform"""
59
+ t = time.time() - self.start_time
60
+
61
+ # Clear
62
+ self.surface.fill((20, 20, 30))
63
+
64
+ # Draw TOP marker (should be at top of canvas)
65
+ pygame.draw.rect(self.surface, (255, 100, 100), (10, 10, 100, 30))
66
+ font = pygame.font.Font(None, 24)
67
+ text = font.render("TOP", True, (255, 255, 255))
68
+ self.surface.blit(text, (20, 15))
69
+
70
+ # Draw BOTTOM marker
71
+ pygame.draw.rect(self.surface, (100, 255, 100), (10, self.height-40, 100, 30))
72
+ text = font.render("BOTTOM", True, (0, 0, 0))
73
+ self.surface.blit(text, (20, self.height-35))
74
+
75
+ # Draw moving circle
76
+ circle_size = 30 + int(20 * np.sin(t * 2))
77
+
78
+ # Color code based on sound source
79
+ if self.sound_source == 'pygame':
80
+ color = (100, 255, 100) # Green for pygame sound
81
+ elif self.sound_source == 'browser':
82
+ color = (100, 100, 255) # Blue for browser sound
83
+ else:
84
+ color = (255, 100, 100) # Red for no sound
85
+
86
+ pygame.draw.circle(self.surface, color,
87
+ (self.mouse_x, self.mouse_y), circle_size)
88
+
89
+ # Draw grid
90
+ for x in range(0, self.width, 50):
91
+ alpha = int(40 + 20 * np.sin(x * 0.1 + t))
92
+ pygame.draw.line(self.surface, (alpha, alpha, 50),
93
+ (x, 0), (x, self.height))
94
+ for y in range(0, self.height, 50):
95
+ alpha = int(40 + 20 * np.cos(y * 0.1 + t))
96
+ pygame.draw.line(self.surface, (alpha, alpha, 50),
97
+ (0, y), (self.width, y))
98
+
99
+ # Sound meter
100
+ meter_width = int(200 * self.sound_amp)
101
+ pygame.draw.rect(self.surface, (60, 60, 60), (self.width-220, 10, 200, 20))
102
+ pygame.draw.rect(self.surface, (100, 255, 100),
103
+ (self.width-220, 10, meter_width, 20))
104
+
105
+ # Shader indicator
106
+ shader_text = font.render("SHADER ON", True, (255, 255, 0))
107
+ self.surface.blit(shader_text, (self.width-150, self.height-30))
108
+
109
+ return pygame.image.tostring(self.surface, 'RGB')
110
+
111
+ def get_frame(self):
112
+ return self.render_frame()
113
+
114
+ renderer = ShaderRenderer()
115
+
116
+ @app.route('/')
117
+ def index():
118
+ return render_template_string('''
119
+ <!DOCTYPE html>
120
+ <html>
121
+ <head>
122
+ <title>Pygame + WebGL Shader + Sound</title>
123
+ <style>
124
+ body {
125
+ margin: 0;
126
+ background: #0a0a0a;
127
+ color: white;
128
+ font-family: Arial, sans-serif;
129
+ display: flex;
130
+ flex-direction: column;
131
+ justify-content: center;
132
+ align-items: center;
133
+ min-height: 100vh;
134
+ }
135
+
136
+ .container {
137
+ text-align: center;
138
+ }
139
+
140
+ canvas {
141
+ width: 640px;
142
+ height: 480px;
143
+ border: 3px solid #4CAF50;
144
+ border-radius: 8px;
145
+ cursor: crosshair;
146
+ display: block;
147
+ margin: 20px 0;
148
+ }
149
+
150
+ .controls {
151
+ background: #1a1a1a;
152
+ padding: 20px;
153
+ border-radius: 8px;
154
+ margin-top: 20px;
155
+ }
156
+
157
+ .button-group {
158
+ display: flex;
159
+ gap: 10px;
160
+ justify-content: center;
161
+ margin: 15px 0;
162
+ flex-wrap: wrap;
163
+ }
164
+
165
+ button {
166
+ background: #333;
167
+ color: white;
168
+ border: none;
169
+ padding: 12px 24px;
170
+ font-size: 16px;
171
+ border-radius: 5px;
172
+ cursor: pointer;
173
+ transition: all 0.3s;
174
+ min-width: 120px;
175
+ }
176
+
177
+ button:hover {
178
+ transform: scale(1.05);
179
+ }
180
+
181
+ button.active {
182
+ background: #4CAF50;
183
+ box-shadow: 0 0 20px #4CAF50;
184
+ }
185
+
186
+ button.shader.active {
187
+ background: #ffaa00;
188
+ color: black;
189
+ }
190
+
191
+ button.sound.active {
192
+ background: #4CAF50;
193
+ }
194
+
195
+ button.browser.active {
196
+ background: #2196F3;
197
+ }
198
+
199
+ .status-bar {
200
+ display: flex;
201
+ justify-content: space-between;
202
+ align-items: center;
203
+ margin-top: 15px;
204
+ padding: 10px;
205
+ background: #222;
206
+ border-radius: 5px;
207
+ }
208
+
209
+ .indicator {
210
+ padding: 5px 10px;
211
+ border-radius: 4px;
212
+ font-size: 14px;
213
+ }
214
+
215
+ .indicator.none { background: #444; }
216
+ .indicator.pygame { background: #4CAF50; }
217
+ .indicator.browser { background: #2196F3; }
218
+ .indicator.shader-on { background: #ffaa00; color: black; }
219
+ .indicator.shader-off { background: #666; }
220
+
221
+ .meter {
222
+ width: 200px;
223
+ height: 20px;
224
+ background: #333;
225
+ border-radius: 10px;
226
+ overflow: hidden;
227
+ }
228
+
229
+ .meter-fill {
230
+ height: 100%;
231
+ width: 0%;
232
+ background: linear-gradient(90deg, #4CAF50, #2196F3);
233
+ transition: width 0.05s;
234
+ }
235
+
236
+ .badge {
237
+ display: inline-block;
238
+ padding: 5px 10px;
239
+ border-radius: 4px;
240
+ margin-left: 10px;
241
+ font-size: 12px;
242
+ }
243
+
244
+ .badge.on {
245
+ background: #4CAF50;
246
+ color: white;
247
+ }
248
+
249
+ .badge.off {
250
+ background: #ff4444;
251
+ color: white;
252
+ }
253
+
254
+ .color-sample {
255
+ display: inline-block;
256
+ width: 12px;
257
+ height: 12px;
258
+ border-radius: 3px;
259
+ margin: 0 5px;
260
+ }
261
+
262
+ .info-text {
263
+ font-size: 12px;
264
+ color: #666;
265
+ margin-top: 15px;
266
+ }
267
+ </style>
268
+ </head>
269
+ <body>
270
+ <div class="container">
271
+ <h1>🎮 Pygame + WebGL Shader + Sound</h1>
272
+
273
+ <canvas id="canvas" width="640" height="480"></canvas>
274
+
275
+ <div class="controls">
276
+ <div class="button-group">
277
+ <button id="shaderBtn" class="shader active" onclick="toggleShader()">
278
+ 🔮 SHADER ON
279
+ </button>
280
+ <span id="shaderBadge" class="badge on">EFFECTS ACTIVE</span>
281
+ </div>
282
+
283
+ <h3>🔊 Sound Source</h3>
284
+ <div class="button-group">
285
+ <button id="btnNone" class="sound active" onclick="setSoundSource('none')">🔇 None</button>
286
+ <button id="btnPygame" class="sound" onclick="setSoundSource('pygame')">🎮 Pygame</button>
287
+ <button id="btnBrowser" class="sound" onclick="setSoundSource('browser')">🌐 Browser</button>
288
+ </div>
289
+
290
+ <div class="status-bar">
291
+ <div>
292
+ <span id="sourceIndicator" class="indicator none">Source: None</span>
293
+ <span id="shaderIndicator" class="indicator shader-on" style="margin-left: 10px;">Shader: ON</span>
294
+ </div>
295
+ <div class="meter">
296
+ <div id="soundMeter" class="meter-fill"></div>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="info-text">
301
+ <span class="color-sample" style="background: #ff6464;"></span> No sound
302
+ <span class="color-sample" style="background: #64ff64;"></span> Pygame sound
303
+ <span class="color-sample" style="background: #6464ff;"></span> Browser sound
304
+ | <span style="color: #ff6464;">🔴 TOP</span> marker should be at top
305
+ | <span style="color: #64ff64;">🟢 BOTTOM</span> at bottom
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <!-- Audio element for browser sound -->
311
+ <audio id="browserAudio" loop style="display:none;">
312
+ <source src="/static/sound.mp3" type="audio/mpeg">
313
+ </audio>
314
+
315
+ <!-- WebGL Shader -->
316
+ <script id="vertex-shader" type="x-shader/x-vertex">
317
+ attribute vec2 position;
318
+ varying vec2 vUv;
319
+ void main() {
320
+ // Flip Y coordinate to fix Pygame orientation
321
+ vUv = vec2(position.x * 0.5 + 0.5, 1.0 - (position.y * 0.5 + 0.5));
322
+ gl_Position = vec4(position, 0.0, 1.0);
323
+ }
324
+ </script>
325
+
326
+ <script id="fragment-shader" type="x-shader/x-fragment">
327
+ precision highp float;
328
+
329
+ uniform sampler2D uTexture;
330
+ uniform float uTime;
331
+ uniform vec2 uMouse;
332
+ uniform vec2 uResolution;
333
+ uniform bool uShaderEnabled;
334
+
335
+ varying vec2 vUv;
336
+
337
+ void main() {
338
+ // Get pixel from pygame texture (now correctly oriented)
339
+ vec4 color = texture2D(uTexture, vUv);
340
+
341
+ if (uShaderEnabled) {
342
+ // SHADER EFFECTS ON
343
+
344
+ // 1. Time-based color shift
345
+ color.r += sin(uTime + vUv.x * 10.0) * 0.2;
346
+ color.g += cos(uTime + vUv.y * 10.0) * 0.2;
347
+
348
+ // 2. Mouse ripple effect
349
+ float dist = distance(vUv, uMouse);
350
+ if (dist < 0.2) {
351
+ float ripple = sin(dist * 50.0 - uTime * 5.0) * 0.5 + 0.5;
352
+ color.rgb += vec3(0.5, 0.2, 0.8) * ripple * 0.5;
353
+ }
354
+
355
+ // 3. Scanlines
356
+ float scanline = sin(vUv.y * uResolution.y * 2.0 + uTime * 10.0) * 0.1 + 0.9;
357
+ color.rgb *= scanline;
358
+
359
+ // 4. Edge glow
360
+ float edge = 1.0 - abs(vUv.x - 0.5) * 2.0;
361
+ edge *= 1.0 - abs(vUv.y - 0.5) * 2.0;
362
+ color.rgb += vec3(0.2, 0.1, 0.5) * edge * sin(uTime) * 0.3;
363
+ }
364
+ // else: SHADER EFFECTS OFF - pure Pygame pixels
365
+
366
+ gl_FragColor = color;
367
+ }
368
+ </script>
369
+
370
+ <script>
371
+ const canvas = document.getElementById('canvas');
372
+ const gl = canvas.getContext('webgl');
373
+ const browserAudio = document.getElementById('browserAudio');
374
+
375
+ if (!gl) {
376
+ alert('WebGL not supported!');
377
+ }
378
+
379
+ let texture = gl.createTexture();
380
+ let startTime = Date.now() / 1000;
381
+ let shaderEnabled = true;
382
+ let mouse = [0.5, 0.5];
383
+ let currentSource = 'none';
384
+
385
+ // UI Elements
386
+ const shaderBtn = document.getElementById('shaderBtn');
387
+ const shaderBadge = document.getElementById('shaderBadge');
388
+ const shaderIndicator = document.getElementById('shaderIndicator');
389
+
390
+ function toggleShader() {
391
+ shaderEnabled = !shaderEnabled;
392
+
393
+ // Update UI
394
+ if (shaderEnabled) {
395
+ shaderBtn.className = 'shader active';
396
+ shaderBtn.innerHTML = '🔮 SHADER ON';
397
+ shaderBadge.className = 'badge on';
398
+ shaderBadge.innerHTML = 'EFFECTS ACTIVE';
399
+ shaderIndicator.className = 'indicator shader-on';
400
+ shaderIndicator.innerHTML = 'Shader: ON';
401
+ } else {
402
+ shaderBtn.className = '';
403
+ shaderBtn.innerHTML = '🎮 SHADER OFF';
404
+ shaderBadge.className = 'badge off';
405
+ shaderBadge.innerHTML = 'PURE PYGAME';
406
+ shaderIndicator.className = 'indicator shader-off';
407
+ shaderIndicator.innerHTML = 'Shader: OFF';
408
+ }
409
+
410
+ // Update shader uniform
411
+ if (program) {
412
+ gl.useProgram(program);
413
+ const uShaderEnabled = gl.getUniformLocation(program, 'uShaderEnabled');
414
+ gl.uniform1i(uShaderEnabled, shaderEnabled);
415
+ }
416
+ }
417
+
418
+ // Sound meter animation
419
+ let soundAmp = 0;
420
+ function updateSoundMeter() {
421
+ if (currentSource === 'browser' && !browserAudio.paused) {
422
+ // Simulate amplitude from browser audio
423
+ soundAmp = 0.3 + 0.2 * Math.sin(Date.now() * 0.01);
424
+ document.getElementById('soundMeter').style.width = (soundAmp * 100) + '%';
425
+ } else if (currentSource === 'pygame') {
426
+ // Get amplitude from server
427
+ fetch('/sound/amp')
428
+ .then(res => res.json())
429
+ .then(data => {
430
+ soundAmp = data.amp;
431
+ document.getElementById('soundMeter').style.width = (soundAmp * 100) + '%';
432
+ });
433
+ } else {
434
+ soundAmp = 0;
435
+ document.getElementById('soundMeter').style.width = '0%';
436
+ }
437
+ requestAnimationFrame(updateSoundMeter);
438
+ }
439
+ updateSoundMeter();
440
+
441
+ function setSoundSource(source) {
442
+ currentSource = source;
443
+
444
+ // Update UI
445
+ document.getElementById('btnNone').className = source === 'none' ? 'sound active' : 'sound';
446
+ document.getElementById('btnPygame').className = source === 'pygame' ? 'sound active' : 'sound';
447
+ document.getElementById('btnBrowser').className = source === 'browser' ? 'sound active' : 'sound';
448
+
449
+ const indicator = document.getElementById('sourceIndicator');
450
+ indicator.className = `indicator ${source}`;
451
+ indicator.innerHTML = `Source: ${source.charAt(0).toUpperCase() + source.slice(1)}`;
452
+
453
+ // Handle audio
454
+ if (source === 'browser') {
455
+ browserAudio.play().catch(e => console.log('Audio play failed:', e));
456
+ } else {
457
+ browserAudio.pause();
458
+ browserAudio.currentTime = 0;
459
+ }
460
+
461
+ // Tell server about source change
462
+ fetch('/sound/source', {
463
+ method: 'POST',
464
+ headers: {'Content-Type': 'application/json'},
465
+ body: JSON.stringify({source: source})
466
+ });
467
+ }
468
+
469
+ // Mouse tracking
470
+ canvas.addEventListener('mousemove', (e) => {
471
+ const rect = canvas.getBoundingClientRect();
472
+ mouse[0] = (e.clientX - rect.left) / rect.width;
473
+ mouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;
474
+
475
+ const x = Math.round((e.clientX - rect.left) * 640 / rect.width);
476
+ const y = Math.round((e.clientY - rect.top) * 480 / rect.height);
477
+
478
+ fetch('/mouse', {
479
+ method: 'POST',
480
+ headers: {'Content-Type': 'application/json'},
481
+ body: JSON.stringify({x, y})
482
+ });
483
+ });
484
+
485
+ // Setup WebGL
486
+ function createShader(type, source) {
487
+ const shader = gl.createShader(type);
488
+ gl.shaderSource(shader, source);
489
+ gl.compileShader(shader);
490
+ return shader;
491
+ }
492
+
493
+ // Compile shaders
494
+ const vertexShader = createShader(gl.VERTEX_SHADER,
495
+ document.getElementById('vertex-shader').textContent);
496
+ const fragmentShader = createShader(gl.FRAGMENT_SHADER,
497
+ document.getElementById('fragment-shader').textContent);
498
+
499
+ // Create program
500
+ const program = gl.createProgram();
501
+ gl.attachShader(program, vertexShader);
502
+ gl.attachShader(program, fragmentShader);
503
+ gl.linkProgram(program);
504
+ gl.useProgram(program);
505
+
506
+ // Set up fullscreen quad
507
+ const vertices = new Float32Array([
508
+ -1, -1, 1, -1, -1, 1,
509
+ -1, 1, 1, -1, 1, 1
510
+ ]);
511
+
512
+ const buffer = gl.createBuffer();
513
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
514
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
515
+
516
+ const position = gl.getAttribLocation(program, 'position');
517
+ gl.enableVertexAttribArray(position);
518
+ gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
519
+
520
+ // Get uniform locations
521
+ const uTexture = gl.getUniformLocation(program, 'uTexture');
522
+ const uTime = gl.getUniformLocation(program, 'uTime');
523
+ const uMouse = gl.getUniformLocation(program, 'uMouse');
524
+ const uResolution = gl.getUniformLocation(program, 'uResolution');
525
+ const uShaderEnabled = gl.getUniformLocation(program, 'uShaderEnabled');
526
+
527
+ gl.uniform1i(uTexture, 0);
528
+ gl.uniform2f(uResolution, canvas.width, canvas.height);
529
+ gl.uniform1i(uShaderEnabled, shaderEnabled);
530
+
531
+ // Texture setup
532
+ gl.bindTexture(gl.TEXTURE_2D, texture);
533
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
534
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
535
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
536
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
537
+
538
+ // Main loop
539
+ function update() {
540
+ fetch('/frame')
541
+ .then(res => res.arrayBuffer())
542
+ .then(buffer => {
543
+ // Update texture with new frame
544
+ gl.bindTexture(gl.TEXTURE_2D, texture);
545
+ gl.texImage2D(
546
+ gl.TEXTURE_2D, 0, gl.RGB, 640, 480, 0,
547
+ gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(buffer)
548
+ );
549
+
550
+ // Update uniforms
551
+ const time = (Date.now() / 1000) - startTime;
552
+ gl.uniform1f(uTime, time);
553
+ gl.uniform2f(uMouse, mouse[0], mouse[1]);
554
+
555
+ // Draw
556
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
557
+
558
+ requestAnimationFrame(update);
559
+ });
560
+ }
561
+
562
+ update();
563
+ </script>
564
+ </body>
565
+ </html>
566
+ ''')
567
+
568
+ @app.route('/frame')
569
+ def frame():
570
+ """Return raw RGB bytes"""
571
+ return Response(renderer.get_frame(), mimetype='application/octet-stream')
572
+
573
+ @app.route('/mouse', methods=['POST'])
574
+ def mouse():
575
+ data = request.json
576
+ renderer.set_mouse(data['x'], data['y'])
577
+ return 'OK'
578
+
579
+ @app.route('/sound/source', methods=['POST'])
580
+ def sound_source():
581
+ data = request.json
582
+ renderer.set_sound_source(data['source'])
583
+ return 'OK'
584
+
585
+ @app.route('/sound/amp')
586
+ def sound_amp():
587
+ return {'amp': renderer.sound_amp}
588
+
589
+ # Serve static sound file
590
+ @app.route('/static/sound.mp3')
591
+ def serve_sound():
592
+ if os.path.exists('sound.mp3'):
593
+ with open('sound.mp3', 'rb') as f:
594
+ return Response(f.read(), mimetype='audio/mpeg')
595
+ return 'Sound not found', 404
596
+
597
+ if __name__ == '__main__':
598
+ print("\n" + "="*70)
599
+ print("🎮 Pygame + WebGL Shader + Sound")
600
+ print("="*70)
601
+ print("🌐 Starting on Hugging Face Spaces")
602
+ print("\n🔮 Shader Toggle: See pure Pygame vs. effects")
603
+ print("="*70 + "\n")
604
+ port = int(os.environ.get('PORT', 7860)) # Use HF port
605
+ app.run(host='0.0.0.0', port=port, debug=False)
local.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+ import numpy as np
3
+ from flask import Flask, Response, render_template_string, request
4
+ import time
5
+ import os
6
+
7
+ # Initialize Pygame headlessly
8
+ os.environ['SDL_VIDEODRIVER'] = 'dummy'
9
+ pygame.init()
10
+ pygame.mixer.init(frequency=44100, size=-16, channels=2)
11
+
12
+ app = Flask(__name__)
13
+
14
+ class ShaderRenderer:
15
+ def __init__(self, width=640, height=480):
16
+ self.width = width
17
+ self.height = height
18
+ self.mouse_x = width // 2
19
+ self.mouse_y = height // 2
20
+ self.start_time = time.time()
21
+ self.surface = pygame.Surface((width, height))
22
+
23
+ # Sound sources
24
+ self.sound_source = 'none' # 'none', 'pygame', 'browser'
25
+ self.pygame_sound = None
26
+ self.pygame_playing = False
27
+ self.sound_amp = 0.0
28
+
29
+ # Load pygame sound if available
30
+ if os.path.exists('sound.mp3'):
31
+ try:
32
+ self.pygame_sound = pygame.mixer.Sound('sound.mp3')
33
+ print("✅ Pygame sound loaded")
34
+ except:
35
+ print("⚠️ Could not load sound.mp3")
36
+
37
+ def set_mouse(self, x, y):
38
+ self.mouse_x = max(0, min(self.width, x))
39
+ self.mouse_y = max(0, min(self.height, y))
40
+
41
+ def set_sound_source(self, source):
42
+ """Change sound source: none, pygame, browser"""
43
+ self.sound_source = source
44
+
45
+ # Handle pygame sound
46
+ if source == 'pygame':
47
+ if self.pygame_sound and not self.pygame_playing:
48
+ self.pygame_sound.play(loops=-1)
49
+ self.pygame_playing = True
50
+ self.sound_amp = 0.5
51
+ else:
52
+ if self.pygame_playing:
53
+ pygame.mixer.stop()
54
+ self.pygame_playing = False
55
+ self.sound_amp = 0.0
56
+
57
+ def render_frame(self):
58
+ """Render a simple pattern that shader will transform"""
59
+ t = time.time() - self.start_time
60
+
61
+ # Clear
62
+ self.surface.fill((20, 20, 30))
63
+
64
+ # Draw TOP marker (should be at top of canvas)
65
+ pygame.draw.rect(self.surface, (255, 100, 100), (10, 10, 100, 30))
66
+ font = pygame.font.Font(None, 24)
67
+ text = font.render("TOP", True, (255, 255, 255))
68
+ self.surface.blit(text, (20, 15))
69
+
70
+ # Draw BOTTOM marker
71
+ pygame.draw.rect(self.surface, (100, 255, 100), (10, self.height-40, 100, 30))
72
+ text = font.render("BOTTOM", True, (0, 0, 0))
73
+ self.surface.blit(text, (20, self.height-35))
74
+
75
+ # Draw moving circle
76
+ circle_size = 30 + int(20 * np.sin(t * 2))
77
+
78
+ # Color code based on sound source
79
+ if self.sound_source == 'pygame':
80
+ color = (100, 255, 100) # Green for pygame sound
81
+ elif self.sound_source == 'browser':
82
+ color = (100, 100, 255) # Blue for browser sound
83
+ else:
84
+ color = (255, 100, 100) # Red for no sound
85
+
86
+ pygame.draw.circle(self.surface, color,
87
+ (self.mouse_x, self.mouse_y), circle_size)
88
+
89
+ # Draw grid
90
+ for x in range(0, self.width, 50):
91
+ alpha = int(40 + 20 * np.sin(x * 0.1 + t))
92
+ pygame.draw.line(self.surface, (alpha, alpha, 50),
93
+ (x, 0), (x, self.height))
94
+ for y in range(0, self.height, 50):
95
+ alpha = int(40 + 20 * np.cos(y * 0.1 + t))
96
+ pygame.draw.line(self.surface, (alpha, alpha, 50),
97
+ (0, y), (self.width, y))
98
+
99
+ # Sound meter
100
+ meter_width = int(200 * self.sound_amp)
101
+ pygame.draw.rect(self.surface, (60, 60, 60), (self.width-220, 10, 200, 20))
102
+ pygame.draw.rect(self.surface, (100, 255, 100),
103
+ (self.width-220, 10, meter_width, 20))
104
+
105
+ # Shader indicator
106
+ shader_text = font.render("SHADER ON", True, (255, 255, 0))
107
+ self.surface.blit(shader_text, (self.width-150, self.height-30))
108
+
109
+ return pygame.image.tostring(self.surface, 'RGB')
110
+
111
+ def get_frame(self):
112
+ return self.render_frame()
113
+
114
+ renderer = ShaderRenderer()
115
+
116
+ @app.route('/')
117
+ def index():
118
+ return render_template_string('''
119
+ <!DOCTYPE html>
120
+ <html>
121
+ <head>
122
+ <title>Pygame + WebGL Shader + Sound</title>
123
+ <style>
124
+ body {
125
+ margin: 0;
126
+ background: #0a0a0a;
127
+ color: white;
128
+ font-family: Arial, sans-serif;
129
+ display: flex;
130
+ flex-direction: column;
131
+ justify-content: center;
132
+ align-items: center;
133
+ min-height: 100vh;
134
+ }
135
+
136
+ .container {
137
+ text-align: center;
138
+ }
139
+
140
+ canvas {
141
+ width: 640px;
142
+ height: 480px;
143
+ border: 3px solid #4CAF50;
144
+ border-radius: 8px;
145
+ cursor: crosshair;
146
+ display: block;
147
+ margin: 20px 0;
148
+ }
149
+
150
+ .controls {
151
+ background: #1a1a1a;
152
+ padding: 20px;
153
+ border-radius: 8px;
154
+ margin-top: 20px;
155
+ }
156
+
157
+ .button-group {
158
+ display: flex;
159
+ gap: 10px;
160
+ justify-content: center;
161
+ margin: 15px 0;
162
+ flex-wrap: wrap;
163
+ }
164
+
165
+ button {
166
+ background: #333;
167
+ color: white;
168
+ border: none;
169
+ padding: 12px 24px;
170
+ font-size: 16px;
171
+ border-radius: 5px;
172
+ cursor: pointer;
173
+ transition: all 0.3s;
174
+ min-width: 120px;
175
+ }
176
+
177
+ button:hover {
178
+ transform: scale(1.05);
179
+ }
180
+
181
+ button.active {
182
+ background: #4CAF50;
183
+ box-shadow: 0 0 20px #4CAF50;
184
+ }
185
+
186
+ button.shader.active {
187
+ background: #ffaa00;
188
+ color: black;
189
+ }
190
+
191
+ button.sound.active {
192
+ background: #4CAF50;
193
+ }
194
+
195
+ button.browser.active {
196
+ background: #2196F3;
197
+ }
198
+
199
+ .status-bar {
200
+ display: flex;
201
+ justify-content: space-between;
202
+ align-items: center;
203
+ margin-top: 15px;
204
+ padding: 10px;
205
+ background: #222;
206
+ border-radius: 5px;
207
+ }
208
+
209
+ .indicator {
210
+ padding: 5px 10px;
211
+ border-radius: 4px;
212
+ font-size: 14px;
213
+ }
214
+
215
+ .indicator.none { background: #444; }
216
+ .indicator.pygame { background: #4CAF50; }
217
+ .indicator.browser { background: #2196F3; }
218
+ .indicator.shader-on { background: #ffaa00; color: black; }
219
+ .indicator.shader-off { background: #666; }
220
+
221
+ .meter {
222
+ width: 200px;
223
+ height: 20px;
224
+ background: #333;
225
+ border-radius: 10px;
226
+ overflow: hidden;
227
+ }
228
+
229
+ .meter-fill {
230
+ height: 100%;
231
+ width: 0%;
232
+ background: linear-gradient(90deg, #4CAF50, #2196F3);
233
+ transition: width 0.05s;
234
+ }
235
+
236
+ .badge {
237
+ display: inline-block;
238
+ padding: 5px 10px;
239
+ border-radius: 4px;
240
+ margin-left: 10px;
241
+ font-size: 12px;
242
+ }
243
+
244
+ .badge.on {
245
+ background: #4CAF50;
246
+ color: white;
247
+ }
248
+
249
+ .badge.off {
250
+ background: #ff4444;
251
+ color: white;
252
+ }
253
+
254
+ .color-sample {
255
+ display: inline-block;
256
+ width: 12px;
257
+ height: 12px;
258
+ border-radius: 3px;
259
+ margin: 0 5px;
260
+ }
261
+
262
+ .info-text {
263
+ font-size: 12px;
264
+ color: #666;
265
+ margin-top: 15px;
266
+ }
267
+ </style>
268
+ </head>
269
+ <body>
270
+ <div class="container">
271
+ <h1>🎮 Pygame + WebGL Shader + Sound</h1>
272
+
273
+ <canvas id="canvas" width="640" height="480"></canvas>
274
+
275
+ <div class="controls">
276
+ <div class="button-group">
277
+ <button id="shaderBtn" class="shader active" onclick="toggleShader()">
278
+ 🔮 SHADER ON
279
+ </button>
280
+ <span id="shaderBadge" class="badge on">EFFECTS ACTIVE</span>
281
+ </div>
282
+
283
+ <h3>🔊 Sound Source</h3>
284
+ <div class="button-group">
285
+ <button id="btnNone" class="sound active" onclick="setSoundSource('none')">🔇 None</button>
286
+ <button id="btnPygame" class="sound" onclick="setSoundSource('pygame')">🎮 Pygame</button>
287
+ <button id="btnBrowser" class="sound" onclick="setSoundSource('browser')">🌐 Browser</button>
288
+ </div>
289
+
290
+ <div class="status-bar">
291
+ <div>
292
+ <span id="sourceIndicator" class="indicator none">Source: None</span>
293
+ <span id="shaderIndicator" class="indicator shader-on" style="margin-left: 10px;">Shader: ON</span>
294
+ </div>
295
+ <div class="meter">
296
+ <div id="soundMeter" class="meter-fill"></div>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="info-text">
301
+ <span class="color-sample" style="background: #ff6464;"></span> No sound
302
+ <span class="color-sample" style="background: #64ff64;"></span> Pygame sound
303
+ <span class="color-sample" style="background: #6464ff;"></span> Browser sound
304
+ | <span style="color: #ff6464;">🔴 TOP</span> marker should be at top
305
+ | <span style="color: #64ff64;">🟢 BOTTOM</span> at bottom
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <!-- Audio element for browser sound -->
311
+ <audio id="browserAudio" loop style="display:none;">
312
+ <source src="/static/sound.mp3" type="audio/mpeg">
313
+ </audio>
314
+
315
+ <!-- WebGL Shader -->
316
+ <script id="vertex-shader" type="x-shader/x-vertex">
317
+ attribute vec2 position;
318
+ varying vec2 vUv;
319
+ void main() {
320
+ // Flip Y coordinate to fix Pygame orientation
321
+ vUv = vec2(position.x * 0.5 + 0.5, 1.0 - (position.y * 0.5 + 0.5));
322
+ gl_Position = vec4(position, 0.0, 1.0);
323
+ }
324
+ </script>
325
+
326
+ <script id="fragment-shader" type="x-shader/x-fragment">
327
+ precision highp float;
328
+
329
+ uniform sampler2D uTexture;
330
+ uniform float uTime;
331
+ uniform vec2 uMouse;
332
+ uniform vec2 uResolution;
333
+ uniform bool uShaderEnabled;
334
+
335
+ varying vec2 vUv;
336
+
337
+ void main() {
338
+ // Get pixel from pygame texture (now correctly oriented)
339
+ vec4 color = texture2D(uTexture, vUv);
340
+
341
+ if (uShaderEnabled) {
342
+ // SHADER EFFECTS ON
343
+
344
+ // 1. Time-based color shift
345
+ color.r += sin(uTime + vUv.x * 10.0) * 0.2;
346
+ color.g += cos(uTime + vUv.y * 10.0) * 0.2;
347
+
348
+ // 2. Mouse ripple effect
349
+ float dist = distance(vUv, uMouse);
350
+ if (dist < 0.2) {
351
+ float ripple = sin(dist * 50.0 - uTime * 5.0) * 0.5 + 0.5;
352
+ color.rgb += vec3(0.5, 0.2, 0.8) * ripple * 0.5;
353
+ }
354
+
355
+ // 3. Scanlines
356
+ float scanline = sin(vUv.y * uResolution.y * 2.0 + uTime * 10.0) * 0.1 + 0.9;
357
+ color.rgb *= scanline;
358
+
359
+ // 4. Edge glow
360
+ float edge = 1.0 - abs(vUv.x - 0.5) * 2.0;
361
+ edge *= 1.0 - abs(vUv.y - 0.5) * 2.0;
362
+ color.rgb += vec3(0.2, 0.1, 0.5) * edge * sin(uTime) * 0.3;
363
+ }
364
+ // else: SHADER EFFECTS OFF - pure Pygame pixels
365
+
366
+ gl_FragColor = color;
367
+ }
368
+ </script>
369
+
370
+ <script>
371
+ const canvas = document.getElementById('canvas');
372
+ const gl = canvas.getContext('webgl');
373
+ const browserAudio = document.getElementById('browserAudio');
374
+
375
+ if (!gl) {
376
+ alert('WebGL not supported!');
377
+ }
378
+
379
+ let texture = gl.createTexture();
380
+ let startTime = Date.now() / 1000;
381
+ let shaderEnabled = true;
382
+ let mouse = [0.5, 0.5];
383
+ let currentSource = 'none';
384
+
385
+ // UI Elements
386
+ const shaderBtn = document.getElementById('shaderBtn');
387
+ const shaderBadge = document.getElementById('shaderBadge');
388
+ const shaderIndicator = document.getElementById('shaderIndicator');
389
+
390
+ function toggleShader() {
391
+ shaderEnabled = !shaderEnabled;
392
+
393
+ // Update UI
394
+ if (shaderEnabled) {
395
+ shaderBtn.className = 'shader active';
396
+ shaderBtn.innerHTML = '🔮 SHADER ON';
397
+ shaderBadge.className = 'badge on';
398
+ shaderBadge.innerHTML = 'EFFECTS ACTIVE';
399
+ shaderIndicator.className = 'indicator shader-on';
400
+ shaderIndicator.innerHTML = 'Shader: ON';
401
+ } else {
402
+ shaderBtn.className = '';
403
+ shaderBtn.innerHTML = '🎮 SHADER OFF';
404
+ shaderBadge.className = 'badge off';
405
+ shaderBadge.innerHTML = 'PURE PYGAME';
406
+ shaderIndicator.className = 'indicator shader-off';
407
+ shaderIndicator.innerHTML = 'Shader: OFF';
408
+ }
409
+
410
+ // Update shader uniform
411
+ if (program) {
412
+ gl.useProgram(program);
413
+ const uShaderEnabled = gl.getUniformLocation(program, 'uShaderEnabled');
414
+ gl.uniform1i(uShaderEnabled, shaderEnabled);
415
+ }
416
+ }
417
+
418
+ // Sound meter animation
419
+ let soundAmp = 0;
420
+ function updateSoundMeter() {
421
+ if (currentSource === 'browser' && !browserAudio.paused) {
422
+ // Simulate amplitude from browser audio
423
+ soundAmp = 0.3 + 0.2 * Math.sin(Date.now() * 0.01);
424
+ document.getElementById('soundMeter').style.width = (soundAmp * 100) + '%';
425
+ } else if (currentSource === 'pygame') {
426
+ // Get amplitude from server
427
+ fetch('/sound/amp')
428
+ .then(res => res.json())
429
+ .then(data => {
430
+ soundAmp = data.amp;
431
+ document.getElementById('soundMeter').style.width = (soundAmp * 100) + '%';
432
+ });
433
+ } else {
434
+ soundAmp = 0;
435
+ document.getElementById('soundMeter').style.width = '0%';
436
+ }
437
+ requestAnimationFrame(updateSoundMeter);
438
+ }
439
+ updateSoundMeter();
440
+
441
+ function setSoundSource(source) {
442
+ currentSource = source;
443
+
444
+ // Update UI
445
+ document.getElementById('btnNone').className = source === 'none' ? 'sound active' : 'sound';
446
+ document.getElementById('btnPygame').className = source === 'pygame' ? 'sound active' : 'sound';
447
+ document.getElementById('btnBrowser').className = source === 'browser' ? 'sound active' : 'sound';
448
+
449
+ const indicator = document.getElementById('sourceIndicator');
450
+ indicator.className = `indicator ${source}`;
451
+ indicator.innerHTML = `Source: ${source.charAt(0).toUpperCase() + source.slice(1)}`;
452
+
453
+ // Handle audio
454
+ if (source === 'browser') {
455
+ browserAudio.play().catch(e => console.log('Audio play failed:', e));
456
+ } else {
457
+ browserAudio.pause();
458
+ browserAudio.currentTime = 0;
459
+ }
460
+
461
+ // Tell server about source change
462
+ fetch('/sound/source', {
463
+ method: 'POST',
464
+ headers: {'Content-Type': 'application/json'},
465
+ body: JSON.stringify({source: source})
466
+ });
467
+ }
468
+
469
+ // Mouse tracking
470
+ canvas.addEventListener('mousemove', (e) => {
471
+ const rect = canvas.getBoundingClientRect();
472
+ mouse[0] = (e.clientX - rect.left) / rect.width;
473
+ mouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;
474
+
475
+ const x = Math.round((e.clientX - rect.left) * 640 / rect.width);
476
+ const y = Math.round((e.clientY - rect.top) * 480 / rect.height);
477
+
478
+ fetch('/mouse', {
479
+ method: 'POST',
480
+ headers: {'Content-Type': 'application/json'},
481
+ body: JSON.stringify({x, y})
482
+ });
483
+ });
484
+
485
+ // Setup WebGL
486
+ function createShader(type, source) {
487
+ const shader = gl.createShader(type);
488
+ gl.shaderSource(shader, source);
489
+ gl.compileShader(shader);
490
+ return shader;
491
+ }
492
+
493
+ // Compile shaders
494
+ const vertexShader = createShader(gl.VERTEX_SHADER,
495
+ document.getElementById('vertex-shader').textContent);
496
+ const fragmentShader = createShader(gl.FRAGMENT_SHADER,
497
+ document.getElementById('fragment-shader').textContent);
498
+
499
+ // Create program
500
+ const program = gl.createProgram();
501
+ gl.attachShader(program, vertexShader);
502
+ gl.attachShader(program, fragmentShader);
503
+ gl.linkProgram(program);
504
+ gl.useProgram(program);
505
+
506
+ // Set up fullscreen quad
507
+ const vertices = new Float32Array([
508
+ -1, -1, 1, -1, -1, 1,
509
+ -1, 1, 1, -1, 1, 1
510
+ ]);
511
+
512
+ const buffer = gl.createBuffer();
513
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
514
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
515
+
516
+ const position = gl.getAttribLocation(program, 'position');
517
+ gl.enableVertexAttribArray(position);
518
+ gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
519
+
520
+ // Get uniform locations
521
+ const uTexture = gl.getUniformLocation(program, 'uTexture');
522
+ const uTime = gl.getUniformLocation(program, 'uTime');
523
+ const uMouse = gl.getUniformLocation(program, 'uMouse');
524
+ const uResolution = gl.getUniformLocation(program, 'uResolution');
525
+ const uShaderEnabled = gl.getUniformLocation(program, 'uShaderEnabled');
526
+
527
+ gl.uniform1i(uTexture, 0);
528
+ gl.uniform2f(uResolution, canvas.width, canvas.height);
529
+ gl.uniform1i(uShaderEnabled, shaderEnabled);
530
+
531
+ // Texture setup
532
+ gl.bindTexture(gl.TEXTURE_2D, texture);
533
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
534
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
535
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
536
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
537
+
538
+ // Main loop
539
+ function update() {
540
+ fetch('/frame')
541
+ .then(res => res.arrayBuffer())
542
+ .then(buffer => {
543
+ // Update texture with new frame
544
+ gl.bindTexture(gl.TEXTURE_2D, texture);
545
+ gl.texImage2D(
546
+ gl.TEXTURE_2D, 0, gl.RGB, 640, 480, 0,
547
+ gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(buffer)
548
+ );
549
+
550
+ // Update uniforms
551
+ const time = (Date.now() / 1000) - startTime;
552
+ gl.uniform1f(uTime, time);
553
+ gl.uniform2f(uMouse, mouse[0], mouse[1]);
554
+
555
+ // Draw
556
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
557
+
558
+ requestAnimationFrame(update);
559
+ });
560
+ }
561
+
562
+ update();
563
+ </script>
564
+ </body>
565
+ </html>
566
+ ''')
567
+
568
+ @app.route('/frame')
569
+ def frame():
570
+ """Return raw RGB bytes"""
571
+ return Response(renderer.get_frame(), mimetype='application/octet-stream')
572
+
573
+ @app.route('/mouse', methods=['POST'])
574
+ def mouse():
575
+ data = request.json
576
+ renderer.set_mouse(data['x'], data['y'])
577
+ return 'OK'
578
+
579
+ @app.route('/sound/source', methods=['POST'])
580
+ def sound_source():
581
+ data = request.json
582
+ renderer.set_sound_source(data['source'])
583
+ return 'OK'
584
+
585
+ @app.route('/sound/amp')
586
+ def sound_amp():
587
+ return {'amp': renderer.sound_amp}
588
+
589
+ # Serve static sound file
590
+ @app.route('/static/sound.mp3')
591
+ def serve_sound():
592
+ if os.path.exists('sound.mp3'):
593
+ with open('sound.mp3', 'rb') as f:
594
+ return Response(f.read(), mimetype='audio/mpeg')
595
+ return 'Sound not found', 404
596
+
597
+ if __name__ == '__main__':
598
+ print("\n" + "="*70)
599
+ print("🎮 Pygame + WebGL Shader + Sound")
600
+ print("="*70)
601
+ print("🌐 http://localhost:5000")
602
+ print("\n🔮 Shader Toggle: See pure Pygame vs. effects")
603
+ print("🔊 Sound Sources:")
604
+ print(" • None - No sound")
605
+ print(" • Pygame - sound.mp3 plays in Pygame (streamed)")
606
+ print(" • Browser - sound.mp3 plays in browser")
607
+ print("\n🎯 Orientation fixed: TOP at top, BOTTOM at bottom")
608
+ print(" Circle color indicates sound source")
609
+ print(" Sound meter shows activity")
610
+ print("="*70 + "\n")
611
+ app.run(host='0.0.0.0', port=5000, debug=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask==2.3.3
2
+ pygame==2.5.2
3
+ numpy==1.24.3
sound.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f6e1d4e2202379f9649940c9b2f5416f7a273310386812b70254d0ff54c00e11
3
+ size 7185837