mpd_spectrum_page.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. # -*- coding: utf-8 -*-
  2. import time
  3. import pygame
  4. from numpy import fromstring,ceil,abs,log10,isnan,isinf,int16,sqrt,mean
  5. from numpy import fft as Fft
  6. import gobject
  7. from beeprint import pp
  8. ## local UI import
  9. from UI.constants import Width,Height,ICON_TYPES
  10. from UI.page import Page,PageSelector
  11. from UI.label import Label
  12. from UI.fonts import fonts
  13. from UI.util_funcs import midRect
  14. from UI.keys_def import CurKeys
  15. from UI.icon_item import IconItem
  16. from UI.icon_pool import MyIconPool
  17. from Queue import Queue, Empty
  18. from threading import Thread
  19. from list_item import ListItem
  20. import myvars
  21. class PIFI(object):
  22. _MPD_FIFO = "/tmp/mpd.fifo"
  23. _SAMPLE_SIZE = 256
  24. _SAMPLING_RATE = 44100
  25. _FIRST_SELECTED_BIN = 5
  26. _NUMBER_OF_SELECTED_BINS = 10
  27. _SCALE_WIDTH = Height/2 - 20
  28. count = 0
  29. average = 0
  30. rmscount=0
  31. rmsaverage=0
  32. def __init__(self):
  33. self.sampleSize = self._SAMPLE_SIZE
  34. self.samplingRate = self._SAMPLING_RATE
  35. self.firstSelectedBin = self._FIRST_SELECTED_BIN
  36. self.numberOfSelectedBins = self._NUMBER_OF_SELECTED_BINS
  37. # Initialization : frequency bins
  38. freq = Fft.fftfreq(self.sampleSize) * self.samplingRate
  39. freqR = freq[:self.sampleSize/2]
  40. self.bins = freqR[self.firstSelectedBin:self.firstSelectedBin+self.numberOfSelectedBins]
  41. self.resetSmoothing()
  42. def resetSmoothing(self):
  43. self.count = 0
  44. self.average = 0
  45. self.rmscount = 0
  46. self.rmsaverage = 0
  47. def rms_smoothOut(self, x):
  48. self.rmscount += 1
  49. self.rmsaverage = (self.rmsaverage*self.rmscount + x) / (self.rmscount+1)
  50. return self.rmsaverage
  51. def smoothOut(self, x):
  52. self.count += 1
  53. self.average = (self.average*self.count + x) / (self.count+1)
  54. return self.average
  55. def scaleList(self, _list):
  56. for i,x in enumerate(_list):
  57. if isnan(x) or isinf(x):
  58. _list[i] = 0
  59. # Compute a simple just-above 'moving average' of maximums
  60. maximum = 1.1*self.smoothOut(max( _list ))
  61. if maximum == 0:
  62. scaleFactor = 0.0
  63. else:
  64. scaleFactor = self._SCALE_WIDTH/float(maximum)
  65. # Compute the scaled list of values
  66. scaledList = [int(x*scaleFactor) for x in _list ]
  67. return scaledList
  68. def computeSpectrum(self, fifoFile):
  69. # Read PCM samples from fifo
  70. rawSamples = fifoFile.read(self.sampleSize) # will return empty lines (non-blocking)
  71. if len(rawSamples) == 0:
  72. print("computeSpectrum read zero")
  73. return [],[]
  74. else:
  75. pass
  76. ## print("computeSpectrum %d " % len(rawSamples))
  77. pcm = fromstring(rawSamples, dtype=int16)
  78. # Normalize [-1; +1]
  79. pcm = pcm / (2.**15)
  80. # Compute RMS directly from signal
  81. rms = sqrt(mean(pcm**2))
  82. # Compute a simple 'moving maximum'
  83. maximum = 2*self.rms_smoothOut(rms)
  84. if maximum == 0:
  85. scaleFactor = 0.0
  86. else:
  87. scaleFactor = self._SCALE_WIDTH/float(maximum)
  88. final_rms = int(rms*scaleFactor)
  89. # Compute FFT
  90. N = pcm.size
  91. fft = Fft.fft(pcm)
  92. uniquePts = ceil((N+1)/2.0)
  93. fft = fft[0:int(uniquePts)]
  94. # Compute amplitude spectrum
  95. amplitudeSpectrum = abs(fft) / float(N)
  96. # Compute power spectrum
  97. p = amplitudeSpectrum**2
  98. # Multiply by two to keep same energy
  99. # See explanation:
  100. # https://web.archive.org/web/20120615002031/http://www.mathworks.com/support/tech-notes/1700/1702.html
  101. if N % 2 > 0:
  102. # odd number of points
  103. # odd nfft excludes Nyquist point
  104. p[1:len(p)] = p[1:len(p)] * 2
  105. else:
  106. # even number of points
  107. p[1:len(p) -1] = p[1:len(p) - 1] * 2
  108. # Power in logarithmic scale (dB)
  109. logPower = 10*log10(p)
  110. # Compute RMS from power
  111. #rms = numpy.sqrt(numpy.sum(p))
  112. #print "RMS(power):", rms
  113. # Select a significant range in the spectrum
  114. spectrum = logPower[self.firstSelectedBin:self.firstSelectedBin+self.numberOfSelectedBins]
  115. # Scale the spectrum
  116. scaledSpectrum = self.scaleList(spectrum)
  117. scaledSpectrum.append( final_rms)
  118. return scaledSpectrum
  119. class MPDSpectrumPage(Page):
  120. _Icons = {}
  121. _Selector=None
  122. _FootMsg = ["Nav","","","Back",""]
  123. _MyList = []
  124. _ListFont = fonts["veramono12"]
  125. _PIFI = None
  126. _FIFO = None
  127. _Color = pygame.Color(126,206,244)
  128. _GobjectIntervalId = -1
  129. _Queue = None
  130. _KeepReading = True
  131. _BGpng = None
  132. _BGwidth = 320
  133. _BGheight = 200
  134. _SheepHead = None
  135. _SheepHeadW = 69
  136. _SheepHeadH = 66
  137. _SheepBody = None
  138. _SheepBodyW = 105
  139. _SheepBodyH = 81
  140. _freq_count = 0
  141. _head_dir = 0
  142. _Neighbor = None
  143. def __init__(self):
  144. Page.__init__(self)
  145. self._Icons = {}
  146. self._CanvasHWND = None
  147. self._MyList = []
  148. self._PIFI = PIFI()
  149. def Init(self):
  150. self._PosX = self._Index * self._Screen._Width
  151. self._Width = self._Screen._Width
  152. self._Height = self._Screen._Height
  153. self._CanvasHWND = self._Screen._CanvasHWND
  154. """
  155. self._BGpng = IconItem()
  156. self._BGpng._ImgSurf = MyIconPool._Icons["sheep_bg"]
  157. self._BGpng._MyType = ICON_TYPES["STAT"]
  158. self._BGpng._Parent = self
  159. self._BGpng.Adjust(0,0,self._BGwidth,self._BGheight,0)
  160. self._SheepHead = IconItem()
  161. self._SheepHead._ImgSurf = MyIconPool._Icons["sheep_head"]
  162. self._SheepHead._MyType = ICON_TYPES["STAT"]
  163. self._SheepHead._Parent = self
  164. self._SheepHead.Adjust(0,0,self._SheepHeadW,self._SheepHeadH,0)
  165. self._SheepBody = IconItem()
  166. self._SheepBody._ImgSurf = MyIconPool._Icons["sheep_body"]
  167. self._SheepBody._MyType = ICON_TYPES["STAT"]
  168. self._SheepBody._Parent = self
  169. self._SheepBody.Adjust(0,0,self._SheepBodyW,self._SheepBodyH,0)
  170. """
  171. self.Start()
  172. self._GobjectIntervalId = gobject.timeout_add(50,self.Playing)
  173. def Start(self):
  174. try:
  175. self._FIFO = open(self._PIFI._MPD_FIFO)
  176. q = Queue()
  177. self._Queue = q
  178. t = Thread(target=self.GetSpectrum)
  179. t.daemon = True # thread dies with the program
  180. t.start()
  181. except IOError:
  182. print("open %s failed"%self._PIFI._MPD_FIFO)
  183. self._FIFO = None
  184. return
  185. def GetSpectrum(self):
  186. if self._FIFO == None:
  187. print("self._FIFO none")
  188. return
  189. scaledSpectrum = self._PIFI.computeSpectrum(self._FIFO)
  190. self._Queue.put( scaledSpectrum )
  191. self._KeepReading = False
  192. return ## Thread ends
  193. def Playing(self):
  194. if self._Screen.CurPage() == self:
  195. if self._KeepReading == False:
  196. self._KeepReading = True
  197. t = Thread(target=self.GetSpectrum)
  198. t.daemon=True
  199. t.start()
  200. self._Screen.Draw()
  201. self._Screen.SwapAndShow()
  202. else:
  203. return False
  204. return True
  205. def OnLoadCb(self):
  206. if self._FIFO == None:
  207. self.Start()
  208. if self._Queue != None:
  209. with self._Queue.mutex:
  210. self._Queue.queue.clear()
  211. try:
  212. if self._GobjectIntervalId != -1:
  213. gobject.source_remove(self._GobjectIntervalId)
  214. except:
  215. pass
  216. self._GobjectIntervalId = gobject.timeout_add(50,self.Playing)
  217. def KeyDown(self,event):
  218. if event.key == CurKeys["Menu"] or event.key == CurKeys["A"]:
  219. if self._FIFO != None and self._FIFO.closed == False:
  220. try:
  221. self._FIFO.close()
  222. self._FIFO = None
  223. except Exception, e:
  224. print(e)
  225. self.ReturnToUpLevelPage()
  226. self._Screen.Draw()
  227. self._Screen.SwapAndShow()
  228. if event.key == CurKeys["Start"]:
  229. self._Screen.Draw()
  230. self._Screen.SwapAndShow()
  231. if event.key == CurKeys["Enter"]:
  232. pass
  233. def Draw(self):
  234. self.ClearCanvas()
  235. bw = 10
  236. spects = None
  237. try:
  238. spects = self._Queue.get_nowait() ## last element is rms
  239. # print("get_nowait: " , spects)
  240. except Empty:
  241. return
  242. else: # got line
  243. if len(spects) == 0:
  244. return
  245. w = self._Width / len( spects[0:-1] )
  246. left_margin = (w-bw)/2
  247. for i,v in enumerate(spects[0:-1]):
  248. pygame.draw.rect(self._CanvasHWND,self._Color,(i*w+left_margin,self._Height-v,bw,v),0)