drums.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import pyaudio
  2. import socket
  3. import optparse
  4. import tarfile
  5. import wave
  6. import cStringIO as StringIO
  7. import array
  8. import time
  9. from packet import Packet, CMD, stoi, OBLIGATE_POLYPHONE
  10. parser = optparse.OptionParser()
  11. parser.add_option('-t', '--test', dest='test', action='store_true', help='As a test, play all samples then exit')
  12. parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Be verbose')
  13. parser.add_option('-V', '--volume', dest='volume', type='float', default=1.0, help='Set the volume factor (nominally [0.0, 1.0], but >1.0 can be used to amplify with possible distortion)')
  14. parser.add_option('-r', '--rate', dest='rate', type='int', default=44100, help='Audio sample rate for output and of input files')
  15. parser.add_option('-u', '--uid', dest='uid', default='', help='User identifier of this client')
  16. parser.add_option('-p', '--port', dest='port', default=13676, type='int', help='UDP port to listen on')
  17. parser.add_option('--repeat', dest='repeat', action='store_true', help='If a note plays longer than a sample length, keep playing the sample')
  18. parser.add_option('--cut', dest='cut', action='store_true', help='If a note ends within a sample, stop playing that sample immediately')
  19. options, args = parser.parse_args()
  20. MAX = 0x7fffffff
  21. MIN = -0x80000000
  22. IDENT = 'DRUM'
  23. if not args:
  24. print 'Need at least one drumpack (.tar.bz2) as an argument!'
  25. parser.print_usage()
  26. exit(1)
  27. DRUMS = {}
  28. for fname in args:
  29. print 'Reading', fname, '...'
  30. tf = tarfile.open(fname, 'r')
  31. names = tf.getnames()
  32. for nm in names:
  33. if not (nm.endswith('.wav') or nm.endswith('.raw')) or len(nm) < 5:
  34. continue
  35. frq = int(nm[:-4])
  36. if options.verbose:
  37. print '\tLoading frq', frq, '...'
  38. fo = tf.extractfile(nm)
  39. if nm.endswith('.wav'):
  40. wf = wave.open(fo)
  41. if wf.getnchannels() != 1:
  42. print '\t\tWARNING: Channel count wrong: got', wf.getnchannels(), 'expecting 1'
  43. if wf.getsampwidth() != 4:
  44. print '\t\tWARNING: Sample width wrong: got', wf.getsampwidth(), 'expecting 4'
  45. if wf.getframerate() != options.rate:
  46. print '\t\tWARNING: Rate wrong: got', wf.getframerate(), 'expecting', options.rate, '(maybe try setting -r?)'
  47. frames = wf.getnframes()
  48. data = ''
  49. while len(data) < wf.getsampwidth() * frames:
  50. data += wf.readframes(frames - len(data) / wf.getsampwidth())
  51. elif nm.endswith('.raw'):
  52. data = fo.read()
  53. frames = len(data) / 4
  54. if options.verbose:
  55. print '\t\tData:', frames, 'samples,', len(data), 'bytes'
  56. if frq in DRUMS:
  57. print '\t\tWARNING: frequency', frq, 'already in map, overwriting...'
  58. DRUMS[frq] = data
  59. if options.verbose:
  60. print len(DRUMS), 'sounds loaded'
  61. PLAYING = set()
  62. class SampleReader(object):
  63. def __init__(self, buf, total, amp):
  64. self.buf = buf
  65. self.total = total
  66. self.cur = 0
  67. self.amp = amp
  68. def read(self, bytes):
  69. if self.cur >= self.total:
  70. return ''
  71. res = ''
  72. while self.cur < self.total and len(res) < bytes:
  73. data = self.buf[self.cur % len(self.buf):self.cur % len(self.buf) + bytes - len(res)]
  74. self.cur += len(data)
  75. res += data
  76. arr = array.array('i')
  77. arr.fromstring(res)
  78. for i in range(len(arr)):
  79. arr[i] = int(arr[i] * self.amp)
  80. return arr.tostring()
  81. def __repr__(self):
  82. return '<SR (%d) @%d / %d A:%f>'%(len(self.buf), self.cur, self.total, self.amp)
  83. def gen_data(data, frames, tm, status):
  84. fdata = array.array('l', [0] * frames)
  85. torem = set()
  86. for src in set(PLAYING):
  87. buf = src.read(frames * 4)
  88. if not buf:
  89. torem.add(src)
  90. continue
  91. samps = array.array('i')
  92. samps.fromstring(buf)
  93. if len(samps) < frames:
  94. samps.extend([0] * (frames - len(samps)))
  95. for i in range(frames):
  96. fdata[i] += samps[i]
  97. for src in torem:
  98. PLAYING.discard(src)
  99. for i in range(frames):
  100. fdata[i] = max(MIN, min(MAX, fdata[i]))
  101. fdata = array.array('i', fdata)
  102. return (fdata.tostring(), pyaudio.paContinue)
  103. pa = pyaudio.PyAudio()
  104. stream = pa.open(rate=options.rate, channels=1, format=pyaudio.paInt32, output=True, frames_per_buffer=64, stream_callback=gen_data)
  105. if options.test:
  106. for frq in sorted(DRUMS.keys()):
  107. print 'Current playing:', PLAYING
  108. print 'Playing:', frq
  109. data = DRUMS[frq]
  110. PLAYING.add(SampleReader(data, len(data), 1.0))
  111. time.sleep(len(data) / (4.0 * options.rate))
  112. print 'Done'
  113. exit()
  114. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  115. sock.bind(('', options.port))
  116. #signal.signal(signal.SIGALRM, sigalrm)
  117. while True:
  118. data = ''
  119. while not data:
  120. try:
  121. data, cli = sock.recvfrom(4096)
  122. except socket.error:
  123. pass
  124. pkt = Packet.FromStr(data)
  125. print 'From', cli, 'command', pkt.cmd
  126. if pkt.cmd == CMD.KA:
  127. pass
  128. elif pkt.cmd == CMD.PING:
  129. sock.sendto(data, cli)
  130. elif pkt.cmd == CMD.QUIT:
  131. break
  132. elif pkt.cmd == CMD.PLAY:
  133. frq = pkt.data[2]
  134. if frq not in DRUMS:
  135. print 'WARNING: No such instrument', frq, ', ignoring...'
  136. continue
  137. rdata = DRUMS[frq]
  138. rframes = len(rdata) / 4
  139. dur = pkt.data[0]+pkt.data[1]/1000000.0
  140. dframes = int(dur * options.rate)
  141. if not options.repeat:
  142. dframes = max(dframes, rframes)
  143. if not options.cut:
  144. dframes = rframes * ((dframes + rframes - 1) / rframes)
  145. amp = max(min(options.volume * pkt.as_float(3), 1.0), 0.0)
  146. PLAYING.add(SampleReader(rdata, dframes * 4, amp))
  147. #signal.setitimer(signal.ITIMER_REAL, dur)
  148. elif pkt.cmd == CMD.CAPS:
  149. data = [0] * 8
  150. data[0] = OBLIGATE_POLYPHONE
  151. data[1] = stoi(IDENT)
  152. for i in xrange(len(options.uid)/4 + 1):
  153. data[i+2] = stoi(options.uid[4*i:4*(i+1)])
  154. sock.sendto(str(Packet(CMD.CAPS, *data)), cli)
  155. # elif pkt.cmd == CMD.PCM:
  156. # fdata = data[4:]
  157. # fdata = struct.pack('16i', *[i<<16 for i in struct.unpack('16h', fdata)])
  158. # QUEUED_PCM += fdata
  159. # print 'Now', len(QUEUED_PCM) / 4.0, 'frames queued'
  160. else:
  161. print 'Unknown cmd', pkt.cmd