Module PeakMeterCtrl
[hide private]
[frames] | no frames]

Source Code for Module PeakMeterCtrl

  1  # --------------------------------------------------------------------------------- # 
  2  # PEAKMETERCTRL wxPython IMPLEMENTATION 
  3  # 
  4  # Andrea Gavana, @ 07 October 2008 
  5  # Latest Revision: 07 October 2008, 22.00 GMT 
  6  # 
  7  # 
  8  # TODO List 
  9  # 
 10  # 1) Falloff effect for vertical bands; 
 11  # 
 12  # 2) Possibly some nicer drawing of bands and leds (using GraphicsContext). 
 13  # 
 14  # 
 15  # For all kind of problems, requests of enhancements and bug reports, please 
 16  # write to me at: 
 17  # 
 18  # andrea.gavana@gmail.com 
 19  # gavana@kpo.kz 
 20  # 
 21  # Or, obviously, to the wxPython mailing list!!! 
 22  # 
 23  # 
 24  # End Of Comments 
 25  # --------------------------------------------------------------------------------- # 
 26   
 27  """ 
 28  Description 
 29  =========== 
 30   
 31  PeakMeterCtrl mimics the behaviour of equalizers that are usually found in stereos 
 32  and MP3 players. This widgets supports: 
 33   
 34  * Vertical and horizontal led bands; 
 35  * Settings number of bands and leds per band; 
 36  * Possibility to change the colour for low/medium/high band frequencies; 
 37  * Falloff effects; 
 38  * Showing a background grid for the bands. 
 39   
 40  And a lot more. Check the demo for an almost complete review of the functionalities. 
 41   
 42   
 43  Supported Platforms 
 44  =================== 
 45   
 46  PeakMeterCtrl has been tested on the following platforms: 
 47    * Windows (Windows XP). 
 48   
 49   
 50  Latest Revision: Andrea Gavana @ 07 October 2008, 22.00 GMT 
 51  Version 0.1 
 52   
 53  """ 
 54   
 55  import wx 
 56   
 57  # Horizontal or vertical PeakMeterCtrl 
 58  PM_HORIZONTAL = 0 
 59  PM_VERTICAL = 1 
 60   
 61  # Some useful constants... 
 62  BAND_DEFAULT = 8 
 63  LEDS_DEFAULT = 8 
 64  BAND_PERCENT = 10       # 10% of Max Range (Auto Decrease) 
 65  GRID_INCREASEBY = 15    # Increase Grid color based on Background color 
 66  FALL_INCREASEBY = 60    # Increase Falloff color based on Background 
 67  DEFAULT_SPEED = 10 
 68   
 69   
70 -def InRange(val, valMin, valMax):
71 """ Returns whether the value val is between valMin and valMax. """ 72 73 return val >= valMin and val <= valMax
74 75
76 -def LightenColor(crColor, byIncreaseVal):
77 """ Lightens a colour. """ 78 79 byRed = crColor.Red() 80 byGreen = crColor.Green() 81 byBlue = crColor.Blue() 82 83 byRed = (byRed + byIncreaseVal <= 255 and [byRed + byIncreaseVal] or [255])[0] 84 byGreen = (byGreen + byIncreaseVal <= 255 and [byGreen + byIncreaseVal] or [255])[0] 85 byBlue = (byBlue + byIncreaseVal <= 255 and [byBlue + byIncreaseVal] or [255])[0] 86 87 return wx.Colour(byRed, byGreen, byBlue)
88 89
90 -def DarkenColor(crColor, byReduceVal):
91 """ Darkens a colour. """ 92 93 byRed = crColor.Red() 94 byGreen = crColor.Green() 95 byBlue = crColor.Blue() 96 97 byRed = (byRed >= byReduceVal and [byRed - byReduceVal] or [0])[0] 98 byGreen = (byGreen >= byReduceVal and [byGreen - byReduceVal] or [0])[0] 99 byBlue = (byBlue >= byReduceVal and [byBlue - byReduceVal] or [0])[0] 100 101 return wx.Colour(byRed, byGreen, byBlue)
102 103
104 -class PeakMeterData(object):
105 """ A simple class which holds data for our L{PeakMeterCtrl}. """ 106
107 - def __init__(self, value=0, falloff=0, peak=0):
108 """ 109 Default class constructor. 110 111 @param value: the current value; 112 @param falloff: the falloff effect; 113 @param peak: the peak value. 114 """ 115 116 self._value = value 117 self._falloff = falloff 118 self._peak = peak
119 120
121 - def IsEqual(self, pm):
122 """ Returns whether 2 PeakMeterData are the same. """ 123 124 return self._value == pm._value
125 126
127 - def IsGreater(self, pm):
128 """ Returns whether one PeakMeterData is greater than another. """ 129 130 return self._value > pm._value
131 132
133 - def IsLower(self, pm):
134 """ Returns whether one PeakMeterData is smaller than another. """ 135 136 return self._value < pm._value
137 138
139 -class PeakMeterCtrl(wx.PyControl):
140 """ The main L{PeakMeterCtrl} implementation. """ 141
142 - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, 143 style=PM_VERTICAL):
144 """ 145 Default class constructor. 146 147 @param parent: the L{PeakMeterCtrl} parent; 148 @param id: the widget id; 149 @param pos: the control position; 150 @param size: the L{PeakMeterCtrl} size; 151 @param style: the widget style, which can be PM_VERTICAL for a vertical 152 L{PeakMeterCtrl} or PM_HORIZONTAL for an horizontal one. 153 """ 154 155 wx.PyControl.__init__(self, parent, id, pos, size, style) 156 self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 157 158 # Initializes all data 159 self.InitData() 160 161 self.Bind(wx.EVT_PAINT, self.OnPaint) 162 self.Bind(wx.EVT_SIZE, self.OnSize) 163 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) 164 self.Bind(wx.EVT_TIMER, self.OnTimer)
165 166
167 - def InitData(self):
168 """ Initializes the control. """ 169 170 colLime = wx.Colour(0, 255, 0) 171 colRed = wx.Colour(255, 0, 0) 172 colYellow = wx.Colour(255, 255, 0) 173 174 self._showGrid = False 175 self._showFalloff = True 176 self._delay = 10 177 self._minValue = 60 # Min Range 0-60 178 self._medValue = 80 # Med Range 60-80 179 self._maxValue = 100 # Max Range 80-100 180 self._numBands = BAND_DEFAULT 181 self._ledBands = LEDS_DEFAULT 182 self._clrBackground = self.GetBackgroundColour() 183 self._clrNormal = colLime 184 self._clrMedium = colYellow 185 self._clrHigh = colRed 186 self._speed = DEFAULT_SPEED 187 self._timer = wx.Timer(self) 188 189 # clear vector data 190 self._meterData = []
191 192
193 - def ResetControl(self):
194 """ Resets the L{PeakMeterCtrl}. """ 195 196 # Initialize vector 197 for i in xrange(self._numBands): 198 pm = PeakMeterData(self._maxValue, self._maxValue, self._speed) 199 self._meterData.append(pm) 200 201 self.Refresh()
202 203
204 - def SetBackgroundColor(self, colorBgnd):
205 """ 206 Set background color for L{PeakMeterCtrl}. 207 208 @param colorBgnd: the background colour to apply. """ 209 210 wx.PyControl.SetBackgroundColour(self, colorBgnd) 211 self._clrBackground = colorBgnd 212 self.Refresh()
213 214
215 - def SetBandsColor(self, colorNormal, colorMedium, colorHigh):
216 """ 217 Set bands color for L{PeakMeterCtrl}. 218 219 @param colorNormal: the color for normal (low) bands; 220 @param colorMedium: the color for medium bands; 221 @param colorHigh: the color for high bands. 222 """ 223 224 self._clrNormal = colorNormal 225 self._clrMedium = colorMedium 226 self._clrHigh = colorHigh 227 228 self.Refresh()
229 230
231 - def SetMeterBands(self, numBands, ledBands):
232 """ 233 Set number of Vertical or Horizontal bands to display. 234 235 @note: obtain smooth effect by setting nHorz or nVert to "1", these 236 cannot be 0. 237 """ 238 239 assert (numBands > 0 and ledBands > 0) 240 241 self._numBands = numBands 242 self._ledBands = ledBands 243 244 # Reset vector 245 self.ResetControl()
246 247
248 - def SetRangeValue(self, minVal, medVal, maxVal):
249 """ 250 Sets the ranges for low, medium and high bands. 251 @note: condition to be satisfied is that: 252 Min: [0 - nMin[, Med: [nMin - nMed[, Max: [nMed - nMax] 253 """ 254 255 assert (maxVal > medVal and medVal > minVal and minVal > 0) 256 257 self._minValue = minVal 258 self._medValue = medVal 259 self._maxValue = maxVal
260 261
262 - def GetRangeValue(self):
263 """ Get Range value of L{PeakMeterCtrl}. """ 264 265 return self._minValue, self._medValue, self._maxValue
266 267
268 - def SetFalloffDelay(self, speed):
269 """ Set Peak value speed before falling off. """ 270 271 self._speed = speed
272 273
274 - def SetFalloffEffect(self, falloffEffect):
275 """ Set falloff effect flag. """ 276 277 if self._showFalloff != falloffEffect: 278 279 self._showFalloff = falloffEffect 280 self.Refresh()
281 282
283 - def GetFalloffEffect(self):
284 """ Read falloff effect flag. """ 285 286 return self._showFalloff
287 288
289 - def ShowGrid(self, showGrid):
290 """ Request to have gridlines visible or not. """ 291 292 if self._showGrid != showGrid: 293 294 self._showGrid = showGrid 295 self.Refresh()
296 297
298 - def IsGridVisible(self):
299 """ Returns if gridlines are visible. """ 300 301 return self._showGrid
302 303
304 - def SetData(self, arrayValue, offset, size):
305 """ 306 Change data value. Use this function to change only 307 a set of values. All bands can be changed or only 1 band, 308 depending on the application. 309 """ 310 311 assert (offset >= 0 and arrayValue != []) 312 313 isRunning = self.IsStarted() 314 315 # Stop timer if Animation is active 316 if isRunning: 317 self.Stop() 318 319 maxSize = offset + size 320 321 for i in xrange(offset, maxSize): 322 323 if i < len(self._meterData): 324 325 pm = self._meterData[i] 326 pm._value = arrayValue[i] 327 328 if pm._falloff < pm._value: 329 330 pm._falloff = pm._value 331 pm._peak = self._speed 332 333 self._meterData[i] = pm 334 335 # Auto-restart 336 if isRunning: 337 return self.Start(self._delay) 338 339 self.Refresh() 340 341 return True
342 343
344 - def IsStarted(self):
345 """ Check if animation is active. """ 346 347 return self._timer.IsRunning()
348 349
350 - def Start(self, delay):
351 """Start the timer and animation effect. """ 352 353 if not self.IsStarted(): 354 self._delay = delay 355 self._timer.Start(self._delay) 356 else: 357 return False 358 359 return True
360 361
362 - def Stop(self):
363 """ Stop the timer and animation effect. """ 364 365 if self.IsStarted(): 366 self._timer.Stop() 367 return True 368 369 return False
370 371
372 - def DoTimerProcessing(self):
373 """ L{PeakMeterCtrl} animation, does the wx.EVT_TIMER processing. """ 374 375 self.Refresh() 376 377 decValue = self._maxValue/self._ledBands 378 noChange = True 379 380 for pm in self._meterData: 381 382 if pm._value > 0: 383 384 pm._value -= (self._ledBands > 1 and [decValue] or [self._maxValue*BAND_PERCENT/100])[0] 385 if pm._value < 0: 386 pm._value = 0 387 388 noChange = False 389 390 if pm._peak > 0: 391 392 pm._peak -= 1 393 noChange = False 394 395 396 if pm._peak == 0 and pm._falloff > 0: 397 398 pm._falloff -= (self._ledBands > 1 and [decValue >> 1] or [5])[0] 399 if pm._falloff < 0: 400 pm._falloff = 0 401 402 noChange = False 403 404 if noChange: # Stop timer if no more data 405 406 self.Stop()
407 408
409 - def DoGetBestSize(self):
410 """ Returns the best size for L{PeakMeterCtrl} (arbitrary). """ 411 412 return wx.Size(200, 150)
413 414
415 - def OnPaint(self, event):
416 """ Handles the wx.EVT_PAINT event for L{PeakMeterCtrl}. """ 417 418 dc = wx.AutoBufferedPaintDC(self) 419 self._clrBackground = self.GetBackgroundColour() 420 dc.SetBackground(wx.Brush(self._clrBackground)) 421 dc.Clear() 422 rc = self.GetClientRect() 423 424 pen = wx.Pen(self._clrBackground) 425 dc.SetPen(pen) 426 427 if self.GetWindowStyleFlag() & PM_VERTICAL: 428 self.DrawVertBand(dc, rc) 429 else: 430 self.DrawHorzBand(dc, rc)
431 432
433 - def OnEraseBackground(self, event):
434 """ Handles the wx.EVT_ERASE_BACKGROUND event for L{PeakMeterCtrl}. """ 435 436 # This is intentionally empty, to reduce flicker 437 pass
438 439
440 - def OnSize(self, event):
441 """ Handles the wx.EVT_SIZE event for L{PeakMeterCtrl}. """ 442 443 self.Refresh() 444 event.Skip()
445 446
447 - def OnTimer(self, event):
448 """ Handles the wx.EVT_TIMER events for L{PeakMeterCtrl}. """ 449 450 self.DoTimerProcessing()
451 452
453 - def DrawHorzBand(self, dc, rect):
454 """ Draw Vertical bands - No falloff effect for vertical bands. """ 455 456 horzBands = (self._ledBands > 1 and [self._ledBands] or [self._maxValue*BAND_PERCENT/100])[0] 457 minHorzLimit = self._minValue*horzBands/self._maxValue 458 medHorzLimit = self._medValue*horzBands/self._maxValue 459 maxHorzLimit = horzBands 460 461 size = wx.Size(rect.width/horzBands, rect.height/self._numBands) 462 rectBand = wx.RectPS(rect.GetTopLeft(), size) 463 464 # Draw band from top 465 rectBand.OffsetXY(0, rect.height-size.y*self._numBands) 466 xDecal = (self._ledBands > 1 and [1] or [0])[0] 467 yDecal = (self._numBands > 1 and [1] or [0])[0] 468 469 for vert in xrange(self._numBands): 470 471 self._value = self._meterData[vert]._value 472 horzLimit = self._value*horzBands/self._maxValue 473 474 for horz in xrange(horzBands): 475 476 rectBand.Deflate(0, yDecal) 477 478 # Find color based on range value 479 colorRect = self._clrBackground 480 if self._showGrid: 481 colorRect = DarkenColor(self._clrBackground, GRID_INCREASEBY) 482 483 if self._showGrid and (horz == minHorzLimit or horz == (horzBands-1)): 484 485 points = [wx.Point() for i in xrange(2)] 486 points[0].x = rectBand.GetTopLeft().x + (rectBand.width >> 1) 487 points[0].y = rectBand.GetTopLeft().y - yDecal 488 points[1].x = points[0].x 489 points[1].y = rectBand.GetBottomRight().y + yDecal 490 dc.DrawLinePoint(points[0], points[1]) 491 492 if horz < horzLimit: 493 494 if InRange(horz, 0, minHorzLimit-1): 495 colorRect = self._clrNormal 496 elif InRange(horz, minHorzLimit, medHorzLimit-1): 497 colorRect = self._clrMedium 498 elif InRange(horz, medHorzLimit, maxHorzLimit): 499 colorRect = self._clrHigh 500 501 dc.SetBrush(wx.Brush(colorRect)) 502 dc.DrawRectangleRect(rectBand) 503 504 rectBand.Inflate(0, yDecal) 505 rectBand.OffsetXY(size.x, 0) 506 507 # Move to Next Vertical band 508 rectBand.OffsetXY(-size.x*horzBands, size.y)
509 510
511 - def DrawVertBand(self, dc, rect):
512 """ Draw Horizontal bands - with Falloff effect. """ 513 514 vertBands = (self._ledBands > 1 and [self._ledBands] or [self._maxValue*BAND_PERCENT/100])[0] 515 minVertLimit = self._minValue*vertBands/self._maxValue 516 medVertLimit = self._medValue*vertBands/self._maxValue 517 maxVertLimit = vertBands 518 519 size = wx.Size(rect.width/self._numBands, rect.height/vertBands) 520 rectBand = wx.RectPS(rect.GetTopLeft(), size) 521 522 # Draw band from bottom 523 rectBand.OffsetXY(0, rect.bottom-size.y) 524 xDecal = (self._numBands > 1 and [1] or [0])[0] 525 yDecal = (self._ledBands > 1 and [1] or [0])[0] 526 527 for horz in xrange(self._numBands): 528 529 self._value = self._meterData[horz]._value 530 vertLimit = self._value*vertBands/self._maxValue 531 rectPrev = wx.Rect(*rectBand) 532 533 for vert in xrange(vertBands): 534 535 rectBand.Deflate(xDecal, 0) 536 537 # Find color based on range value 538 colorRect = self._clrBackground 539 if self._showGrid: 540 colorRect = DarkenColor(self._clrBackground, GRID_INCREASEBY) 541 542 # Draw grid line (level) bar 543 if self._showGrid and (vert == minVertLimit or vert == (vertBands-1)): 544 545 points = [wx.Point() for i in xrange(2)] 546 points[0].x = rectBand.GetTopLeft().x - xDecal 547 points[0].y = rectBand.GetTopLeft().y + (rectBand.height >> 1) 548 points[1].x = rectBand.GetBottomRight().x + xDecal 549 points[1].y = points[0].y 550 dc.DrawLinePoint(points[0], points[1]) 551 552 if vert < vertLimit: 553 554 if InRange(vert, 0, minVertLimit-1): 555 colorRect = self._clrNormal 556 elif InRange(vert, minVertLimit, medVertLimit-1): 557 colorRect = self._clrMedium 558 elif InRange(vert, medVertLimit, maxVertLimit): 559 colorRect = self._clrHigh 560 561 dc.SetBrush(wx.Brush(colorRect)) 562 dc.DrawRectangleRect(rectBand) 563 564 rectBand.Inflate(xDecal, 0) 565 rectBand.OffsetXY(0, -size.y) 566 567 # Draw falloff effect 568 if self._showFalloff: 569 570 oldPen = dc.GetPen() 571 pen = wx.Pen(DarkenColor(self._clrBackground, FALL_INCREASEBY)) 572 maxHeight = size.y*vertBands 573 points = [wx.Point() for i in xrange(2)] 574 points[0].x = rectPrev.GetTopLeft().x + xDecal 575 points[0].y = rectPrev.GetBottomRight().y - self._meterData[horz]._falloff*maxHeight/self._maxValue 576 points[1].x = rectPrev.GetBottomRight().x - xDecal 577 points[1].y = points[0].y 578 dc.SetPen(pen) 579 dc.DrawLinePoint(points[0], points[1]) 580 dc.SetPen(oldPen) 581 582 # Move to Next Horizontal band 583 rectBand.OffsetXY(size.x, size.y*vertBands)
584