audioHandler.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import pyaudio
  2. import wave
  3. import os
  4. import numpy as np
  5. import struct
  6. class Listener:
  7. def __init__(self, dataTime = 1/20, agcTime = 10, input = False):
  8. self._dataTime = dataTime
  9. self._agcTime = agcTime
  10. self._input = input
  11. self.buffersize = 512
  12. self.p = pyaudio.PyAudio()
  13. self.left = self.right = self.fft = []
  14. self._agcMaxima = [0]
  15. self._agcIndex = 0
  16. self._agcLen = 0
  17. self._beatCb = None
  18. self._sampleRate = 0
  19. self._hasNewData = False
  20. def start(self, dev = None):
  21. self._device = dev or self.getDefaultOutputDeviceInfo()
  22. if self._device == None:
  23. print("no device found")
  24. return
  25. print("device name: {} channels: {} defaultSampleRate: {}".format(self._device["name"], self._device["channels"], self._device["defaultSampleRate"]))
  26. if self._sampleRate != self._device["defaultSampleRate"]:
  27. self._sampleRate = self._device["defaultSampleRate"]
  28. self.buffersize = int(self._sampleRate * self._dataTime)
  29. self.fft = self.right = self.left = np.ndarray((self.buffersize))
  30. self._agcLen = int(1 / self._dataTime * self._agcTime)
  31. self._agcMaxima = np.ndarray((self._agcLen))
  32. self._agcMaxima.fill(2**15 * 0.1)
  33. self._agcIndex = 0
  34. self._lastBeatTime = 0
  35. self.meanAmp = 2**15 * 0.1
  36. try:
  37. self._stream = self.openStream(self._device)
  38. except OSError:
  39. self._stream = None
  40. if not self._stream:
  41. print("stream open failed")
  42. return
  43. self._stream.start_stream()
  44. return self._stream.is_active()
  45. def stop(self):
  46. if not self._stream:# or not self._stream.is_active():
  47. return False
  48. self._stream.stop_stream()
  49. return True
  50. def setBeatCb(self, cb):
  51. self._beatCb = cb
  52. def getDefaultOutputDeviceInfo(self):
  53. #Set default to first in list or ask Windows
  54. try:
  55. self.p.terminate()
  56. self.p.__init__()
  57. if self._input:
  58. info = self.p.get_default_input_device_info()
  59. else:
  60. info = self.p.get_default_output_device_info()
  61. except IOError:
  62. info = None
  63. #Handle no devices available
  64. if info == None:
  65. print ("No device available.")
  66. return None
  67. if (self.p.get_host_api_info_by_index(info["hostApi"])["name"]).find("WASAPI") == -1:
  68. for i in range(0, self.p.get_device_count()):
  69. x = self.p.get_device_info_by_index(i)
  70. is_wasapi = (self.p.get_host_api_info_by_index(x["hostApi"])["name"]).find("WASAPI") != -1
  71. if x["name"].find(info["name"]) >= 0 and is_wasapi:
  72. info = x
  73. break
  74. #Handle no devices available
  75. if info == None:
  76. print ("Device doesn't support WASAPI")
  77. return None
  78. info["channels"] = info["maxInputChannels"] if (info["maxOutputChannels"] < info["maxInputChannels"]) else info["maxOutputChannels"]
  79. return info
  80. def openStream(self, dev):
  81. is_input = dev["maxInputChannels"] > 0
  82. is_wasapi = (self.p.get_host_api_info_by_index(dev["hostApi"])["name"]).find("WASAPI") != -1
  83. #print("is input: {} is wasapi: {}".format(is_input, is_wasapi))
  84. if not is_input and not is_wasapi:
  85. print ("Selection is output and does not support loopback mode.")
  86. return None
  87. if is_wasapi:
  88. stream = self.p.open(
  89. format = pyaudio.paInt16,
  90. channels = (dev["channels"] if dev["channels"] < 2 else 2),
  91. rate = int(self._sampleRate),
  92. input = True,
  93. frames_per_buffer = self.buffersize,
  94. input_device_index = dev["index"],
  95. stream_callback=self.streamCallback,
  96. as_loopback = False if is_input else is_wasapi)
  97. else:
  98. stream = self.p.open(
  99. format = pyaudio.paInt16,
  100. channels = (dev["channels"] if dev["channels"] < 2 else 2),
  101. rate = int(self._sampleRate),
  102. input = True,
  103. frames_per_buffer = self.buffersize,
  104. input_device_index = dev["index"],
  105. stream_callback=self.streamCallback)
  106. return stream
  107. def closeStream(self):
  108. if not self._stream:
  109. return False
  110. self._stream.close()
  111. return True
  112. def streamCallback(self, buf, frame_count, time_info, flag):
  113. self._buf = buf
  114. arr = np.array(struct.unpack("%dh" % (len(buf)/2), buf))
  115. mx = arr.max()
  116. self._agcIndex += 1
  117. if self._agcIndex >= self._agcLen:
  118. self._agcIndex = 0
  119. self._agcMaxima[self._agcIndex] = mx
  120. self.meanAmp = np.mean(np.absolute(self._agcMaxima))
  121. if self.meanAmp > 2**15 * 0.02:
  122. amp = 1 / self.meanAmp
  123. else:
  124. amp = 1 / (2**15 * 0.02)
  125. if self._device["channels"] >= 2:
  126. self.left, self.right = arr[::2] * amp, arr[1::2] * amp
  127. self.fft = self.fftCalc((self.left+self.right)/2)
  128. else:
  129. self.left = self.right = arr * amp
  130. self.fft = self.fftCalc(self.left)
  131. self._hasNewData = True
  132. self.agcFFT = np.fft.rfft(self._agcMaxima, self.beatnFFT) / self.beatnFFT
  133. if self._beatCb and mx * (time_info["current_time"] - self._lastBeatTime) > m * 0.5:
  134. self._lastBeatTime = time_info["current_time"]
  135. self._beatCb(self.fft)
  136. return (None, pyaudio.paContinue)
  137. def hasNewData(self):
  138. if not self._hasNewData:
  139. return False
  140. self._hasNewData = False
  141. return True
  142. def getSampleRate(self):
  143. return int(self._sampleRate)
  144. def getAgc(self):
  145. return self.meanAmp / 2**15
  146. def getVolume(self):
  147. if self._agcMaxima.sum() == 0:
  148. return 0
  149. return self._agcMaxima[self._agcIndex] / self.meanAmp
  150. def isActive(self):
  151. if not self._stream:
  152. return False
  153. return self._stream.is_active()
  154. def fftSetLimits(self, nFFT, fMin, fMax):
  155. self.nFFT = nFFT
  156. self.fftMin = int(fMin / self._sampleRate * nFFT)
  157. self.fftMax = int(fMax / self._sampleRate * nFFT)
  158. print("nFFT: {} \tfftMin: {} \tfftMax: {}".format(self.nFFT, self.fftMin, self.fftMax))
  159. def agcFftSetLimits(self, fMin, fMax):
  160. self.beatnFFT = self._agcLen
  161. self.beatFftMin = int(fMin * self._dataTime * self.beatnFFT)
  162. self.beatFftMax = int(fMax * self._dataTime * self.beatnFFT)
  163. print("beat nFFT: {} \tfftMin: {} \tfftMax: {}".format(self.beatnFFT, self.beatFftMin, self.beatFftMax))
  164. def fftCalc(self, data):
  165. return abs(np.fft.rfft(data, self.nFFT)[self.fftMin:self.fftMax]) / self.nFFT
  166. def fftGroup(self, fft, limits):
  167. groups = []
  168. for freqs in zip(limits, limits[1:]):
  169. a = int(freqs[0] / self._sampleRate * self.nFFT)
  170. b = int(freqs[1] / self._sampleRate * self.nFFT)
  171. #groups.append(sum(fft[a:b]) / (b-a) if (b-a) > 0 else 0)
  172. if b != a:
  173. groups.append(max(fft[a:b]))
  174. else:
  175. groups.append(fft[a])
  176. return groups