Touchscreen audio controller for presentation sound
Program Startup
When the Raspberry Pi boots, the audio program starts automatically. It reads the config file and instantiates a class for each playlist, announcement, or background sound. Playlists preload their tracks, so this process takes a little while, but it displays progress as it loads each file.
Once everything is loaded, the screen layout is generated (Figure 4). Playlists are shown first – one per row. Next, announcements are shown, and they will wrap onto as many rows as needed. Finally, background sounds are shown – two per row. The middle column is left empty.
Code Walkthrough
Listing 2 shows the complete Python program domeAudio. In lines 1-2, import
is Python's command to include an external library. Here, I'm using pygame
for the screen display and audio playback and os
for access to the filesystem.
Listing 2
domeAudio.py
001 import pygame 002 import os 003 004 class background: 005 def __init__ ( self , caption , path ): 006 self.path = path 007 self.caption = caption 008 self.state = "PAUSED" 009 self.volume = 100 010 011 self.buttonFont = pygame.font.SysFont ( "" , 30 ) 012 013 self.oldButtonState = False 014 self.drawPlayButton ( False ) 015 016 def drawPlayButton ( self , active ): 017 if active == None: active = self.oldButtonState 018 019 if active == False: 020 playText = self.buttonFont.render ( self.caption , True , ( 0 , 0 , 0 ) ) 021 self.playButton = pygame.Surface ( ( 100 , 50 ) ) 022 self.playButton.fill ( ( 100 , 0 , 0 ) ) 023 self.playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 024 self.oldButtonState = False 025 026 else: 027 playText = self.buttonFont.render ( self.caption , True , ( 0 , 0 , 0 ) ) 028 self.playButton = pygame.Surface ( ( 100 , 50 ) ) 029 self.playButton.fill ( ( 255 , 0 , 0 ) ) 030 self.playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 031 self.oldButtonState = True 032 033 halfVolume = int ( float ( self.volume ) / 2.0 ) 034 top = 50 - halfVolume 035 pygame.draw.rect ( self.playButton , ( 0 , 0 , 255 ) , ( 0 , top , 3 , halfVolume ) , 0 ) 036 037 def toggle ( self ): 038 if self.state == "PLAYING": 039 self.stop() 040 self.state = "PAUSED" 041 else: 042 self.play() 043 self.state = "PLAYING" 044 045 def play ( self ): 046 self.track = pygame.mixer.Sound ( self.path ) 047 self.track.play ( -1 ) 048 self.track.set_volume ( ( float ( self.volume ) / 100 ) ) 049 050 self.drawPlayButton ( True ) 051 052 def stop ( self ): 053 self.track.fadeout ( 1000 ) 054 self.state = "PAUSED" 055 056 self.drawPlayButton ( False ) 057 058 def controls ( self ): 059 self.buttons = list() 060 061 volupText = self.buttonFont.render ( "VOL UP" , True , ( 0 , 0 , 0 ) ) 062 volupButton = pygame.Surface ( ( 100 , 50 ) ) 063 volupButton.fill ( ( 255 , 0 , 0 ) ) 064 volupButton.blit ( volupText , ( 50 - ( volupText.get_width() / 2 ) , 10 ) ) 065 self.buttons.append ( ( volupButton , self.caption , "BGVOLUP" ) ) 066 067 voldnText = self.buttonFont.render ( "VOL DN" , True , ( 0 , 0 , 0 ) ) 068 voldnButton = pygame.Surface ( ( 100 , 50 ) ) 069 voldnButton.fill ( ( 255 , 0 , 0 ) ) 070 voldnButton.blit ( voldnText , ( 50 - ( voldnText.get_width() / 2 ) , 10 ) ) 071 self.buttons.append ( ( voldnButton , self.caption , "BGVOLDN" ) ) 072 073 self.drawPlayButton ( None ) 074 075 return self.buttons 076 077 def command ( self , cmd ): 078 if cmd == "BGVOLUP": 079 self.volume += 2 080 if self.volume > 100: self.volume = 100 081 if self.state == "PLAYING": self.track.set_volume ( ( float ( self.volume ) / 100 ) ) 082 elif cmd == "BGVOLDN": 083 self.volume -= 2 084 if self.volume < 0: self.volume = 0 085 if self.state == "PLAYING": self.track.set_volume ( ( float ( self.volume ) / 100 ) ) 086 self.drawPlayButton ( None ) 087 088 089 class announcement: 090 def __init__ ( self , caption , path ): 091 self.path = path 092 self.caption = caption 093 094 def play ( self ): 095 announce = pygame.mixer.Sound ( self.path ) 096 announce.play() 097 098 class playlist: 099 def __init__ ( self , caption , path , screen ): 100 self.screen = screen 101 self.caption = caption 102 self.tracks = list() 103 104 self.trackFont = pygame.font.SysFont ( "" , 20 ) 105 self.buttonFont = pygame.font.SysFont ( "" , 30 ) 106 107 y = 10 108 self.screen.fill ( ( 0 , 0 , 0 ) ) 109 pygame.display.flip() 110 for track in os.listdir ( path ): 111 nameSurf = self.trackFont.render ( path + "/" + track , True , ( 255 , 0 , 0 ) ) 112 self.screen.blit ( nameSurf , ( 10 , y ) ) 113 y += 25 114 pygame.display.flip() 115 self.tracks.append ( pygame.mixer.Sound ( path + "/" + track ) ) 116 117 self.trackIndex = 0 118 self.currentTime = 0 119 self.trackLength = 0 120 self.volume = 100 121 self.volumeChange = False 122 self.state = "PAUSED" 123 self.channel = None 124 125 def play ( self ): 126 if self.channel != None: self.channel.set_endevent() #Clear the end event 127 self.currentTrack = self.tracks [ self.trackIndex ] 128 self.currentTrack.set_volume ( float ( self.volume ) / 100 ) 129 self.trackLength = int ( self.currentTrack.get_length() ) 130 self.channel = self.currentTrack.play() 131 self.state = "PLAYING" 132 self.channel.set_endevent ( pygame.USEREVENT ) 133 134 def pause ( self ): 135 self.channel.set_endevent() #Clear the end event 136 self.currentTrack.fadeout ( 1000 ) 137 self.state = "PAUSED" 138 139 def checkStopped ( self ): 140 if self.state == "PLAYING": 141 if self.channel.get_busy() == False: 142 self.channel.set_endevent() 143 self.trackIndex += 1 144 if self.trackIndex > len ( self.tracks ) - 1: self.trackIndex = 0 145 self.currentTrack = self.tracks [ self.trackIndex ] 146 self.currentTrack.set_volume ( float ( self.volume ) / 100 ) 147 148 self.trackLength = int ( self.currentTrack.get_length() ) 149 self.currentTime = 0 150 self.channel = self.currentTrack.play() 151 self.channel.set_endevent ( pygame.USEREVENT ) 152 153 def controls ( self ): 154 self.buttons = list() 155 if self.state == "PLAYING": 156 playText = self.buttonFont.render ( "PAUSE" , True , ( 0 , 0 , 0 ) ) 157 playButton = pygame.Surface ( ( 100 , 50 ) ) 158 playButton.fill ( ( 255 , 0 , 0 ) ) 159 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 160 self.buttons.append ( ( playButton , self.caption , "PAUSE" ) ) 161 else: 162 playText = self.buttonFont.render ( "PLAY" , True , ( 0 , 0 , 0 ) ) 163 playButton = pygame.Surface ( ( 100 , 50 ) ) 164 playButton.fill ( ( 255 , 0 , 0 ) ) 165 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 166 self.buttons.append ( ( playButton , self.caption , "PLAY" ) ) 167 168 playText = self.buttonFont.render ( "NEXT" , True , ( 0 , 0 , 0 ) ) 169 playButton = pygame.Surface ( ( 100 , 50 ) ) 170 playButton.fill ( ( 255 , 0 , 0 ) ) 171 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 172 self.buttons.append ( ( playButton , self.caption , "NEXT" ) ) 173 174 playText = self.buttonFont.render ( "PREV" , True , ( 0 , 0 , 0 ) ) 175 playButton = pygame.Surface ( ( 100 , 50 ) ) 176 playButton.fill ( ( 255 , 0 , 0 ) ) 177 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 178 self.buttons.append ( ( playButton , self.caption , "PREV" ) ) 179 180 playText = self.buttonFont.render ( "VOL UP" , True , ( 0 , 0 , 0 ) ) 181 playButton = pygame.Surface ( ( 100 , 50 ) ) 182 playButton.fill ( ( 255 , 0 , 0 ) ) 183 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 184 self.buttons.append ( ( playButton , self.caption , "VOLUP" ) ) 185 186 playText = self.buttonFont.render ( "VOL DN" , True , ( 0 , 0 , 0 ) ) 187 playButton = pygame.Surface ( ( 100 , 50 ) ) 188 playButton.fill ( ( 255 , 0 , 0 ) ) 189 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 190 self.buttons.append ( ( playButton , self.caption , "VOLDN" ) ) 191 192 return self.buttons 193 194 def command ( self , cmd ): 195 if cmd == "PLAY": self.play() 196 elif cmd == "PAUSE": self.pause() 197 elif cmd == "NEXT": self.next() 198 elif cmd == "PREV": self.prev() 199 elif cmd == "VOLUP": self.volup() 200 elif cmd == "VOLDN": self.voldown() 201 def next ( self ): 202 self.channel.set_endevent() #Clear the end event 203 self.currentTrack.fadeout ( 1000 ) 204 self.trackIndex += 1 205 if self.trackIndex > len ( self.tracks ) - 1: self.trackIndex = 0 206 self.currentTrack = self.tracks [ self.trackIndex ] 207 self.trackLength = int ( self.currentTrack.get_length() ) 208 self.currentTime = 0 209 self.channel = self.currentTrack.play() 210 self.channel.set_endevent ( pygame.USEREVENT ) 211 212 def prev ( self ): 213 self.channel.set_endevent() #Clear the end event 214 self.currentTrack.fadeout ( 1000 ) 215 self.trackIndex -= 1 216 if self.trackIndex < 0: self.trackIndex = len ( self.tracks ) - 1 217 self.currentTrack = self.tracks [ self.trackIndex ] 218 self.trackLength = int ( self.currentTrack.get_length() ) 219 self.currentTime = 0 220 self.channel = self.currentTrack.play() 221 self.channel.set_endevent ( pygame.USEREVENT ) 222 223 def volup ( self ): 224 self.volume += 2 225 if self.volume > 100: self.volume = 100 226 if self.state == "PLAYING": self.currentTrack.set_volume ( ( float ( self.volume ) / 100 ) ) 227 self.volumeChange = True 228 229 def voldown ( self ): 230 self.volume -= 2 231 if self.volume < 0: self.volume = 0 232 if self.state == "PLAYING": self.currentTrack.set_volume ( ( float ( self.volume ) / 100 ) ) 233 self.volumeChange = True 234 235 def drawVolume ( self ): 236 return self.trackFont.render ( str ( self.volume ) + "%" , True , ( 255 , 0 , 0 ) ) 237 238 def tick ( self ): 239 if self.state == "PLAYING": 240 self.currentTime += 1 241 time = self.trackFont.render ( str ( self.trackLength - self.currentTime ) , True , ( 255 , 0 , 0 ) ) 242 return time 243 244 class music: 245 def __init__ ( self , screen ): 246 self.screen = screen 247 self.playlists = dict() 248 self.announcements = dict() 249 self.background = dict() 250 251 pygame.mixer.init() 252 pygame.mixer.init() 253 254 pygame.font.init() 255 self.labelFont = pygame.font.SysFont ( "" , 30 ) 256 257 config = open ( "config.txt" , "r" ) 258 for line in config: 259 lineParts = line.split ( "\t" ) 260 if lineParts [ 2 ].strip() == "PLAYLIST": self.playlists [ lineParts [ 0 ].strip() ] = \ playlist ( lineParts [ 0 ] , lineParts [ 1 ] , self.screen ) 261 if lineParts [ 2 ].strip() == "ANNOUNCEMENT": self.announcements [ lineParts [ 0 ].strip() ] = \ announcement ( lineParts [ 0 ] , lineParts [ 1 ] ) 262 if lineParts [ 2 ].strip() == "BACKGROUND": self.background [ lineParts [ 0 ].strip() ] = \ background ( lineParts [ 0 ] , lineParts [ 1 ] ) 263 264 265 def drawControls ( self ): 266 self.screen.fill ( ( 0 , 0 , 0 ) ) 267 self.clickZones = list() 268 y = 10 269 for index , playlist in self.playlists.items(): 270 x = 250 271 caption = self.labelFont.render ( index , True , ( 255 , 0 , 0 ) ) 272 self.screen.blit ( caption , ( 10 , y ) ) 273 self.screen.blit ( playlist.drawVolume() , ( 50 , y + 30 ) ) 274 for button , playlist , cmd in playlist.controls(): 275 zoneRect = self.screen.blit ( button , ( x , y ) ) 276 x += button.get_width() + 10 277 self.clickZones.append ( ( zoneRect , playlist , cmd ) ) 278 y += 60 279 280 x = 30 281 lineCount = 0 282 for index , announce in self.announcements.items(): 283 playText = self.labelFont.render ( index , True , ( 0 , 0 , 0 ) ) 284 playButton = pygame.Surface ( ( 100 , 50 ) ) 285 playButton.fill ( ( 255 , 0 , 0 ) ) 286 playButton.blit ( playText , ( 50 - ( playText.get_width() / 2 ) , 10 ) ) 287 zone = self.screen.blit ( playButton , ( x , y ) ) 288 self.clickZones.append ( ( zone , index , "ANNOUNCEMENT" ) ) 289 x += 110 290 lineCount += 1 291 if lineCount == 7: 292 lineCount = 0 293 x = 30 294 y += 60 295 296 x = 30 297 y += 60 298 lineCount = 0 299 for index , backTrack in self.background.items(): 300 zone = self.screen.blit ( backTrack.playButton , ( x , y ) ) 301 self.clickZones.append ( ( zone , index , "BACKGROUND" ) ) 302 x += 110 303 304 for button , playlist , cmd in backTrack.controls(): 305 zoneRect = self.screen.blit ( button , ( x , y ) ) 306 x += button.get_width() + 10 307 self.clickZones.append ( ( zoneRect , playlist , cmd ) ) 308 309 lineCount += 1 310 if lineCount == 6: 311 lineCount = 0 312 x = 30 313 y += 60 314 315 316 pygame.display.flip() 317 318 def loop ( self ): 319 pygame.time.set_timer ( pygame.USEREVENT + 1 , 1000 ) 320 self.drawControls() 321 322 looping = True 323 while looping: 324 for event in pygame.event.get(): 325 if event.type == pygame.MOUSEBUTTONDOWN: 326 for zone in self.clickZones: 327 if zone [ 0 ].collidepoint ( event.pos ) == True: 328 if zone [ 2 ] == "ANNOUNCEMENT": 329 self.announcements [ zone [ 1 ] ].play() 330 elif zone [ 2 ] == "BACKGROUND": 331 self.background [ zone [ 1 ] ].toggle() 332 self.drawControls() 333 elif zone [ 2 ] == "BGVOLUP": 334 self.background [ zone [ 1 ] ].command ( zone [ 2 ] ) 335 self.drawControls() 336 elif zone [ 2 ] == "BGVOLDN": 337 self.background [ zone [ 1 ] ].command ( zone [ 2 ] ) 338 self.drawControls() 339 else: 340 self.playlists [ zone [ 1 ] ].command ( zone [ 2 ] ) 341 self.drawControls() 342 elif event.type == pygame.USEREVENT: 343 for index , playlist in self.playlists.items(): 344 playlist.checkStopped() 345 elif event.type == pygame.USEREVENT + 1: 346 redraw = list() 347 y = 10 348 for index , playlist in self.playlists.items(): 349 pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( 10 , y + 30 , 100 , 20 ) ) 350 redraw.append ( self.screen.blit ( playlist.tick() , ( 10 , y + 30 ) ) ) 351 y += 60 352 pygame.display.update ( redraw ) 353 y = 10 354 for index , playlist in self.playlists.items(): 355 if playlist.volumeChange == True: 356 pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) , ( 50 , y + 30 , 100 , 20 ) ) 357 rect = self.screen.blit ( playlist.drawVolume() , ( 50 , y + 30 ) ) 358 pygame.display.update ( [ rect ] ) 359 playlist.volumeChange = False 360 y += 60 361 362 pygame.display.init() 363 screen = pygame.display.set_mode ( ( 800 , 480 ) ) 364 365 sounds = music ( screen ) 366 sounds.loop()
Buy this article as PDF
Pages: 2
(incl. VAT)