picadae calibration programs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AudioDevice.py 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import pprint,queue
  2. import sounddevice as sd
  3. import soundfile as sf
  4. import numpy as np
  5. from result import Result
  6. class AudioDevice(object):
  7. def __init__(self):
  8. self.inputPortIdx = None
  9. self.outputPortIdx = None
  10. self.recordFl = False
  11. self.sineFl = False
  12. self.inStream = None
  13. self.queue = queue.Queue()
  14. self.bufL = []
  15. self.bufIdx = -1
  16. self.srate = 0
  17. self.ch_cnt = 1
  18. def setup( self, **kwargs ):
  19. res = Result()
  20. if kwargs is None:
  21. return res
  22. if 'inPortLabel' in kwargs:
  23. res += self.select_port( True, kwargs['inPortLabel'] )
  24. if 'outPortLabel' in kwargs:
  25. res += self.select_port( False, kwargs['outPortLabel'] )
  26. return res
  27. def get_port_label( self, inFl ):
  28. portL = self.get_port_list(inFl)
  29. portIdx = self.inputPortIdx if inFl else self.outputPortIdx
  30. portLabel = None
  31. if portL and (portIdx is not None):
  32. for port in portL:
  33. if portIdx == port['index']:
  34. portLabel = port['label']
  35. break
  36. return portLabel
  37. def get_state( self ):
  38. d= {
  39. 'recordFl': self.inStream is not None and self.inStream.active,
  40. 'sineFl': self.sineFl,
  41. 'inPortL': [ d['label'] for d in self.get_port_list(True) ],
  42. 'outPortL': [ d['label'] for d in self.get_port_list(False) ],
  43. 'inPortLabel': self.get_port_label(True),
  44. 'outPortLabel': self.get_port_label(False)
  45. }
  46. return d
  47. def get_port_list( self, inFl ):
  48. devLabelL = str(sd.query_devices()).split("\n")
  49. portL = []
  50. for i,dev in enumerate(sd.query_devices()):
  51. isInputFl = dev['max_input_channels'] > 0
  52. isOutputFl = dev['max_output_channels'] > 0
  53. if (inFl and isInputFl) or (not inFl and isOutputFl):
  54. portL.append({ 'chN':dev['max_input_channels'], 'label':devLabelL[i].strip(), 'index':i } )
  55. return portL
  56. def get_in_port_list( self ):
  57. return get_port_list( True )
  58. def get_out_port_list( self ):
  59. return get_port_list( False )
  60. def select_port( self, inFl, portLabel ):
  61. res = Result()
  62. if inFl and self.inStream is not None and self.inStream.active:
  63. return res.set_error("A new port may not be selected while it is active." % (portLabel))
  64. devLabelL = str(sd.query_devices()).split("\n")
  65. foundFl = False
  66. if portLabel is None and len(devLabelL)>0:
  67. portLabel = devLabelL[0]
  68. portLabel = portLabel.strip()
  69. N = len(portLabel)
  70. # for each device
  71. for i,label in enumerate(devLabelL):
  72. label = label.strip()
  73. # if the first N char's of this label match the requested port label
  74. if len(label)>=N and label[0:N] == portLabel:
  75. if inFl:
  76. self.inputPortIdx = i
  77. else:
  78. self.outputPortIdx = i
  79. foundFl = True
  80. if inFl:
  81. dev = sd.query_devices()[i]
  82. self.srate = dev['default_samplerate']
  83. if self.inStream:
  84. self.inStream.close()
  85. self.inStream = sd.InputStream(samplerate=self.srate, device=self.inputPortIdx, channels=self.ch_cnt, callback=self._record_callback, dtype=np.dtype('float32'))
  86. break
  87. if not foundFl:
  88. res.set_error("Unable to locate the audio port named: '%s'." % (portLabel))
  89. return res
  90. def record_enable( self, enableFl ):
  91. # if the input stream has not already been configured
  92. if enableFl and self.inStream is None:
  93. return Result(None,'Recording cannot start because a recording input port has not been selected.')
  94. if not enableFl and self.inStream is None:
  95. return Result()
  96. # if the input stream already matches the 'enableFl'.
  97. if enableFl == self.inStream.active:
  98. return Result()
  99. # if we are starting recording
  100. if enableFl:
  101. try:
  102. self.bufL = []
  103. self.inStream.start()
  104. except Exception as ex:
  105. return Result(None,'The recording input stream could not be started. Reason: %s' % str(ex))
  106. else:
  107. self.inStream.stop()
  108. return Result()
  109. def play_buffer( self ):
  110. if not self.playFl:
  111. self.playFl = True
  112. def play_end( self ):
  113. if self.playFl:
  114. self.playFl = False
  115. def write_buffer( self, fn=None ):
  116. if fn is None:
  117. fn = "temp.wav"
  118. with sf.SoundFile(fn,'w',samplerate=int(self.srate), channels=int(self.ch_cnt)) as f:
  119. for i,buf in enumerate(self.bufL):
  120. N = buf.shape[0] if i<len(self.bufL)-1 else self.bufIdx
  121. f.write(buf[0:N,])
  122. return Result()
  123. def buffer_sample_count( self ):
  124. smpN = 0
  125. for i in range(len(self.bufL)):
  126. smpN += self.bufIdx if i == len(self.bufL)-1 else self.bufL[i].shape[0]
  127. return Result(smpN)
  128. def buffer_sample_ms( self ):
  129. r = self.buffer_sample_count()
  130. if r:
  131. r.value = int(r.value * 1000.0 / self.srate)
  132. return r
  133. def linear_buffer( self ):
  134. smpN = self.buffer_sample_count()
  135. bV = np.zeros( (smpN,self.ch_cnt) )
  136. bi = 0
  137. for i in range(len(self.bufL)):
  138. bufSmpN = self.bufIdx if i == len(self.bufL)-1 else self.bufL[i].shape[0]
  139. bV[ bi:bi+bufSmpN, ] = self.bufL[i][0:bufSmpN]
  140. bi += bufSmpN
  141. return Result(bV)
  142. def _record_callback(self, v, frames, time, status):
  143. """This is called (from a separate thread) for each audio block."""
  144. # send audio to the app
  145. self.queue.put(v.copy())
  146. def tick( self, ms ):
  147. try:
  148. while True:
  149. # get audio from the incoming audio thread
  150. v = self.queue.get_nowait()
  151. #print(type(v),len(v),np.result_type(v))
  152. self._update_buffer(v)
  153. except queue.Empty: # queue was empty
  154. pass
  155. return Result()
  156. def _update_buffer(self,v ):
  157. # get the length of the last buffer in bufL
  158. bufN = 0 if len(self.bufL)==0 else self.bufL[-1].shape[0] - self.bufIdx
  159. # get the length of the incoming sample vector
  160. vN = v.shape[0]
  161. # if there are more incoming samples than space in the buffer
  162. if vN > bufN:
  163. # store as much of the incoming vector as possible
  164. if len(self.bufL) > 0:
  165. self.bufL[-1][self.bufIdx:,:] = v[0:bufN,:]
  166. vi = bufN # increment the starting position of the src vector
  167. vN -= bufN # decrement the count of samples that needs to be stored
  168. # add an empty buffer to bufL[]
  169. N = int(self.srate * 60) # add a one minute buffer to bufL
  170. self.bufL.append( np.zeros( (N, self.ch_cnt), dtype=np.dtype('float32') ) )
  171. self.bufIdx = 0
  172. self.bufL[-1][self.bufIdx:vN,:] = v[vi:,]
  173. self.bufIdx += vN
  174. else:
  175. self.bufL[-1][self.bufIdx:self.bufIdx+vN,:] = v
  176. self.bufIdx += vN
  177. if __name__ == "__main__":
  178. ad = AudioDevice()
  179. ad.get_port_list(True)