diff --git a/Menu/GameShell/Music Player/list_item.py b/Menu/GameShell/Music Player/list_item.py index f681fc3..c03ab5d 100644 --- a/Menu/GameShell/Music Player/list_item.py +++ b/Menu/GameShell/Music Player/list_item.py @@ -65,7 +65,7 @@ class ListItem(object): _PlayingProcess = 0 # 0 - 100 _Parent = None - + _Text = "" def __init__(self): self._Labels = {} self._Icons = {} @@ -75,6 +75,7 @@ class ListItem(object): def Init(self,text): #self._Fonts["normal"] = fonts["veramono12"] + self._Text = text l = ListItemLabel() l._PosX = 22 diff --git a/Menu/GameShell/Music Player/mpd_spectrum_page.py b/Menu/GameShell/Music Player/mpd_spectrum_page.py index d12bccf..4892a95 100644 --- a/Menu/GameShell/Music Player/mpd_spectrum_page.py +++ b/Menu/GameShell/Music Player/mpd_spectrum_page.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +import os import time import pygame -from numpy import fromstring,ceil,abs,log10,isnan,isinf,int16,sqrt,mean -from numpy import fft as Fft +import numpy +import math import gobject @@ -20,141 +21,51 @@ from UI.keys_def import CurKeys from UI.icon_item import IconItem from UI.icon_pool import MyIconPool -from Queue import Queue, Empty from threading import Thread - - from list_item import ListItem import myvars class PIFI(object): _MPD_FIFO = "/tmp/mpd.fifo" - _SAMPLE_SIZE = 256 + _SAMPLE_SIZE = 1024 _SAMPLING_RATE = 44100 _FIRST_SELECTED_BIN = 5 - _NUMBER_OF_SELECTED_BINS = 10 - _SCALE_WIDTH = Height/2 - 20 + _NUMBER_OF_SELECTED_BINS = 1024 - count = 0 - average = 0 - - rmscount=0 - rmsaverage=0 def __init__(self): self.sampleSize = self._SAMPLE_SIZE self.samplingRate = self._SAMPLING_RATE - self.firstSelectedBin = self._FIRST_SELECTED_BIN - self.numberOfSelectedBins = self._NUMBER_OF_SELECTED_BINS - # Initialization : frequency bins - freq = Fft.fftfreq(self.sampleSize) * self.samplingRate - freqR = freq[:self.sampleSize/2] - self.bins = freqR[self.firstSelectedBin:self.firstSelectedBin+self.numberOfSelectedBins] - - self.resetSmoothing() + def GetSpectrum(self,fifoFile,trim_by=10,log_scale=False,div_by=100): + try: + rawSamples = os.read(fifoFile,self.sampleSize) # will return empty lines (non-blocking) + if len(rawSamples) < 1: +# print("Read error") + return rawSamples + except Exception,e: + return "" - def resetSmoothing(self): - self.count = 0 - self.average = 0 - self.rmscount = 0 - self.rmsaverage = 0 + data = numpy.fromstring(rawSamples, dtype=numpy.int16) - def rms_smoothOut(self, x): - self.rmscount += 1 - self.rmsaverage = (self.rmsaverage*self.rmscount + x) / (self.rmscount+1) - return self.rmsaverage + data = data * numpy.hanning(len(data)) + + left,right = numpy.split(numpy.abs(numpy.fft.fft(data)),2) + spec_y = numpy.add(left,right[::-1]) + + if log_scale: + spec_y=numpy.multiply(20,numpy.log10(spec_y)) + if trim_by: + i=int((self.sampleSize/2)/trim_by) + spec_y=spec_y[:i] + if div_by: + spec_y=spec_y/float(div_by) + + return spec_y - def smoothOut(self, x): - self.count += 1 - self.average = (self.average*self.count + x) / (self.count+1) - return self.average - def scaleList(self, _list): - for i,x in enumerate(_list): - if isnan(x) or isinf(x): - _list[i] = 0 - - # Compute a simple just-above 'moving average' of maximums - maximum = 1.1*self.smoothOut(max( _list )) - if maximum == 0: - scaleFactor = 0.0 - else: - scaleFactor = self._SCALE_WIDTH/float(maximum) - - # Compute the scaled list of values - scaledList = [int(x*scaleFactor) for x in _list ] - return scaledList - - - def computeSpectrum(self, fifoFile): - - # Read PCM samples from fifo - rawSamples = fifoFile.read(self.sampleSize) # will return empty lines (non-blocking) - if len(rawSamples) == 0: - print("computeSpectrum read zero") - return [],[] - else: - pass -## print("computeSpectrum %d " % len(rawSamples)) - - pcm = fromstring(rawSamples, dtype=int16) - - # Normalize [-1; +1] - pcm = pcm / (2.**15) - - # Compute RMS directly from signal - rms = sqrt(mean(pcm**2)) - # Compute a simple 'moving maximum' - maximum = 2*self.rms_smoothOut(rms) - if maximum == 0: - scaleFactor = 0.0 - else: - scaleFactor = self._SCALE_WIDTH/float(maximum) - - final_rms = int(rms*scaleFactor) - - # Compute FFT - N = pcm.size - fft = Fft.fft(pcm) - uniquePts = ceil((N+1)/2.0) - fft = fft[0:int(uniquePts)] - - # Compute amplitude spectrum - amplitudeSpectrum = abs(fft) / float(N) - - # Compute power spectrum - p = amplitudeSpectrum**2 - - # Multiply by two to keep same energy - # See explanation: - # https://web.archive.org/web/20120615002031/http://www.mathworks.com/support/tech-notes/1700/1702.html - if N % 2 > 0: - # odd number of points - # odd nfft excludes Nyquist point - p[1:len(p)] = p[1:len(p)] * 2 - else: - # even number of points - p[1:len(p) -1] = p[1:len(p) - 1] * 2 - - # Power in logarithmic scale (dB) - logPower = 10*log10(p) - - # Compute RMS from power - #rms = numpy.sqrt(numpy.sum(p)) - #print "RMS(power):", rms - - # Select a significant range in the spectrum - spectrum = logPower[self.firstSelectedBin:self.firstSelectedBin+self.numberOfSelectedBins] - - # Scale the spectrum - scaledSpectrum = self.scaleList(spectrum) - scaledSpectrum.append( final_rms) - return scaledSpectrum - - class MPDSpectrumPage(Page): _Icons = {} @@ -162,14 +73,15 @@ class MPDSpectrumPage(Page): _FootMsg = ["Nav","","","Back",""] _MyList = [] _ListFont = fonts["veramono12"] - + _SongFont = fonts["notosanscjk17"] _PIFI = None _FIFO = None _Color = pygame.Color(126,206,244) _GobjectIntervalId = -1 _Queue = None _KeepReading = True - + _ReadingThread = None + _BGpng = None _BGwidth = 320 _BGheight = 200 @@ -182,10 +94,23 @@ class MPDSpectrumPage(Page): _SheepBodyW = 105 _SheepBodyH = 81 + _RollCanvas = None + _RollW = 220 + _RollH = 68 + _freq_count = 0 _head_dir = 0 _Neighbor = None + + + _bby = [] + _bbs = [] + _capYPositionArray = [] + _frames = 0 + read_retry = 0 + _queue_data = [] + _vis_values = [] def __init__(self): Page.__init__(self) @@ -200,7 +125,8 @@ class MPDSpectrumPage(Page): self._Height = self._Screen._Height self._CanvasHWND = self._Screen._CanvasHWND - + self._RollCanvas = pygame.Surface(( self._RollW,self._RollH)) + """ self._BGpng = IconItem() self._BGpng._ImgSurf = MyIconPool._Icons["sheep_bg"] @@ -221,19 +147,35 @@ class MPDSpectrumPage(Page): self._SheepBody.Adjust(0,0,self._SheepBodyW,self._SheepBodyH,0) """ - self.Start() - self._GobjectIntervalId = gobject.timeout_add(50,self.Playing) + self._cwp_png = IconItem() + self._cwp_png._ImgSurf = MyIconPool._Icons["clockworkpi"] + self._cwp_png._MyType = ICON_TYPES["STAT"] + self._cwp_png._Parent = self + self._cwp_png.Adjust(0,0,79,79,0) + + + self._song_title = Label() + self._song_title.SetCanvasHWND(self._RollCanvas) + + self._song_title.Init("Untitled",self._SongFont,(255,255,255)) + + + + self.Start() + def Start(self): + + if self._Screen.CurPage() != self: + return try: - self._FIFO = open(self._PIFI._MPD_FIFO) - q = Queue() - self._Queue = q + self._FIFO = os.open(self._PIFI._MPD_FIFO, os.O_RDONLY | os.O_NONBLOCK) t = Thread(target=self.GetSpectrum) t.daemon = True # thread dies with the program t.start() + self._ReadingThread = t except IOError: print("open %s failed"%self._PIFI._MPD_FIFO) @@ -242,61 +184,79 @@ class MPDSpectrumPage(Page): def GetSpectrum(self): - if self._FIFO == None: - print("self._FIFO none") - return + while self._KeepReading and self._FIFO != None: + raw_samples = self._PIFI.GetSpectrum(self._FIFO) + if len(raw_samples) < 1: + #print("sleeping... 0.01") + time.sleep(0.01) + self.read_retry+=1 + if self.read_retry > 40: + os.close(self._FIFO) + self._FIFO = os.open(self._PIFI._MPD_FIFO, os.O_RDONLY | os.O_NONBLOCK) + self.read_retry = 0 + + self.Playing() + + else: + self.read_retry = 0 + self._queue_data = raw_samples + self.Playing() - scaledSpectrum = self._PIFI.computeSpectrum(self._FIFO) - self._Queue.put( scaledSpectrum ) - - self._KeepReading = False - - return ## Thread ends def Playing(self): - if self._Screen.CurPage() == self: - if self._KeepReading == False: - self._KeepReading = True - - t = Thread(target=self.GetSpectrum) - t.daemon=True - t.start() - - self._Screen.Draw() - self._Screen.SwapAndShow() - - else: - - return False - return True + self._Screen.Draw() + self._Screen.SwapAndShow() + + + def ClearCanvas(self): + self._CanvasHWND.fill((0,0,0)) + + def SgsSmooth(self): + passes = 1 + points = 3 + origs = self._bby[:] + for p in range(0,passes): + pivot = int(points/2.0) + + for i in range(0,pivot): + self._bby[i] = origs[i] + self._bby[ len(origs) -i -1 ] = origs[ len(origs) -i -1 ] + + smooth_constant = 1.0/(2.0*pivot+1.0) + for i in range(pivot, len(origs)-pivot): + _sum = 0.0 + for j in range(0,(2*pivot)+1): + _sum += (smooth_constant * origs[i+j-pivot]) +j -pivot + + self._bby[i] = _sum + + if p < (passes - 1): + origs = self._bby[:] def OnLoadCb(self): + if self._Neighbor != None: + pass + + if self._KeepReading == False: + self._KeepReading = True + if self._FIFO == None: self.Start() - if self._Queue != None: - with self._Queue.mutex: - self._Queue.queue.clear() - - try: - if self._GobjectIntervalId != -1: - gobject.source_remove(self._GobjectIntervalId) - except: - pass - - self._GobjectIntervalId = gobject.timeout_add(50,self.Playing) - def KeyDown(self,event): if event.key == CurKeys["Menu"] or event.key == CurKeys["A"]: - if self._FIFO != None and self._FIFO.closed == False: - try: - self._FIFO.close() - self._FIFO = None - except Exception, e: - print(e) + try: + os.close(self._FIFO) + self._FIFO = None + + except Exception, e: + print(e) + self._KeepReading = False + self._ReadingThread.join() + self._ReadingThread = None self.ReturnToUpLevelPage() self._Screen.Draw() @@ -308,25 +268,101 @@ class MPDSpectrumPage(Page): if event.key == CurKeys["Enter"]: pass + def Draw(self): self.ClearCanvas() bw = 10 + gap = 2 + margin_bottom = 100 + spects = None + meterNum = self._Width / float(bw +gap ) ## 320/12= 26 + meter_left = meterNum - int(meterNum) + meter_left = meter_left*int(bw+gap) + margin_left = meter_left / 2 + gap + meterNum = int(meterNum) + + self._cwp_png.NewCoord(43,149) + self._cwp_png.Draw() + + if self._Neighbor != None: + if self._Neighbor._CurSongName != "": + self._song_title.SetText(self._Neighbor._CurSongName) + + + + if self._RollCanvas != None: + self._RollCanvas.fill((0,0,0)) + + self._song_title.NewCoord(0,0) + self._song_title.Draw() + + self._CanvasHWND.blit(self._RollCanvas,(86,114,220,68)) + try: - spects = self._Queue.get_nowait() ## last element is rms -# print("get_nowait: " , spects) + spects = self._queue_data + if len(spects) == 0: + return +# print("spects:",spects) + step = int( round( len( spects ) / meterNum) ) + + self._bbs = [] + + for i in range(0,meterNum): + index = int(i*step) + total = 0 + + value = spects[index] + self._bbs.append(value) + + if len(self._bby) < len(self._bbs): + self._bby = self._bbs + elif len(self._bby) == len(self._bbs): + for i in range(0,len(self._bbs)): + self._bby[i] = (self._bby[i]+self._bbs[i])/2 + + self.SgsSmooth() + + for i in range(0,meterNum): + value = self._bby[ i ] + if math.isnan(value) or math.isinf(value): + value = 0 + + value = value/32768.0 + value = value * 100 + value = value % (self._Height-gap-margin_bottom) + + if len(self._vis_values) < len(self._bby): + self._vis_values.append(value) + elif len(self._vis_values) == len(self._bby): + if self._vis_values[i] < value: + self._vis_values[i] = value + + except Empty: return else: # got line - if len(spects) == 0: + if len(self._vis_values) == 0: return - w = self._Width / len( spects[0:-1] ) - left_margin = (w-bw)/2 - for i,v in enumerate(spects[0:-1]): - pygame.draw.rect(self._CanvasHWND,self._Color,(i*w+left_margin,self._Height-v,bw,v),0) - + + for i in range(0,meterNum): + value = self._vis_values[i] + + if len(self._capYPositionArray) < round(meterNum): + self._capYPositionArray.append(value) + + if value < self._capYPositionArray[i]: + self._capYPositionArray[i]-=0.5 + else: + self._capYPositionArray[i] = value + + pygame.draw.rect(self._CanvasHWND,(255,255,255),(i*(bw+gap)+margin_left,self._Height-gap-self._capYPositionArray[i]-margin_bottom,bw,gap),0) + + pygame.draw.rect(self._CanvasHWND,(255,255,255),(i*(bw+gap)+margin_left,self._Height-value-gap-margin_bottom,bw,value+gap),0) + + self._vis_values[i] -= 2 diff --git a/Menu/GameShell/Music Player/play_list_page.py b/Menu/GameShell/Music Player/play_list_page.py index 308a888..b5ae9bf 100644 --- a/Menu/GameShell/Music Player/play_list_page.py +++ b/Menu/GameShell/Music Player/play_list_page.py @@ -71,6 +71,8 @@ class PlayListPage(Page): _BGheight = 70 _Scrolled = 0 + + _CurSongName = "" def __init__(self): self._Icons = {} @@ -134,6 +136,8 @@ class PlayListPage(Page): if "song" in current_song: posid = int(current_song["song"]) if posid < len(self._MyList): # out of index + self._CurSongName = self._MyList[posid]._Text + if "state" in current_song: if current_song["state"] == "stop": self._MyList[posid]._Playing = False diff --git a/skin/default/sys.py/gameshell/icons/clockworkpi.png b/skin/default/sys.py/gameshell/icons/clockworkpi.png new file mode 100644 index 0000000..b45aff9 Binary files /dev/null and b/skin/default/sys.py/gameshell/icons/clockworkpi.png differ