From 55755a29f64dba0168f1ce2900562c8468662cbc Mon Sep 17 00:00:00 2001 From: Thorin Date: Mon, 11 Jun 2012 14:35:03 +0000 Subject: import tool combobox --- diff --git a/J2JToolbar.py b/J2JToolbar.py index 2e66eaa..54b9b22 100755 --- a/J2JToolbar.py +++ b/J2JToolbar.py @@ -21,7 +21,7 @@ import pygame import olpcgames from gettext import gettext as _ from sugar.graphics.toolbutton import ToolButton -from sugar.graphics.toolcombobox import ToolComboBox + import logging log = logging.getLogger('City run') @@ -31,6 +31,12 @@ ImagePath = os.path.dirname(os.path.abspath(__file__)) + "/City/Images" GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] + + +from sugar.graphics.toolcombobox import ToolComboBox + + + class readScenes(object): def __init__(self, scpath): self.scpath = scpath diff --git a/J2JToolbar.py~ b/J2JToolbar.py~ new file mode 100755 index 0000000..2e66eaa --- /dev/null +++ b/J2JToolbar.py~ @@ -0,0 +1,319 @@ +#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 gtk, gst, thread, tempfile, time, os +import pygame +import olpcgames +from gettext import gettext as _ +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toolcombobox import ToolComboBox +import logging + +log = logging.getLogger('City run') +log.setLevel(logging.DEBUG) + +ImagePath = os.path.dirname(os.path.abspath(__file__)) + "/City/Images" + +GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] + +class readScenes(object): + def __init__(self, scpath): + self.scpath = scpath + self.scene_names = [dir for dir in os.listdir(self.scpath) if os.path.isdir(self.scpath + '/' + dir)] + self.scene_data = [] + for n in self.scene_names: + fp = self.scpath + "/" + n + mdfile = [open(fp + '/' + f) for f in os.listdir(fp) if os.path.isfile(fp + "/" + f) and f.startswith(n)] + if mdfile: + result = {"Name":n} + defaults = {} + for line in mdfile[0]: + if line.startswith('#') or line.startswith('\n'): + pass + else: + keyvals = line.split('=') + if len(keyvals) == 2: + key = keyvals[0].upper() + val = (keyvals[1][:-1] if keyvals[1][-1] == '\n' else keyvals[1]) + if key.startswith('TEMPO'): + result['Tempo'] = val.replace(' ','') + elif key.startswith('KEY'): + result['Key'] = val.replace(' ','') + elif key.startswith('MODE'): + result['Mode'] = val.replace(' ','') + else: + pass + else: + raise IOError, "Bad Scene Meta Data file: %s" %keyvals + result['Defaults'] = {} + self.scene_data.append(result) + else: + raise IOError, "Can't find Meta Data file in %s Scene" %n + def scene_instruct(self, name): + "returns a list of strings suitable to give to a ScenePlayer object for creating a scene" + for scd in self.scene_data: + if scd['Name'] == name: + collected = [name] + for k in ['Key', 'Mode', 'Tempo', 'Defaults']: + try: + collected.append(str(scd[k])) + except KeyError: + collected.append('None') + return collected + def get_scene_list(self): + "returns a list of scene strings for the toolbar, with City as the default" + ordered_names = self.scene_names[:] + if 'City' in ordered_names: + ordered_names.insert(0,ordered_names.pop(ordered_names.index('City'))) + return [self.scene_instruct(s) for s in ordered_names] + +class CameraSnap(object): + """A class representing the OLPC camera.""" + def __init__(self): + log.info("CameraSnap init") + snap_file, self.snap_path = tempfile.mkstemp(suffix = '.png') + pipe = GST_PIPE + ['filesink location=%s' % self.snap_path] + self.pipe = gst.parse_launch('!'.join(pipe)) + self.bus = self.pipe.get_bus() + log.info("tempfile is %s " %self.snap_path) + def Snap(self): + """Take a snapshot.""" + log.info("about to set pipe state to PLAY") + self.pipe.set_state(gst.STATE_PLAYING) + log.info("about to poll") + thread.start_new_thread(self.bus.poll, (gst.MESSAGE_EOS, -1)) + for i in xrange(60): + time.sleep(0.18) + if os.path.getsize(self.snap_path) > 0: break + else: raise IOError, "Error writing camera snap to file" + return self.snap_path + def Stop(self): + self.pipe.set_state(gst.STATE_NULL) + + +class Jam2JamToolBar(gtk.Toolbar): + def __init__(self, activity): + gtk.Toolbar.__init__(self) + self.activity = activity + self.parameters = ['Density', 'Pitch', 'Length', 'Timbre', 'Volume'] # no tempo here. + scene_stuff = readScenes(self.activity._ScenePath) + self.scenes = scene_stuff.get_scene_list() + print "SCENE DATA IS ", self.scenes + #self.scenes = [['City', 'A', 'minor pentatonic'], ['City', 'G#', 'major']] #this data needs to be obtained from directories + self.play_pause_state = 'Playing' + self.scene_init = True + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + self.insert(separator, -1) + + #Horizontal Parameter control combobox + self._add_widget(gtk.Label(_('Horizontal:'))) + self._Hparameter_combo = ToolComboBox() + for i, f in enumerate(self.parameters): + self._Hparameter_combo.combo.append_item(i, f) + self._Hparameter_combo.combo.connect('changed', self._Hparameter_change_cb) + self._add_widget(self._Hparameter_combo) + self._Hparameter_combo.combo.set_active(0) + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + separator.show() + self.insert(separator, -1) + + #Vertical Parameter control combobox + self._add_widget(gtk.Label(_('Vertical:'))) + self._Vparameter_combo = ToolComboBox() + for j, k in enumerate(self.parameters): + self._Vparameter_combo.combo.append_item(j, k) + self._Vparameter_combo.combo.connect('changed', self._Vparameter_change_cb) + self._add_widget(self._Vparameter_combo) + self._Vparameter_combo.combo.set_active(1) + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + separator.show() + self.insert(separator, -1) + + + #Scene Selection control combobox + self._add_widget(gtk.Label(_('Scene:'))) + self._Scene_combo = ToolComboBox() + for l, m in enumerate(self.scenes): + self._Scene_combo.combo.append_item(l, m[0]) + self._Scene_combo.combo.connect('changed', self._Scene_change_cb) + self._add_widget(self._Scene_combo) + #ought to do this safely somehow. + self._Scene_combo.combo.set_active(0) + self.scene_init = False + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + separator.show() + self.insert(separator, -1) + + + #Camera Button + self.camera_ready = True + camera_icon = ImagePath + "/camera-external.svg" + camera_busy_icon = ImagePath + "/camera-busy.svg" + self.camera_image, self.camera_busy_image = gtk.Image(), gtk.Image() + self.camera_image.set_from_file(camera_icon) + self.camera_busy_image.set_from_file(camera_busy_icon) + self.camera_image.show() + #camera_busy_image.show() + self._cameraButton = ToolButton() + self._cameraButton.set_icon_widget(self.camera_image) + self._cameraButton.connect('clicked', self._cameraSnap_cb) + self._cameraButton.set_tooltip(_('Snapshot')) + self.insert(self._cameraButton, -1) + self._cameraButton.show() + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + separator.show() + self.insert(separator, -1) + + #Play/Pause Button + pause_icon = ImagePath + "/media-playback-pause.svg" + play_icon = ImagePath + "/media-playback-start.svg" + self.pause_image = gtk.Image() + self.pause_image.set_from_file(pause_icon) + + self.play_image = gtk.Image() + self.play_image.set_from_file(play_icon) + + self._pauseButton = ToolButton() + self._pauseButton.connect('clicked', self._pause_cb) + self.pause_image.show() + self._pauseButton.set_icon_widget(self.pause_image) + self._pauseButton.set_tooltip(_('Pause')) + #self._toggleplay_pause() + self.insert(self._pauseButton, -1) + self._pauseButton.show() + + # Separator + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + separator.show() + self.insert(separator, -1) + + + def _add_widget(self, widget, expand=False): + tool_item = gtk.ToolItem() + tool_item.set_expand(expand) + tool_item.add(widget) + widget.show() + self.insert(tool_item, -1) + tool_item.show() + + def _toggleplay_pause(self): + if self.play_pause_state == "Playing": + self.activity.jamScene.music_player.pause() + self.play_image.show() + self._pauseButton.set_icon_widget(self.play_image) + self._pauseButton.set_tooltip(_('Play')) + self.play_pause_state = "Paused" + else: + self.activity.jamScene.music_player.resume() + self.pause_image.show() + self._pauseButton.set_icon_widget(self.pause_image) + self._pauseButton.set_tooltip(_('Pause')) + self.play_pause_state = "Playing" + try: + self.activity._pgc.grab_focus() + except AttributeError: + pass + + def _show_busy_camera(self): + self.camera_ready = False + self.camera_busy_image.show() + self._cameraButton.set_icon_widget(self.camera_busy_image) + self._cameraButton.set_tooltip(_('Please wait...')) + + def _show_active_camera(self): + self.camera_image.show() + self._cameraButton.set_icon_widget(self.camera_image) + self._cameraButton.set_tooltip(_('Snap')) + self.camera_ready = True + + def _Hparameter_change_cb(self, widget): + param = "Parameter|Horizontal|" + self.parameters[self._Hparameter_combo.combo.get_active()] + olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=param)) + try: + self.activity._pgc.grab_focus() + except AttributeError: + pass + + def _Vparameter_change_cb(self, widget): + param = "Parameter|Vertical|" + self.parameters[self._Vparameter_combo.combo.get_active()] + olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=param)) + try: + self.activity._pgc.grab_focus() + except AttributeError: + pass + + def _Scene_change_cb(self, widget): + if self.scene_init: + pass + else: + selection = self.scenes[self._Scene_combo.combo.get_active()] + scene = "Reload|" + '|'.join(map(lambda x: str(x), selection)) + olpcgames.eventwrap.post(olpcgames.eventwrap.Event(pygame.USEREVENT, action=scene)) + try: + self.activity._pgc.grab_focus() + except AttributeError: + pass + + ### functions to assist calls from pygame + def deactivate_scene_change(self): + self._Scene_combo.set_sensitive(False) + def reactivate_scene_change(self): + self._Scene_combo.set_sensitive(True) + def set_horizontal_parameter(self, param): + ndx = self.parameters.index(param) + self._Hparameter_combo.combo.set_active(ndx) + def set_vertical_parameter(self, param): + ndx = self.parameters.index(param) + self._Vparameter_combo.combo.set_active(ndx) + + def _cameraSnap_cb(self, widget): + "Here I could wrap a camera event..." + def snaptime(): + snap = CameraSnap() + self.activity.cameras_loaded.append(snap) + picpath = snap.Snap() + self.activity.load_image(picpath) + snap.Stop() + self._show_active_camera() + self.activity._pgc.grab_focus() + if self.camera_ready: + self._show_busy_camera() + thread.start_new_thread(snaptime, ()) + else: + log.info('Ignoring request to use camera, as camera is currently busy') + + def _pause_cb(self, widget): + self._toggleplay_pause() + log.info("Play/Pause Button pressed") + 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