]> git.neil.brown.name Git - freerunner.git/blob - lib/play.py
Lots of random updates
[freerunner.git] / lib / play.py
1
2 # Python library to play sounds using ALSA from inside a
3 # glib event loop
4 #
5 # We use the 'non-blocking' output routines, write until
6 # we can write no more, or we hit the stop-latency limit.
7 # Then set a timer to try again when we estimate the buffer
8 # will be 3/4 full.
9 #
10 # playing can be interrupted at any time - we allow the buffers
11 # to flush.
12 #
13 # Currently only wav files
14
15 import gobject, alsaaudio, time, struct, sys
16
17 class Play():
18     def __init__(self, file, latency_ms = 1000, done = None):
19         # Arrange to play 'file' - which is the name of a .wav file
20         self.pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK)
21         self.latency_ms = latency_ms
22         self.finished = False
23         self.done = done
24         self.setfile(file)
25
26     def setfile(self, file):
27         # A wav file starts:
28         #   0-3  "RIFF"
29         #   4-7  Bytes in rest of file.
30         #   8-11 "WAVE"
31         #  12-15 "fmt "
32         #  16-19 bytes of format
33         #  20-21 ==1  Microsoft PCM
34         #  22-23      channels
35         #  24-27  freq
36         #  28-31  byte rate
37         #  32-33  bytes per frame
38         #  34-35  bits per sample
39         #  36-39 "data"
40         #  40-43 number of bytes of data
41         #  44... actual samples
42         self.f = open(file)
43         header = self.f.read(44)
44         if len(header) != 44:
45             raise IOError
46         riff, b1, wave, fmt, b2, format, chan, rate, br, bf, bs, data, b3 = \
47               struct.unpack("4si4s 4sihhiihh 4si", header)
48
49         if riff != "RIFF" or wave != "WAVE" or fmt != "fmt " or data != "data":
50             raise ValueError
51         if format != 1 or bs != 16:
52             raise ValueError
53         else:
54             self.pcm.setformat(alsaaudio.PCM_FORMAT_S16_LE)
55
56         if chan < 1 or chan > 4:
57             raise ValueError
58         else:
59             self.pcm.setchannels(chan)
60
61         self.pcm.setrate(rate)
62         self.bytes_per_second = rate * 2 * chan
63
64         # choose the period to be 1/8 of the latency,
65         # probably need to set an upper bound
66         frames_per_latency = rate * self.latency_ms / 1000
67         self.bytes_per_latency = frames_per_latency * chan * 2;
68         #self.bytes_per_period = (frames_per_latency / 8) * chan * 2
69         self.bytes_per_period = 320
70
71         self.data = None
72         
73         self.pcm.setperiodsize(self.bytes_per_period / chan / 2)
74         #print "bytes_per_period", self.bytes_per_period
75         #print "period size", self.bytes_per_period / chan / 2
76
77         self.start = time.time()
78         self.loaded = 0
79         self.finished = False
80         self.playsome()
81
82     def playsome(self):
83         if self.finished:
84             return
85         now = time.time()
86
87         self.now = now
88         pos = int( (time.time() - self.start) * self.bytes_per_second)
89         buffered = self.loaded - pos
90         cnt = 0
91         data = self.data
92         while buffered < self.bytes_per_latency + self.bytes_per_period:
93             if not data:
94                 data = self.f.read(self.bytes_per_period)
95             if not data:
96                 self.finished = True
97                 self.data = None
98                 if self.done:
99                     self.done()
100                 return
101             if not self.pcm.write(data):
102                 break
103             data = None
104
105             cnt += 1
106             buffered += self.bytes_per_period
107
108         self.data = data
109         self.loaded = buffered + pos
110
111         pos = int( (time.time() - self.start) * self.bytes_per_second)
112         buffered = self.loaded - pos
113         delay = int(buffered /4 * 1000 / self.bytes_per_second)
114         print "wrote", cnt, "delay" ,delay
115         if delay < 20:
116             self.start += float( 20 - delay) / 1000
117             delay = 10
118         gobject.timeout_add(delay, self.playsome, priority = gobject.PRIORITY_HIGH)
119
120
121 if __name__ == "__main__":
122     # test code.
123     # play given wav file in a loop for 20 seconds, then stop
124     p = None
125     def done():
126         p.setfile(sys.argv[1])
127     p = Play(sys.argv[1], 400, done)
128     c = gobject.main_context_default()
129     def abort():
130         p.finished = True
131     gobject.timeout_add(20000, abort)
132     while not p.finished:
133         c.iteration()