forked from kragen/pytebeat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pytebeat.py
executable file
·129 lines (108 loc) · 3.87 KB
/
pytebeat.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
#!/usr/bin/python
# -*- coding: utf-8 -*-
# next items:
# - vertical waveform display
# - integer object has no attribute astype
# - improve parse errors
import sys, wave, os, time, subprocess, pygame, shuntparse, sdltextfield
from pygame.locals import *
try:
from Numeric import array, arange, UInt8
except ImportError:
from numpy import array, arange, uint8
UInt8 = uint8
rate = 8000
current_formula = None
t = 0
interval = 33
last_time = start = time.time()
def eval_formula(error, formula):
global current_formula, t, last_time, start
needed = int(min(rate * (time.time() - start + 1.5*interval/1000.0) - t,
3 * interval / 1000.0 * rate))
granularity = 1024
if needed % granularity != 0:
needed += granularity - needed % granularity
print time.time() - last_time, needed
try:
new_formula = shuntparse.parse(shuntparse.tokenize(formula.text))
new_formula.eval({'t': array(0)})
current_formula = new_formula
except:
_, exc, _ = sys.exc_info()
error.text=repr(exc)
else:
error.text=''
try:
rv = current_formula.eval({'t': arange(t, t+needed)}).astype(UInt8).tostring()
t += needed
return rv
except:
if current_formula:
error.text=str(sys.exc_info()[1])
return ''
def run_mainloop(error, formula, outfd, screen):
global last_time, start
event = pygame.event.poll()
if event.type in [pygame.QUIT, pygame.MOUSEBUTTONDOWN]:
# For some reason, normal ways of exiting aren’t working.
os.kill(os.getpid(), 9)
elif event.type in [pygame.KEYDOWN, pygame.KEYUP]:
if (event.key == K_ESCAPE):
os.kill(os.getpid(), 9)
else:
formula.handle_keyevent(event)
elif event.type == pygame.NOEVENT:
formula.poll()
output = eval_formula(error, formula)
formula.draw(screen)
error.draw(screen)
outstart = time.time()
outfd.write(output)
outfd.flush()
last_time = time.time()
# hacky kludge to keep us from getting too far behind if for some
# reason the audio output isn’t draining fast enough
if last_time - outstart > interval * 0.1:
print "buffer overrun of %f" % (last_time - outstart)
start += (last_time - outstart) / 2
if len(output) > 1:
pygame.draw.rect(screen, (0,0,0), (0, 0, screen.get_width(), 256))
pygame.draw.lines(screen, (255, 255, 255), False,
list(enumerate(map(ord, output[:screen.get_width()]))))
pygame.display.flip()
pygame.time.delay(interval)
class Tee(object):
def __init__(self, a, b):
self.contents = (a, b)
def write(self, data):
for item in self.contents:
item.write(data)
def flush(self):
for item in self.contents:
item.flush()
def open_new_outfile():
i = 1
while True:
filename = 'bytebeat_%d.raw' % i
if not os.path.exists(filename):
return open(filename, 'w')
i += 1
def make_window():
outfd = open('/dev/dsp', 'w')
outfile2 = open_new_outfile()
outfd = Tee(outfd, outfile2)
pygame.init()
default_font = 'NovaMono.ttf'
font = pygame.font.Font(default_font, 24) if os.path.exists(default_font) else None
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.mouse.set_visible(False)
formula = sdltextfield.TextField((10, 266),
foreground=(0,0,255),
font=font,
text = 't >> 5 | t >> 4')
error = sdltextfield.TextField((10, 400), foreground=(255,0,0), focused=False, font=font)
while True:
run_mainloop(error, formula, outfd, screen)
if __name__ == '__main__':
make_window()