-
Notifications
You must be signed in to change notification settings - Fork 0
/
pong-audio.py
449 lines (390 loc) · 17 KB
/
pong-audio.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
"""
Based on: https://gist.github.com/xjcl/8ce64008710128f3a076
Modified by PedroLopes and ShanYuanTeng for Intro to HCI class but credit remains with author
HOW TO SETUP:
Start the python game: >python3 pong-audio.py
HOW TO PLAY:
Well.. use your auditory interface.
p.s.: Player 1 controls the left paddle: UP (W) DOWN (S) <- change this to auditory interface
Player 2controls the right paddle: UP (O) DOWN (L)
HOW TO QUIT:
Say "quit".
HOW TO INSTALL:
Follow https://hciintro19.plopes.org/wiki/doku.php?id=assignment9
p.s.: this needs 10x10 image in the same directory: "white_square.png".
"""
#native imports
import math
import random
import pyglet
import sys
from playsound import playsound
# speech recognition library
# -------------------------------------#
# threading so that listenting to speech would not block the whole program
import threading
# speech recognition (default using google, requiring internet)
import speech_recognition as sr
# -------------------------------------#
# pitch & volume detection
# -------------------------------------#
import aubio
import numpy as num
import pyaudio
import wave
# -------------------------------------#
quit = False
debug = 1
# pitch & volume detection
# -------------------------------------#
# PyAudio object.
p = pyaudio.PyAudio()
# Open stream.
stream = p.open(format=pyaudio.paFloat32,
channels=1, rate=44100, input=True,
frames_per_buffer=1024)
# Aubio's pitch detection.
pDetection = aubio.pitch("default", 2048,
2048//2, 44100)
# Set unit.
pDetection.set_unit("Hz")
pDetection.set_silence(-40)
# -------------------------------------#
# keeping score of points:
p1_score = 0
p2_score = 0
#play some fun sounds?
def hit():
playsound('hit.wav', False)
hit()
# speech recognition functions using google api
# -------------------------------------#
def listen_to_speech():
global quit
while not quit:
# obtain audio from the microphone
r = sr.Recognizer()
with sr.Microphone() as source:
print("[speech recognition] Say something!")
audio = r.listen(source)
# recognize speech using Google Speech Recognition
try:
# for testing purposes, we're just using the default API key
# to use another API key, use `r.recognize_google(audio, key="GOOGLE_SPEECH_RECOGNITION_API_KEY")`
# instead of `r.recognize_google(audio)`
recog_results = r.recognize_google(audio)
print("[speech recognition] Google Speech Recognition thinks you said \"" + recog_results + "\"")
# if recognizing quit and exit then exit the program
if recog_results == "quit" or recog_results == "exit":
quit = True
except sr.UnknownValueError:
print("[speech recognition] Google Speech Recognition could not understand audio")
except sr.RequestError as e:
print("[speech recognition] Could not request results from Google Speech Recognition service; {0}".format(e))
# -------------------------------------#
# pitch & volume detection
# -------------------------------------#
def sense_microphone():
global quit
while not quit:
data = stream.read(1024,exception_on_overflow=False)
samples = num.fromstring(data,
dtype=aubio.float_type)
# Compute the pitch of the microphone input
pitch = pDetection(samples)[0]
# Compute the energy (volume) of the mic input
volume = num.sum(samples**2)/len(samples)
# Format the volume output so that at most
# it has six decimal numbers.
volume = "{:.6f}".format(volume)
# uncomment these lines if you want pitch or volume
print("pitch "+str(pitch)+" volume "+str(volume))
# -------------------------------------#
class Ball(object):
def __init__(self):
self.debug = 0
self.TO_SIDE = 5
self.x = 50.0 + self.TO_SIDE
self.y = float( random.randint(0, 450) )
self.x_old = self.x # coordinates in the last frame
self.y_old = self.y
self.vec_x = 2**0.5 / 2 # sqrt(2)/2
self.vec_y = random.choice([-1, 1]) * 2**0.5 / 2
class Player(object):
def __init__(self, NUMBER, screen_WIDTH=800):
"""NUMBER must be 0 (left player) or 1 (right player)."""
self.NUMBER = NUMBER
self.x = 50.0 + (screen_WIDTH - 100) * NUMBER
self.y = 50.0
self.last_movements = [0]*4 # short movement history
# used for bounce calculation
self.up_key, self.down_key = None, None
if NUMBER == 0:
self.up_key = pyglet.window.key.W
self.down_key = pyglet.window.key.S
elif NUMBER == 1:
self.up_key = pyglet.window.key.O
self.down_key = pyglet.window.key.L
class Model(object):
"""Model of the entire game. Has two players and one ball."""
def __init__(self, DIMENSIONS=(800, 450)):
"""DIMENSIONS is a tuple (WIDTH, HEIGHT) of the field."""
# OBJECTS
WIDTH = DIMENSIONS[0]
self.players = [Player(0, WIDTH), Player(1, WIDTH)]
self.ball = Ball()
# DATA
self.pressed_keys = set() # set has no duplicates
self.quit_key = pyglet.window.key.Q
self.menu_key = pyglet.window.key.SPACE
self.level_1_key = pyglet.window.key._1
self.level_2_key = pyglet.window.key._2
self.level_3_key = pyglet.window.key._3
self.speed = 6 # in pixels per frame
self.ball_speed = self.speed #* 2.5
self.WIDTH, self.HEIGHT = DIMENSIONS
# STATE VARS
self.menu = 0 # 0: menu, 1: game
self.level = 1
self.paused = False
self.i = 0 # "frame count" for debug
def reset_ball(self, who_scored):
"""Place the ball anew on the loser's side."""
if debug: print(str(who_scored)+" scored. reset.")
self.ball.y = float( random.randint(0, self.HEIGHT) )
self.ball.vec_y = random.choice([-1, 1]) * 2**0.5 / 2
if who_scored == 0:
self.ball.x = self.WIDTH - 50.0 - self.ball.TO_SIDE
self.ball.vec_x = - 2**0.5 / 2
elif who_scored == 1:
self.ball.x = 50.0 + self.ball.TO_SIDE
self.ball.vec_x = + 2**0.5 / 2
elif who_scored == "debug":
self.ball.x = 70 # in paddle atm -> usage: hold f
self.ball.y = self.ball.debug
self.ball.vec_x = -1
self.ball.vec_y = 0
self.ball.debug += 0.2
if self.ball.debug > 100:
self.ball.debug = 0
def check_if_oob_top_bottom(self):
"""Called by update_ball to recalc. a ball above/below the screen."""
# bounces. if -- bounce on top of screen. elif -- bounce on bottom.
b = self.ball
if b.y - b.TO_SIDE < 0:
illegal_movement = 0 - (b.y - b.TO_SIDE)
b.y = 0 + b.TO_SIDE + illegal_movement
b.vec_y *= -1
elif b.y + b.TO_SIDE > self.HEIGHT:
illegal_movement = self.HEIGHT - (b.y + b.TO_SIDE)
b.y = self.HEIGHT - b.TO_SIDE + illegal_movement
b.vec_y *= -1
def check_if_oob_sides(self):
global p2_score, p1_score
"""Called by update_ball to reset a ball left/right of the screen."""
b = self.ball
if b.x + b.TO_SIDE < 0: # leave on left
self.reset_ball(1)
p2_score+=1
elif b.x - b.TO_SIDE > self.WIDTH: # leave on right
p1_score+=1
self.reset_ball(0)
def check_if_paddled(self):
"""Called by update_ball to recalc. a ball hit with a player paddle."""
b = self.ball
p0, p1 = self.players[0], self.players[1]
angle = math.acos(b.vec_y)
factor = random.randint(5, 15)
cross0 = (b.x < p0.x + 2*b.TO_SIDE) and (b.x_old >= p0.x + 2*b.TO_SIDE)
cross1 = (b.x > p1.x - 2*b.TO_SIDE) and (b.x_old <= p1.x - 2*b.TO_SIDE)
if cross0 and -25 < b.y - p0.y < 25:
hit()
if debug: print("hit at "+str(self.i))
illegal_movement = p0.x + 2*b.TO_SIDE - b.x
b.x = p0.x + 2*b.TO_SIDE + illegal_movement
angle -= sum(p0.last_movements) / factor / self.ball_speed
b.vec_y = math.cos(angle)
b.vec_x = (1**2 - b.vec_y**2) ** 0.5
elif cross1 and -25 < b.y - p1.y < 25:
hit()
if debug: print("hit at "+str(self.i))
illegal_movement = p1.x - 2*b.TO_SIDE - b.x
b.x = p1.x - 2*b.TO_SIDE + illegal_movement
angle -= sum(p1.last_movements) / factor / self.ball_speed
b.vec_y = math.cos(angle)
b.vec_x = - (1**2 - b.vec_y**2) ** 0.5
# -------------- Ball position: you can find it here -------
def update_ball(self):
"""
Update ball position with post-collision detection.
I.e. Let the ball move out of bounds and calculate
where it should have been within bounds.
When bouncing off a paddle, take player velocity into
consideration as well. Add a small factor of random too.
"""
self.i += 1 # "debug"
b = self.ball
b.x_old, b.y_old = b.x, b.y
b.x += b.vec_x * self.ball_speed
b.y += b.vec_y * self.ball_speed
self.check_if_oob_top_bottom() # oob: out of bounds
self.check_if_oob_sides()
self.check_if_paddled()
# -------------- Menu -------
def toggle_menu(self):
if (self.menu != 0):
self.menu = 0
self.paused = True
else:
self.menu = 1
self.paused = False
def update(self):
"""Work through all pressed keys, update and call update_ball."""
# you can change these to voice input too
pks = self.pressed_keys
if quit:
sys.exit(1)
if self.quit_key in pks:
exit(0)
if self.menu_key in pks:
self.toggle_menu()
pks.remove(self.menu_key) # debounce: get rid of quick duplicated presses
if self.level_1_key in pks:
self.level = 1
self.ball_speed = self.speed
pks.remove(self.level_1_key)
if self.level_2_key in pks:
self.level = 2
self.ball_speed = self.speed*2
pks.remove(self.level_2_key)
if self.level_3_key in pks:
self.level = 3
self.ball_speed = self.speed*3
pks.remove(self.level_3_key)
if pyglet.window.key.R in pks and debug:
self.reset_ball(1)
if pyglet.window.key.F in pks and debug:
self.reset_ball("debug")
if not self.paused:
# -------------- If you want to change paddle position, change it here
# player 1: the user controls the left player by W/S but you should change it to VOICE input
p1 = self.players[0]
p1.last_movements.pop(0)
if p1.up_key in pks and p1.down_key not in pks: #change this to voice input
p1.y -= self.speed
p1.last_movements.append(-self.speed)
elif p1.up_key not in pks and p1.down_key in pks: #change this to voice input
p1.y += self.speed
p1.last_movements.append(+self.speed)
else:
# notice how we popped from _place_ zero,
# but append _a number_ zero here. it's not the same.
p1.last_movements.append(0)
# ----------------- DO NOT CHANGE BELOW ----------------
# player 2: the other user controls the right player by O/L
p2 = self.players[1]
p2.last_movements.pop(0)
if p2.up_key in pks and p2.down_key not in pks: #change this to voice input
p2.y -= self.speed
p2.last_movements.append(-self.speed)
elif p2.up_key not in pks and p2.down_key in pks: #change this to voice input
p2.y += self.speed
p2.last_movements.append(+self.speed)
else:
# notice how we popped from _place_ zero,
# but append _a number_ zero here. it's not the same.
p2.last_movements.append(0)
self.update_ball()
class Controller(object):
def __init__(self, model):
self.m = model
def on_key_press(self, symbol, modifiers):
# `a |= b`: mathematical or. add to set a if in set a or b.
# equivalent to `a = a | b`.
# XXX p0 holds down both keys => p1 controls break # PYGLET!? D:
self.m.pressed_keys |= set([symbol])
def on_key_release(self, symbol, modifiers):
if symbol in self.m.pressed_keys:
self.m.pressed_keys.remove(symbol)
def update(self):
self.m.update()
class View(object):
def __init__(self, window, model):
self.w = window
self.m = model
# ------------------ IMAGES --------------------#
# "white_square.png" is a 10x10 white image
lplayer = pyglet.resource.image("white_square.png")
self.player_spr = pyglet.sprite.Sprite(lplayer)
def redraw_game(self):
# ------------------ PLAYERS --------------------#
TO_SIDE = self.m.ball.TO_SIDE
for p in self.m.players:
self.player_spr.x = p.x//1 - TO_SIDE
# oh god! pyglet's (0, 0) is bottom right! madness.
self.player_spr.y = self.w.height - (p.y//1 + TO_SIDE)
self.player_spr.draw() # these 3 lines: pretend-paddle
self.player_spr.y -= 2*TO_SIDE; self.player_spr.draw()
self.player_spr.y += 4*TO_SIDE; self.player_spr.draw()
# ------------------ BALL --------------------#
self.player_spr.x = self.m.ball.x//1 - TO_SIDE
self.player_spr.y = self.w.height - (self.m.ball.y//1 + TO_SIDE)
self.player_spr.draw()
def redraw_menu(self):
self.start_label = pyglet.text.Label("press space to start", font_name='San Serif', font_size=36, x=self.w.width//2, y=self.w.height//2, anchor_x='center', anchor_y='center')
self.level_label = pyglet.text.Label("easy | hard | insane", font_name='San Serif', font_size=24, x=self.w.width//2, y=self.w.height//2+100, anchor_x='center', anchor_y='center')
if (self.m.level == 1):
self.level_indicator_label = pyglet.text.Label("------", font_name='San Serif', font_size=24, x=self.w.width//2-105, y=self.w.height//2+80, anchor_x='center', anchor_y='center')
elif (self.m.level == 2):
self.level_indicator_label = pyglet.text.Label("------", font_name='San Serif', font_size=24, x=self.w.width//2-12, y=self.w.height//2+80, anchor_x='center', anchor_y='center')
elif (self.m.level == 3):
self.level_indicator_label = pyglet.text.Label("---------", font_name='San Serif', font_size=24, x=self.w.width//2+92, y=self.w.height//2+80, anchor_x='center', anchor_y='center')
self.start_label.draw()
self.level_label.draw()
self.level_indicator_label.draw()
class Window(pyglet.window.Window):
def __init__(self, *args, **kwargs):
DIM = (800, 450) # DIMENSIONS
super(Window, self).__init__(width=DIM[0], height=DIM[1],
*args, **kwargs)
# ------------------ MVC --------------------#
the_window = self
self.model = Model(DIM)
self.view = View(the_window, self.model)
self.controller = Controller(self.model)
# ------------------ CLOCK --------------------#
fps = 30.0
pyglet.clock.schedule_interval(self.update, 1.0/fps)
#pyglet.clock.set_fps_limit(fps)
self.score_label = pyglet.text.Label(str(p1_score)+':'+str(p2_score), font_name='San Serif', font_size=36, x=self.width//2, y=self.height//2, anchor_x='center', anchor_y='center')
def on_key_release(self, symbol, modifiers):
self.controller.on_key_release(symbol, modifiers)
def on_key_press(self, symbol, modifiers):
self.controller.on_key_press(symbol, modifiers)
def update(self, *args, **kwargs):
# XXX make more efficient (save last position, draw black square
# over that and the new square, don't redraw _entire_ frame.)
self.clear()
self.controller.update()
if (self.model.menu == 1):
self.view.redraw_game()
self.score_label.draw()
else:
self.view.redraw_menu()
self.score_label.text = str(p1_score)+':'+str(p2_score)
window = Window()
# speech recognition thread
# -------------------------------------#
# start a thread to listen to speech
speech_thread = threading.Thread(target=listen_to_speech, args=())
speech_thread.start()
# -------------------------------------#
# pitch & volume detection
# -------------------------------------#
# start a thread to detect pitch and volume
microphone_thread = threading.Thread(target=sense_microphone, args=())
microphone_thread.start()
# -------------------------------------#
if debug: print("init window...")
if debug: print("done! init app...")
pyglet.app.run()