From 55755a29f64dba0168f1ce2900562c8468662cbc Mon Sep 17 00:00:00 2001 From: Thorin Date: Mon, 11 Jun 2012 14:35:03 +0000 Subject: import tool combobox --- (limited to 'run.py~') diff --git a/run.py~ b/run.py~ new file mode 100644 index 0000000..242ad90 --- /dev/null +++ b/run.py~ @@ -0,0 +1,576 @@ +#! /usr/bin/env python + +#This python module is part of the Jam2Jam XO Activity, March, 2010 +# +#Copyright (C) 2010 Thorin Kerr & Andrew Brown +# +#This program is free software; you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation; either version 2 of the License, or any +#later version. +# +#This program is distributed in the hope that it will be useful, but +#WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +#along with this program; if not, write to the Free Software +#Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import subprocess +import logging, olpcgames +import olpcgames.pausescreen as pausescreen +import olpcgames.mesh as mesh +from olpcgames import camera +from sugar.presence import presenceservice +from threading import Timer +from math import ceil, sqrt + + +from City.CsHelpers import * +from City.Parameters import Instrument +import City.City as City + +log = logging.getLogger( 'City run' ) +log.setLevel( logging.DEBUG ) + +def buildInstruments(names, imgpath, screensize, scale): + "returns a list of Instrument objects, loaded with images" + Instruments = [Instrument(names[i]) for i in range(len(names))] + imagefiles = ResourceList(imgpath, '.png') + startx = 0 + for i in Instruments: + for f in imagefiles: + if i.name.startswith(f[:4]): + i.loadImage(ImagePath+'/'+f, scale) + i.x = screensize[0] * 0.8 - startx + i.y = (screensize[1] - i.image.get_size()[1]) * 0.5 + i.Touch = True + startx = startx + (screensize[0] * 0.8) / len(Instruments) + return Instruments + +def getInstrumentParameters(scene, inm): + "return a list of parameters values for the instrument, in order of PNAMES" + pobj = scene.Params + result = [] + for pnm in PNAMES: + result.append(pobj.getValue(pnm, inm)) + return result + +def setInstrumentParameters(scene, inm, vlst): + "sets parameters for an instrument" + pobj = scene.Params + for pnm,val in zip(PNAMES, vlst): + pobj.setValue(pnm, inm, val) + return True + +KEYCODES = {276:"Nudge|Left", 275:"Nudge|Right", 274:"Nudge|Down", 273:"Nudge|Up", + 260:"Nudge|Left", 262:"Nudge|Right", 258:"Nudge|Down", 264:"Nudge|Up", + 263: "Instrument|Bass", 257:"Instrument|Chords", 265:"Instrument|Lead", 259:"Instrument|Drums", + 49: "Instrument|Bass", 50:"Instrument|Chords", 51:"Instrument|Lead", 52:"Instrument|Drums", + 112: "Parameter|Pitch", 118:"Parameter|Volume", 100:"Parameter|Density", 108:"Parameter|Length", 116:"Parameter|Timbre", + 304: "Modifier|Shift"} + + +class jamScene( object ): + def __init__(self, screen, scene = 'City', key = 'A', mode = 'minor', tempo = 120, initial_parameters = {}): + self.scene = City.ScenePlayer(scene, key, mode, tempo, initial_parameters) + self.music_player = City.makePlayer(self.scene) + self.beatEstimator = beatEstimator(self.music_player.tempoMult, 0.17, self.music_player.beatlimit) + self.pending_instrument_assignment = [] + self.latency_counter = 0 + self.latency = [0.07] + self.latency_time_ID = {} + self._syncloop_running = 0 + global schedEvent, now + schedEvent = self.scene.TimeQueue.schedEvent + now = self.scene.cs.perfTime + self.screen = screen + screenRect = screen.get_rect() + self.screenSize = screen.get_size() + self.playArea = pygame.Rect(screenRect.left,screenRect.top, screenRect.width, screenRect.height * 0.8) + if olpcgames.ACTIVITY: + olpcgames.ACTIVITY.playArea = self.playArea + olpcgames.ACTIVITY.jamScene = self + self.panelArea = pygame.Rect(screenRect.left,screenRect.height * 0.8, screenRect.width, screenRect.height * 0.2) + self.TemplateInstruments = buildInstruments(INAMES, ImagePath, self.playArea.size, 2) + for oni in self.TemplateInstruments: + oni.activate() + self.PanelInstruments = buildInstruments(INAMES, ImagePath, self.panelArea.size, 1.5) + for pnl in self.PanelInstruments: + pnl.Touch = True + pnl.activate() + imagesize = self.TemplateInstruments[0].image.get_size() + self.panelSize = (self.screenSize[0], imagesize[1] + 10) + #movement limits + self.xmin = self.playArea.left + imagesize[0] * 0.5 + self.xmax = self.playArea.right - imagesize[0] * 0.5 + self.ymax = self.playArea.bottom - imagesize[1] * 0.5 + self.ymin = self.playArea.top + imagesize[1] * 0.5 + #interface key codes + self.keycode = KEYCODES + #various states + self.keyActions = [] + self.selectedInstrument = self.TemplateInstruments[0] + self.occupiedInstruments = {self.selectedInstrument.name: None} + self.myself = None + self.sharer = False + self.connected = False + self.timeTally = [] + self.running = True + self.Vparam = "Pitch" + self.Hparam = "Density" + #interface controls + self.movingInstrument = False + #initial draw + panelColour = (0,0,0) + self.snap_store = (olpcgames.ACTIVITY.snap_store if platform == 'Sugar' else []) + self.feedbackgroundImage = None + if self.screenSize == (1200, 780): + self.setbackgroundImage(pygame.image.load(ImagePath + "/jam2jamXO_2.png").convert()) + else: + bgi = pygame.image.load(ImagePath + "/jam2jamXO_2.png").convert() + bgi_scaled = pygame.transform.scale(bgi, self.playArea.size) + self.setbackgroundImage(bgi_scaled) + self.panel = pygame.Surface((self.panelArea.width, self.panelArea.height)) + self.panel.fill(panelColour) + self.screen.blit(self.panel, self.panelArea) + pygame.display.flip() + for pnl in self.PanelInstruments: pnl.y = pnl.y() + self.playArea.height + def setbackgroundImage(self, img): + self.backgroundImage = img + self.screen.blit(self.backgroundImage, (0,0), self.playArea) + self.selectedInstrument.Touch = True + def updatePanel(self): + "redraw panel icons" + for pi in self.PanelInstruments: + if not pi.Touch: + pass + else: + if pi.name in self.occupiedInstruments: + pi.deactivate() + else: + pi.activate() + self.screen.blit(pi.image, pi.Rect) + pi.Touch = False + def runloop(self): + "main game loop" + clock = pygame.time.Clock() + imgcnt = 0 + self.music_player.playLoop(now()) + while self.running: + events = (pausescreen.get_events(sleep_timeout = 43200) if platform == 'Sugar' else pygame.event.get()) + for event in events: + self.eventAction(event) + for act in self.keyActions: + self.interfaceAction(act) + if self.feedbackgroundImage: + self.setbackgroundImage(self.feedbackgroundImage) + self.feedbackgroundImage = None + self.updateInspos() + self.updatePanel() + if platform == 'Sugar': currentcnt = len(self.snap_store) + else: currentcnt = 0 + if imgcnt == currentcnt: + pass + else: + self.music_player.picture_cycle = [self, True] + imgcnt = currentcnt + pygame.display.flip() + clock.tick(25) + def updateInspos(self): + "animate selected instrument." + ins = self.selectedInstrument + if ins.Touch: + xval = self.scene.Params.getValue(self.Hparam, ins.name) + yval = self.scene.Params.getValue(self.Vparam, ins.name) + xpos = rescale(xval, 0,1,self.xmin, self.xmax) + ypos = rescale(yval, 0,1,self.ymax, self.ymin) + self.screen.blit(self.backgroundImage, ins.Rect, ins.Rect) + ins.ctr = (xpos, ypos) + ins.Touch = False + self.screen.blit(ins.image, ins.Rect) + def sendSync(self): + "Tell audio loop to broadcast time and beat messages" + if self._syncloop_running: + self.music_player.sendSync = True + log.info("sent sync") + schedEvent(now() + 10.7, self.sendSync) + def setselectedInstrument(self, ins): + "select the instrument onscreen" + self.selectedInstrument = ins + if ins.name not in self.occupiedInstruments: + self.occupiedInstruments.update({ins.name:str(self.myself)}) + self.selectedInstrument.Touch = True + def eventAction(self, event): + "detect events, and select action" + if event.type == pygame.QUIT: + self.music_player.freeze() + self.running = False + elif event.type == pygame.USEREVENT: + if hasattr(event, "action"): + if event.action.startswith("Parameter"): + args = event.action.split('|') + if args[1] == "Horizontal": + self.Hparam = args[2] + self.selectedInstrument.Touch = True + elif args[1] == "Vertical": + self.Vparam = args[2] + self.selectedInstrument.Touch = True + else: + raise ValueError, 'Unknown Parameter Action %s' %args + elif event.action.startswith('Reload'): + #should look always like this: "Reload|name|key:mode|tempo|defaults" + args = event.action.split('|') + name = args[1] + key = ('E' if args[2] == 'None' else args[2]) + mode = ('minor' if args[3] == 'None' else args[3]) + tempo = (117 if args[4] == 'None' else int(args[4])) + d = eval(args[5]) + defaults = (d if d else {}) + self.load_scene(name, key, mode, tempo, defaults) #this call blocks + if self.pending_instrument_assignment: #now check if we are waiting to assign instruments and params. + self.receiveMessage("AuthorisedInstrument|%s|%s" %(self.pending_instrument_assignment[0], self.pending_instrument_assignment[1]), self.myself) + elif event.action.startswith("Shared"): + self.sharer = "Pending" + log.info("Sharing activity") + elif event.action.startswith("Joined"): + log.info("Joined Activity") + else: + log.debug("unknown parameter change: %s", event.action) + else: log.debug("ignoring USEREVENT %s", event) + elif event.type == pygame.MOUSEBUTTONDOWN: + x,y = event.pos + Ins = self.selectedInstrument + if Ins.Rect.collidepoint(x,y): + self.movingInstrument = Ins + else: + for Panndx in range(len(self.PanelInstruments)): + Pan = self.PanelInstruments[Panndx] + if Pan.Rect.collidepoint(x,y): + if Pan.active: self.requestInstrument(Pan.name) + break + elif event.type == pygame.MOUSEMOTION: + if self.movingInstrument: + insname = self.movingInstrument.name + self.scene.Params.setValue(self.Hparam, insname, rescale(event.pos[0], self.playArea.left, self.playArea.right, 0, 1)) + self.scene.Params.setValue(self.Vparam, insname, limit(rescale(event.pos[1], self.playArea.bottom, self.playArea.top, 0, 1), 0,1)) + self.movingInstrument.Touch = True + elif event.type == pygame.MOUSEBUTTONUP: + self.movingInstrument = False + elif platform == 'Sugar' and event.type == mesh.CONNECT: + log.info( """Connected to the mesh!| %s""", event ) + self.connected = True + self.music_player.resetBeat() + elif event.type == pygame.KEYDOWN: + try: + iaction = self.keycode[event.key] + self.keyActions.append(iaction) + except KeyError: + pass + elif event.type == pygame.KEYUP: + try: + self.keyActions.remove(self.keycode[event.key]) + except ValueError: pass + except KeyError: pass + elif self.connected and event.type == mesh.PARTICIPANT_ADD: + if not self.myself: self.myself = mesh.my_handle() + if event.handle == self.myself: + if self.sharer == "Pending": self.sharer = self.myself + elif len(self.occupiedInstruments) == 4: + pass + else: + if self.sharer == self.myself: + giveupInstrument = [p for p in self.PanelInstruments if p.active][0].name + giveupparameters = getInstrumentParameters(self.scene, giveupInstrument) + mesh.send_to(event.handle, "Welcome|%s|%s|%s" %(self.scene.scene_name, giveupInstrument, giveupparameters)) + self.stealInstrument(giveupInstrument, handle = event.handle) + if self.connected: mesh.broadcast('Occupied|%s' %self.occupiedInstruments) + olpcgames.ACTIVITY.J2JToolbar.deactivate_scene_change() + if len(self.occupiedInstruments) >= 2 and not self._syncloop_running: + self._syncloop_running = True + self.sendSync() + else: + self.latency_checker() + log.info("Waiting to be assigned instrument from sharer") + elif self.connected and event.type == mesh.PARTICIPANT_REMOVE: + "return instrument to the sharer if a jammer leaves." + try: + relname = [n for n in self.occupiedInstruments if self.occupiedInstruments[n] == str(event.handle)][0] + relpanel = [p for p in self.PanelInstruments if p.name == relname][0] + del self.occupiedInstruments[relname] + relpanel.Touch = True + if self.sharer == self.myself: + self.music_player.mutelist.remove(relname) + if len(self.occupiedInstruments) == 1: + olpcgames.ACTIVITY.J2JToolbar.reactivate_scene_change() + if len(self.occupiedInstruments) <= 1: + self._syncloop_running = False + except IndexError: log.debug("Index error while removing jammer %s occ = %s" %(str(event.handle), self.occupiedInstruments)) + except KeyError: pass + except ValueError: pass + if self.sharer == self.myself: mesh.broadcast('Occupied|%s' %self.occupiedInstruments) + log.info( """Removed jammer| %s""", event ) + elif self.connected and (event.type == mesh.MESSAGE_MULTI or event.type == mesh.MESSAGE_UNI): + if event.handle == self.myself: + pass + else: + self.receiveMessage(event.content, event.handle) + def interfaceAction(self, iaction): + if iaction.startswith("Nudge"): + direction = iaction.split("|")[1] + insname = self.selectedInstrument.name + if direction == "Left" or direction == "Right": + param = self.Hparam + else: + param = self.Vparam + currentValue = self.scene.Params.getValue(param, insname) + newvalue = limit((currentValue - 0.03 if direction == "Left" or direction == "Down" else currentValue + 0.03), 0, 1) + self.scene.Params.setValue(param, insname, newvalue) + self.selectedInstrument.Touch = True + if newvalue == 0 or newvalue == 1: + try: + self.keyActions.remove(iaction) + except ValueError: pass + elif iaction.startswith("Instrument"): + ins = iaction.split('|')[1] + self.requestInstrument(ins) + try: + self.keyActions.remove(iaction) + except ValueError: pass + elif iaction.startswith("Parameter"): + pm = iaction.split('|')[1] + if "Modifier|Shift" in self.keyActions: + print "shift key is on" + if olpcgames.ACTIVITY: + olpcgames.ACTIVITY.J2JToolbar.set_vertical_parameter(pm) + else: + self.Vparam = pm + else: + print "shift key is off" + if olpcgames.ACTIVITY: + olpcgames.ACTIVITY.J2JToolbar.set_horizontal_parameter(pm) + else: + self.Hparam = pm + try: + self.keyActions.remove(iaction) + except ValueError: pass + else: pass + def stealInstrument(self, stealname, releasename = False, handle = False): + "attempts to deactivate an instrument, and make it unavailable for selection" + if not handle: handle = self.myself + if stealname == self.selectedInstrument.name: + log.info("ignoring request to steal %s: already active" %stealname) + return False + elif stealname in self.occupiedInstruments and not releasename: + log.info ("ignoring request to steal %s: already occupied and no release instrument provided" %stealname) + return False + else: + paneli = [pnli for pnli in self.PanelInstruments if pnli.name == stealname][0] + self.occupiedInstruments.update({stealname:str(handle)}) + self.music_player.mutelist.append(stealname) + if releasename: + relname = releasename + relpanel = [p for p in self.PanelInstruments if p.name == relname][0] + try: + del self.occupiedInstruments[relname] + relpanel.Touch = True + self.music_player.mutelist.remove(relname) + except KeyError: pass + except ValueError: pass + paneli.Touch = True + return True + def requestInstrument(self, name): + "instrument selections should go through this first. To request an instrument, you need to give one up" + if name in self.occupiedInstruments: + log.info('failed instrument selection, as instrument currently occupied') + else: + if self.connected and (self.sharer != self.myself): + releasename = self.selectedInstrument.name + iparams = getInstrumentParameters(self.scene, releasename) + requestname = name + mesh.send_to(self.sharer, 'JammerRequest|%s|%s|%s' %(releasename, requestname, iparams)) + else: + self.reselectInstruments(name) + if self.connected: mesh.broadcast('Occupied|%s' %self.occupiedInstruments) + def receiveMessage(self, instruction, handle): + if instruction.startswith("Welcome"): + messages = instruction.split("|") + self.sharer = handle + jam_scene = messages[1] + self.pending_instrument_assignment = [messages[2],messages[3]] + self.select_activity_scene(jam_scene) + if self.sharer != self.myself: + olpcgames.ACTIVITY.J2JToolbar.deactivate_scene_change() + elif instruction.startswith("Beat"): + splitvals = instruction.split('|') + receivedBeat = int(splitvals[1]) + time_now = now() + self.beatEstimator.addBeat(receivedBeat, time_now) + if abs(receivedBeat - self.beatEstimator.beat_match(time_now)) > 0.17: + pass + else: + latency = (sum(self.latency) / len(self.latency)) + tmult = self.music_player.tempoMult + latency = latency * 0.25 + 0.04 #this might be XO 1.0 specific + beatadvance = int(ceil(latency * 1/tmult)) + scheduled_time = now() + ((beatadvance * tmult) - latency) + self.music_player.Cease() + self.music_player.playLoop(scheduled_time, (receivedBeat + beatadvance) % self.music_player.beatlimit) + elif instruction.startswith("JammerRequest"): + "In theory only the sharer ever gets this message" + split = instruction.split('|') + releasename = split[1] + requestname = split[2] + iparams = eval(split[3]) + stealresult = self.stealInstrument(requestname, releasename, handle) + if stealresult: + setInstrumentParameters(self.scene, releasename, iparams) + rqparams = getInstrumentParameters(self.scene, requestname) + mesh.send_to(handle, "AuthorisedInstrument|%s|%s" %(requestname, rqparams)) + mesh.broadcast('Occupied|%s' %self.occupiedInstruments) + else: + mesh.send_to(handle, "DeniedInstrument|%s") + elif instruction.startswith("AuthorisedInstrument"): + "In theory only a 'joiner' receives this message" + msg = instruction.split('|') + ai = msg[1] + params = eval(msg[2]) + setInstrumentParameters(self.scene, ai, params) + self.reselectInstruments(ai) + elif instruction.startswith("DeniedInstrument"): + di = instruction.split('|')[1] + log.info("Instrument request for %s was denied by sharer." %di) + elif instruction.startswith("Occupied"): + insdict = eval(instruction.split('|')[1]) + self.occupiedInstruments = insdict + for pni in self.PanelInstruments: + if pni.name in insdict: + pni.deactivate() + else: + pni.activate() + elif instruction.startswith("LateReq"): + id = instruction.split('|')[1] + mesh.send_to(handle, "LateResp|%s" %id) + elif instruction.startswith("LateResp"): + id = int(instruction.split('|')[1]) + try: + t = self.latency_time_ID[id] + del self.latency_time_ID[id] + result = (now() - t) / 2 + avglat = sum(self.latency) / len(self.latency) + diffs = [(val - avglat) ** 2 for val in self.latency] + stddev = sqrt(sum(diffs) / len(diffs)) + if id == 0: + del self.latency[0] + self.latency.append(result) + elif result > (avglat + stddev): + pass + elif result < (avglat - stddev) and len(self.latency) > 6: + pass + elif len(self.latency) > 12: + del self.latency[0] + self.latency.append(result) + else: + self.latency.append(result) + except KeyError: + log.info('Unmatched time ID %s' %id) + else: + log.debug("UNKNOWN INSTRUCTION RECEIVED :%s", instruction) + def reselectInstruments(self, name): + "Swaps the instrument on screen and selects the active Panel Instruments available to this user" + oldInstrument = self.selectedInstrument + oldname = oldInstrument.name + if (self.sharer == self.myself) or not self.connected: + del self.occupiedInstruments[oldname] + self.occupiedInstruments.update({name:str(self.myself)}) + self.screen.blit(self.backgroundImage, oldInstrument.Rect, oldInstrument.Rect) + oldInstrument.Touch = False + self.setselectedInstrument([i for i in self.TemplateInstruments if i.name == name][0]) + for w in self.PanelInstruments: + if w.name == name or w.name == oldname: + w.Touch = True + if self.connected: + if self.sharer == self.myself: + self.music_player.mutelist = [j for j in self.occupiedInstruments if j != self.selectedInstrument.name] + else: + self.music_player.mutelist = [k.name for k in self.TemplateInstruments if k.name != self.selectedInstrument.name] + def load_scene(self, name, key, mode, tempo, defaults = {}): + self.music_player.freeze() + self.music_player.cs.perf.Stop() + self.music_player.cs.perf.Join() + self.music_player.cs.csound.cleanup() + sc = City.ScenePlayer(name, key, mode, tempo, defaults) + mp = City.makePlayer(sc) + global schedEvent, now + schedEvent = sc.TimeQueue.schedEvent + now = sc.cs.perfTime + self.scene = sc + self.music_player = mp + self.music_player.playLoop(now()) + self.selectedInstrument.Touch = True + self.music_player.picture_cycle = [self, True] + + def select_activity_scene(self, scene_name, key = None, mode = None, tempo = None, defaults = None): + current_scene_name = self.scene.scene_name + if scene_name == current_scene_name: + if self.pending_instrument_assignment: + self.receiveMessage("AuthorisedInstrument|%s|%s" %(self.pending_instrument_assignment[0], self.pending_instrument_assignment[1]), self.myself) + elif not olpcgames.ACTIVITY: + k = (key if key else 'G') + m = (mode if mode else 'major') + t = (int(tempo) if tempo else 164) + d = (eval(defaults) if defaults else {}) + self.load_scene(scene_name, k,m,t,d) + else: + toolbar = olpcgames.ACTIVITY.J2JToolbar + try: + ndx = [n[0] for n in toolbar.scenes].index(scene_name) + toolbar._Scene_combo.combo.set_active(ndx) + except ValueError: + log.info('request to change to unknown scene: %s', scene_name) + def latency_checker(self): + if self.sharer: + self.latency_time_ID[self.latency_counter] = now() + mesh.send_to(self.sharer, "LateReq|%s" %self.latency_counter) + self.latency_counter += 1 + if self.latency_counter < 10: + Timer(3.5, self.latency_checker, ()).start() + elif self.latency_counter < 50: + Timer(6.75, self.latency_checker, ()).start() + else: + log.info('turning off latency checking') + +#pygame main loop +def main(): + # check automatic power management + try: + sugar_pm_check = subprocess.Popen("sugar-control-panel -g automatic_pm", shell=True, stdout=subprocess.PIPE) + sugar_pm_result = sugar_pm_check.communicate()[0] + if sugar_pm_result.startswith("on"): + subprocess.Popen("sugar-control-panel -s automatic_pm off", shell=True) + _spm_off = True + else: + _spm_off = False + except OSError: + _spm_off = False + log.info("Failed to detect and set automatic power management") + screenSize_X, screenSize_Y = (olpcgames.ACTIVITY.game_size if platform=="Sugar" else (1024,640)) + toolbarheight = 45 + screen = pygame.display.set_mode((screenSize_X, screenSize_Y - toolbarheight)) + a_,b_,c_,d_ = pygame.cursors.load_xbm("arrow40b.xbm", "arrow40b-mask.xbm") + pygame.mouse.set_cursor(a_,b_,c_,d_) + jam = jamScene(screen, tempo = 120) + jam.runloop() + pygame.quit() + jam.music_player.freeze() + jam.music_player.cs.perf.Stop() + jam.music_player.cs.csound.cleanup() + if _spm_off: subprocess.Popen("sugar-control-panel -s automatic_pm on", shell=True) + +if __name__ == '__main__': + logging.basicConfig() + print "running as main" + main() + -- cgit v0.9.1