summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Michel Trivi <>2009-04-03 18:25:54 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-04-03 18:25:54 -0700
commit6b065d0f7161fe54e6f58fd2b8ad6c650b2d3657 (patch)
tree743a03c5e06fab24cb940fdaf236875f73453501
parent9bdfa57352c68c2060fb37fcc3aa5b6d20e0dec1 (diff)
downloadandroid_external_sonivox-6b065d0f7161fe54e6f58fd2b8ad6c650b2d3657.tar.gz
android_external_sonivox-6b065d0f7161fe54e6f58fd2b8ad6c650b2d3657.tar.bz2
android_external_sonivox-6b065d0f7161fe54e6f58fd2b8ad6c650b2d3657.zip
AI 144576: am: CL 144575 am: CL 144573 Checking in Sonivox' JetCreator code.
Original author: jmtrivi Merged from: //branches/cupcake/... Original author: android-build Automated import of CL 144576
-rwxr-xr-xjet_tools/JetCreator/JetAudition.py509
-rwxr-xr-xjet_tools/JetCreator/JetCreator.py1431
-rwxr-xr-xjet_tools/JetCreator/JetCreatorhlp.dat119
-rwxr-xr-xjet_tools/JetCreator/JetCtrls.py567
-rwxr-xr-xjet_tools/JetCreator/JetDebug.py71
-rwxr-xr-xjet_tools/JetCreator/JetDefs.py560
-rwxr-xr-xjet_tools/JetCreator/JetDialogs.py1031
-rwxr-xr-xjet_tools/JetCreator/JetFile.py775
-rwxr-xr-xjet_tools/JetCreator/JetHelp.py33
-rwxr-xr-xjet_tools/JetCreator/JetPreview.py200
-rwxr-xr-xjet_tools/JetCreator/JetSegGraph.py361
-rwxr-xr-xjet_tools/JetCreator/JetStatusEvent.py38
-rwxr-xr-xjet_tools/JetCreator/JetSystemInfo.py40
-rwxr-xr-xjet_tools/JetCreator/JetUtils.py788
-rwxr-xr-xjet_tools/JetCreator/ReadMe1st.txt44
-rwxr-xr-xjet_tools/JetCreator/eas.py1229
-rwxr-xr-xjet_tools/JetCreator/img_Copy.py51
-rwxr-xr-xjet_tools/JetCreator/img_Cut.py104
-rwxr-xr-xjet_tools/JetCreator/img_Find.py85
-rwxr-xr-xjet_tools/JetCreator/img_New.py61
-rwxr-xr-xjet_tools/JetCreator/img_Open.py79
-rwxr-xr-xjet_tools/JetCreator/img_Paste.py41
-rwxr-xr-xjet_tools/JetCreator/img_Print.py62
-rwxr-xr-xjet_tools/JetCreator/img_Redo.py79
-rwxr-xr-xjet_tools/JetCreator/img_Save.py99
-rwxr-xr-xjet_tools/JetCreator/img_Undo.py81
-rwxr-xr-xjet_tools/JetCreator/img_favicon.py61
-rwxr-xr-xjet_tools/JetCreator/img_splash.py1157
-rwxr-xr-xjet_tools/JetCreator/midifile.py1579
29 files changed, 11335 insertions, 0 deletions
diff --git a/jet_tools/JetCreator/JetAudition.py b/jet_tools/JetCreator/JetAudition.py
new file mode 100755
index 0000000..bc0d4de
--- /dev/null
+++ b/jet_tools/JetCreator/JetAudition.py
@@ -0,0 +1,509 @@
+"""
+ File:
+ JetAudition.py
+
+ Contents and purpose:
+ Auditions a jet file to simulate interactive music functions
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from __future__ import with_statement
+
+import wx
+import sys
+import thread
+import time
+
+from JetUtils import *
+from JetDefs import *
+from JetCtrls import JetListCtrl, JetTrackCtrl
+from JetSegGraph import SegmentGraph, Marker
+from eas import *
+from JetStatusEvent import *
+
+CMD_QUEUE_AND_CANCEL = 'QueueNCancel'
+CMD_QUEUE_AND_CANCEL_CURRENT = 'QueueCancelCurrent'
+CMD_MUTEALL = 'MuteAll'
+CMD_UNMUTEALL = 'UnMuteAll'
+CMD_ORIGINALMUTES = 'MuteOrg'
+CMD_STOP = 'Stop'
+CMD_PAUSE = 'Pause'
+CMD_PLAY = 'Play'
+
+STATUS_PENDING = 'Pending'
+STATUS_PLAYING = 'Playing'
+STATUS_COMPLETE = 'Complete'
+STATUS_CANCELED = 'Canceled'
+STATUS_QUEUED = 'Queued'
+
+LOAD_QUEUE_DISPLAY = 'LOAD_QUEUE'
+GRAPH_POSITION_UPDATE = 'GRAPH_POS'
+NEW_SEGMENT_DISPLAY = 'NEW SEG'
+CLR_INFO = 'CLR_INFO'
+
+class Audition(wx.Dialog):
+ """ Initializes Audition window controls, then spawns off a thread to be ready for playback commands """
+ def __init__(self, jet_file, pSize):
+ wx.Dialog.__init__(self, None, -1, title=JetDefs.DLG_AUDITION)
+
+ self.jet = None
+ self.playerLock = threading.RLock()
+ self.jet_file = jet_file
+ self.queueSegs = []
+ self.keepPlaying = True
+ self.nextSegNum = 0
+ self.currentSegmentIndex = None
+ self.currentSegmentName = ""
+ self.playCommand = ""
+ self.threadShutdown = True
+
+ panel = wx.Panel(self, -1)
+
+ self.segList = JetListCtrl(panel)
+ self.segList.AddCol(JetDefs.GRD_SEGMENTS, 180)
+ self.segList.AddCol(JetDefs.GRD_LENGTH, 20)
+
+ self.segList.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnQueueSegment)
+ self.segList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSegListClick)
+
+ self.queueList = JetListCtrl(panel)
+ self.queueList.AddCol(JetDefs.GRD_QUEUE, 180)
+ self.queueList.AddCol(JetDefs.GRD_STATUS, 20)
+
+ self.trackList = JetTrackCtrl(panel)
+ self.trackList.AddCol(JetDefs.GRD_TRACK, JetDefs.MUTEGRD_TRACK)
+ self.trackList.AddCol(JetDefs.GRD_CHANNEL, JetDefs.MUTEGRD_CHANNEL)
+ self.trackList.AddCol(JetDefs.GRD_NAME, JetDefs.MUTEGRD_NAME)
+ self.trackList.BindCheckBox(self.OnTrackChecked)
+
+ self.btnMuteAll = wx.Button(panel, -1, JetDefs.BUT_MUTEALL)
+ self.btnUnMuteAll = wx.Button(panel, -1, JetDefs.BUT_MUTENONE)
+ self.btnMuteOrg = wx.Button(panel, -1, JetDefs.BUT_ORGMUTES)
+ hMuteButs = wx.BoxSizer(wx.HORIZONTAL)
+ hMuteButs.Add(self.btnMuteAll, 1, wx.EXPAND)
+ hMuteButs.Add(self.btnUnMuteAll, 1, wx.EXPAND)
+ hMuteButs.Add(self.btnMuteOrg, 1, wx.EXPAND)
+ vMuteButs = wx.BoxSizer(wx.VERTICAL)
+ vMuteButs.Add(self.trackList, 1, wx.EXPAND)
+ vMuteButs.Add((-1, 5))
+ vMuteButs.Add(hMuteButs, 0, wx.EXPAND)
+
+ self.btnQueue = wx.Button(panel, -1, JetDefs.BUT_QUEUE)
+ self.btnCancelNQueue = wx.Button(panel, -1, JetDefs.BUT_CANCELANDQUEUE)
+ hSegButs = wx.BoxSizer(wx.HORIZONTAL)
+ hSegButs.Add(self.btnQueue, 1, wx.EXPAND)
+ hSegButs.Add(self.btnCancelNQueue, 1, wx.EXPAND)
+ vSegButs = wx.BoxSizer(wx.VERTICAL)
+ vSegButs.Add(self.segList, 1, wx.EXPAND)
+ vSegButs.Add((-1, 5))
+ vSegButs.Add(hSegButs, 0, wx.EXPAND)
+
+ self.btnQueueCancelCurrent = wx.Button(panel, -1, JetDefs.BUT_CANCELCURRENT)
+ self.btnPause = wx.Button(panel, -1, JetDefs.BUT_PAUSE)
+ self.btnStop = wx.Button(panel, -1, JetDefs.BUT_STOP)
+ hQueueButs = wx.BoxSizer(wx.HORIZONTAL)
+ hQueueButs.Add(self.btnQueueCancelCurrent, 1, wx.EXPAND)
+ hQueueButs.Add(self.btnPause, 1, wx.EXPAND)
+ hQueueButs.Add(self.btnStop, 1, wx.EXPAND)
+ vQueueButs = wx.BoxSizer(wx.VERTICAL)
+ vQueueButs.Add(self.queueList, 1, wx.EXPAND)
+ vQueueButs.Add((-1, 5))
+ vQueueButs.Add(hQueueButs, 0, wx.EXPAND)
+
+ self.Bind(wx.EVT_BUTTON, self.OnQueueSegmentViaBut, id=self.btnQueue.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnCancelNQueue, id=self.btnCancelNQueue.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnStop, id=self.btnStop.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnQueueCancelCurrent, id=self.btnQueueCancelCurrent.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnPause, id=self.btnPause.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnMuteAll, id=self.btnMuteAll.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnUnMuteAll, id=self.btnUnMuteAll.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnMuteOrg, id=self.btnMuteOrg.GetId())
+
+ EVT_JET_STATUS(self, self.OnJetStatusUpdate)
+
+ BORDER = 10
+ hboxTop = wx.BoxSizer(wx.HORIZONTAL)
+ hboxTop.Add(vSegButs, 1, wx.EXPAND)
+ hboxTop.Add((5, -1))
+ hboxTop.Add(vQueueButs, 1, wx.EXPAND)
+ hboxTop.Add((5, -1))
+ hboxTop.Add(vMuteButs, 1, wx.EXPAND)
+
+ self.log = wx.TextCtrl(panel, -1)
+ self.graph = SegmentGraph(panel, size=(-1, 50))
+ self.graph.ClickCallbackFct = self.GraphTriggerClip
+
+ vboxBot = wx.BoxSizer(wx.VERTICAL)
+ vboxBot.Add(self.log, 0, wx.EXPAND)
+ vboxBot.Add((-1, 5))
+ vboxBot.Add(self.graph, 1, wx.EXPAND)
+
+ hboxMain = wx.BoxSizer(wx.VERTICAL)
+ hboxMain.Add(hboxTop, 2, wx.EXPAND | wx.ALL, BORDER)
+ hboxMain.Add(vboxBot, 1, wx.EXPAND | wx.ALL, BORDER)
+
+ panel.SetSizer(hboxMain)
+
+ self.LoadSegList()
+ self.initHelp()
+
+ self.SetSize(pSize)
+ self.CenterOnParent()
+
+ wx.EVT_CLOSE(self, self.OnClose)
+
+ thread.start_new_thread(self.PlaySegs, ())
+
+ def initHelp(self):
+ """ Initializes context sensitive help text """
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP )
+ self.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, ''))
+ self.segList.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, JetDefs.AUDITION_SEGLIST))
+ self.queueList.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, JetDefs.AUDITION_QUEUELIST))
+ self.trackList.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, JetDefs.AUDITION_TRACKLIST))
+ self.graph.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, JetDefs.AUDITION_GRAPH))
+
+ def OnMuteAll(self, event):
+ """ Sets command to mute all tracks """
+ self.SetPlayCommand(CMD_MUTEALL)
+
+ def OnUnMuteAll(self, event):
+ """ Sets command to un-mute all tracks """
+ self.SetPlayCommand(CMD_UNMUTEALL)
+
+ def OnMuteOrg(self, event):
+ """ Sets command to set mute flags to their original values """
+ self.SetPlayCommand(CMD_ORIGINALMUTES)
+
+ def OnTrackChecked(self, index, checked):
+ """ Mutes or un-mutes a track interactively """
+ with self.playerLock:
+ trackNum = self.trackList.GetTrackNumber(index)
+ self.SetMuteFlag(trackNum, checked)
+
+ def SetMuteFlag(self, trackNum, mute):
+ """ Mutes or un-mutes a track """
+ with self.playerLock:
+ try:
+ sync = JetDefs.DEFAULT_MUTE_SYNC
+ self.jet.SetMuteFlag(trackNum, mute, sync)
+ logging.info("SetMuteFlag() Track:%d Mute:%d Sync:%d" % (trackNum, mute, sync))
+ return True
+ except:
+ return False
+
+ def LoadSegList(self):
+ """ Loads the list of segments """
+ with self.playerLock:
+ self.segList.DeleteAllItems()
+ for segment in self.jet_file.GetSegments():
+ info = MidiSegInfo(segment)
+ index = self.segList.InsertStringItem(sys.maxint, StrNoneChk(segment.segname))
+ self.segList.SetStringItem(index, 1, TimeStr(info.iLengthInMs))
+
+ def GraphTriggerClip(self, sClipName, iEventId):
+ """ Triggers a clip """
+ with self.playerLock:
+ try:
+ self.jet.TriggerClip(iEventId)
+ self.log.SetValue(JetDefs.PLAY_TRIGGERCLIP_MSG % (iEventId, sClipName))
+ return True
+ except:
+ return False
+
+ def OnSegListClick(self, event):
+ """ Sets current segment name based on what's clicked """
+ with self.playerLock:
+ self.currentSegmentIndex = event.m_itemIndex
+ self.currentSegmentName = getColumnText(self.segList, event.m_itemIndex, 0)
+
+ def OnCancelNQueue(self, event):
+ """ Sets command to cancel the currently playing segment and queues another """
+ if self.currentSegmentIndex == None:
+ return
+ self.SetPlayCommand(CMD_QUEUE_AND_CANCEL)
+
+ def OnPause(self, event):
+ """ Sets a command to pause playback """
+ if self.currentSegmentIndex == None:
+ return
+ self.SetPlayCommand(CMD_PAUSE)
+
+ def OnStop(self, event):
+ """ Sets a command to stop playback """
+ if self.currentSegmentIndex == None:
+ return
+ self.SetPlayCommand(CMD_STOP)
+
+ def OnQueueCancelCurrent(self, event):
+ """ Sets a command to cancel the currently playing segment """
+ if self.currentSegmentIndex == None:
+ return
+ self.SetPlayCommand(CMD_QUEUE_AND_CANCEL_CURRENT)
+
+ def OnQueueSegmentViaBut(self, event):
+ """ Queues a segment via the button """
+ if self.currentSegmentIndex == None:
+ return
+ with self.playerLock:
+ segNum = self.currentSegmentIndex
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ self.QueueOneSegment(segment, segNum)
+
+ def OnQueueSegment(self, event):
+ """ Queues a segment """
+ with self.playerLock:
+ segNum = event.m_itemIndex
+ segment = self.jet_file.GetSegment(getColumnText(self.segList, segNum, 0))
+ self.QueueOneSegment(segment, segNum)
+
+ def QueueOneSegment(self, segment, segNum):
+ """ Queues one segment """
+ with self.playerLock:
+ userID = len(self.queueSegs)
+ if FileExists(segment.dlsfile):
+ dls_num = FindDlsNum(self.jet_file.libraries, segment.dlsfile)
+ else:
+ dls_num = -1
+ self.queueSegs.append(QueueSeg(segment.segname, userID, segNum, dls_num, segment.repeat, segment.transpose, segment.mute_flags, STATUS_PENDING))
+ self.LoadQueueDisplay()
+
+ def SetKeepPlayingFlag(self, val):
+ """ Sets a flag to continue play loop or shut down """
+ with self.playerLock:
+ self.keepPlaying = val
+
+ def GetKeepPlayingFlag(self):
+ """ Gets the play flag """
+ with self.playerLock:
+ return self.keepPlaying
+
+ def SetThreadShutdownFlag(self, val):
+ """ Set a flag to shutdown thread """
+ with self.playerLock:
+ self.threadShutdown = val
+
+ def GetThreadShutdownFlag(self):
+ """ Gets the thread shutdown flag """
+ with self.playerLock:
+ return self.threadShutdown
+
+ def SetPlayCommand(self, cmd):
+ """ Sets a play command """
+ with self.playerLock:
+ self.playCommand = cmd
+
+ def GetPlayCommand(self):
+ """ Gets a play command """
+ with self.playerLock:
+ return self.playCommand
+
+ def SetStatus(self, index, status):
+ """ Sets the status of a segment """
+ with self.playerLock:
+ self.queueSegs[index].status = status
+
+ def GetStatus(self, index):
+ """ Gets the status of a segment """
+ with self.playerLock:
+ return self.queueSegs[index].status
+
+ def LoadQueueDisplay(self):
+ """ Loads up the displayed queue list """
+ with self.playerLock:
+ self.queueList.DeleteAllItems()
+ for item in self.queueSegs:
+ index = self.queueList.InsertStringItem(sys.maxint, item.name)
+ self.queueList.SetStringItem(index, 1, item.status)
+
+ def NextSegment(self):
+ """ Gets the next segment in the queueu """
+ with self.playerLock:
+ num = len(self.queueSegs)
+ for i in range(num):
+ if self.queueSegs[i].status == STATUS_PENDING:
+ return i
+ return -1
+
+ def PlaySegs(self):
+ """ Sets up a loop looking for jet file actions based on UI commands """
+ self.jet = JET()
+ self.jet.eas.StartWave()
+ self.jet.OpenFile(self.jet_file.config.filename)
+
+ self.SetKeepPlayingFlag(True)
+ while self.GetKeepPlayingFlag():
+ self.SetThreadShutdownFlag(False)
+
+ time.sleep(.5)
+ index = self.NextSegment()
+ if index != -1:
+ lastID = -1
+
+ Queue(self.jet, self.queueSegs[index])
+
+ self.SetStatus(index, STATUS_QUEUED)
+
+ wx.PostEvent(self, JetStatusEvent(LOAD_QUEUE_DISPLAY, None))
+
+ self.jet.Play()
+ self.paused = False
+ wx.PostEvent(self, JetStatusEvent(CMD_PLAY, None))
+
+ while self.GetKeepPlayingFlag():
+ self.jet.Render()
+ status = self.jet.Status()
+
+ if status.currentUserID <> lastID and status.currentUserID <> -1:
+ wx.PostEvent(self, JetStatusEvent(NEW_SEGMENT_DISPLAY, status.currentUserID))
+ if lastID != -1:
+ self.SetStatus(lastID, STATUS_COMPLETE)
+ self.SetStatus(status.currentUserID, STATUS_PLAYING)
+ lastID = status.currentUserID
+ wx.PostEvent(self, JetStatusEvent(LOAD_QUEUE_DISPLAY, None))
+
+ if status.numQueuedSegments == 0:
+ break
+
+ self.jet.GetAppEvent()
+
+ index = self.NextSegment()
+ if (index >= 0) and (status.numQueuedSegments < 2):
+ Queue(self.jet, self.queueSegs[index])
+ self.SetStatus(index, STATUS_QUEUED)
+ wx.PostEvent(self, JetStatusEvent(LOAD_QUEUE_DISPLAY, None))
+
+ wx.PostEvent(self, JetStatusEvent(GRAPH_POSITION_UPDATE, status.location))
+
+ playCmd = self.GetPlayCommand()
+ if playCmd == CMD_QUEUE_AND_CANCEL or playCmd == CMD_STOP or playCmd == CMD_QUEUE_AND_CANCEL_CURRENT:
+ if playCmd == CMD_QUEUE_AND_CANCEL or playCmd == CMD_STOP:
+ num = len(self.queueSegs)
+ for i in range(num):
+ curStatus = self.GetStatus(i)
+ if curStatus == STATUS_PENDING or curStatus == STATUS_PLAYING or curStatus == STATUS_QUEUED:
+ self.SetStatus(i, STATUS_CANCELED)
+
+ if playCmd == CMD_QUEUE_AND_CANCEL_CURRENT:
+ self.SetStatus(status.currentUserID, STATUS_CANCELED)
+ num = len(self.queueSegs)
+ for i in range(num):
+ curStatus = self.GetStatus(i)
+ if curStatus == STATUS_QUEUED:
+ self.SetStatus(i, STATUS_PENDING)
+
+ if playCmd == CMD_QUEUE_AND_CANCEL:
+ segNum = self.currentSegmentIndex
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ wx.PostEvent(self, JetStatusEvent(CMD_QUEUE_AND_CANCEL, (segment, segNum)))
+
+ #MAC has a 'pop' when clearing the queue; not sure why so this avoids it
+ if OsWindows():
+ self.jet.Clear_Queue()
+ else:
+ self.jet = self.SafeJetRestart(self.playerLock, self.jet, self.jet_file.config.filename)
+
+ if playCmd == CMD_ORIGINALMUTES:
+ wx.PostEvent(self, JetStatusEvent(CMD_ORIGINALMUTES, segment.mute_flags))
+
+ if playCmd == CMD_UNMUTEALL:
+ wx.PostEvent(self, JetStatusEvent(CMD_UNMUTEALL, None))
+
+ if playCmd == CMD_PAUSE:
+ wx.PostEvent(self, JetStatusEvent(CMD_PAUSE, None))
+
+ if playCmd == CMD_MUTEALL:
+ wx.PostEvent(self, JetStatusEvent(CMD_MUTEALL, None))
+
+ self.SetPlayCommand('')
+
+ if self.GetStatus(lastID) != STATUS_CANCELED:
+ self.SetStatus(lastID, STATUS_COMPLETE)
+
+ wx.PostEvent(self, JetStatusEvent(LOAD_QUEUE_DISPLAY, None))
+ wx.PostEvent(self, JetStatusEvent(CLR_INFO, None))
+
+ SafeJetShutdown(self.playerLock, self.jet)
+ self.SetThreadShutdownFlag(True)
+
+ def OnJetStatusUpdate(self, evt):
+ """ All UI needed from within thread called via postevent otherwise mac crashes """
+ if evt.mode == LOAD_QUEUE_DISPLAY:
+ self.LoadQueueDisplay()
+ elif evt.mode == GRAPH_POSITION_UPDATE:
+ self.graph.UpdateLocation(evt.data)
+ elif evt.mode == NEW_SEGMENT_DISPLAY:
+ self.currentSegmentName = getColumnText(self.queueList, evt.data, 0)
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ info = self.graph.LoadSegment(segment)
+ self.trackList.DeleteAllItems()
+ if info <> None:
+ for track in info.trackList:
+ self.trackList.AddTrackRow(track)
+ self.trackList.CheckTracks(segment.mute_flags)
+ self.log.SetValue(self.currentSegmentName)
+ elif evt.mode == CMD_QUEUE_AND_CANCEL:
+ self.QueueOneSegment(evt.data[0], evt.data[1])
+ elif evt.mode == CMD_ORIGINALMUTES:
+ self.trackList.CheckTracks(evt.data)
+ elif evt.mode == CMD_UNMUTEALL:
+ num = self.trackList.GetItemCount()
+ for i in range(num):
+ self.trackList.CheckItem(i, False)
+ elif evt.mode == CMD_MUTEALL:
+ num = self.trackList.GetItemCount()
+ for i in range(num):
+ self.trackList.CheckItem(i)
+ elif evt.mode == CLR_INFO:
+ self.log.SetValue("")
+ self.graph.ClearGraph()
+ self.graph.UpdateLocation(0)
+ elif evt.mode == CMD_PLAY:
+ self.btnPause.SetLabel(JetDefs.BUT_PAUSE)
+ elif evt.mode == CMD_PAUSE or evt.mode == CMD_PLAY:
+ if not self.paused:
+ self.jet.Pause()
+ self.paused = True
+ self.btnPause.SetLabel(JetDefs.BUT_RESUME)
+ else:
+ self.jet.Play()
+ self.paused = False
+ self.btnPause.SetLabel(JetDefs.BUT_PAUSE)
+
+ def SafeJetRestart(self, lock, jet, filename):
+ """ Shuts down the jet engine """
+ SafeJetShutdown(lock, jet)
+ with lock:
+ jet = JET()
+ jet.eas.StartWave()
+ jet.OpenFile(filename)
+ return jet
+
+ def OnClose(self, event):
+ """ When exiting the audition window, shut down jet play thread """
+ i = 0
+ while(not self.GetThreadShutdownFlag() and i < 5):
+ #Make sure we shutdown the playing thread, but don't wait forever
+ self.SetKeepPlayingFlag(False)
+ logging.info("Waiting on shutdown %d" % (self.GetThreadShutdownFlag()))
+ time.sleep(.5)
+ i = i + 1
+
+ #make certain we clean up
+ if self.jet is not None:
+ SafeJetShutdown(self.playerLock, self.jet)
+ self.Destroy()
+
diff --git a/jet_tools/JetCreator/JetCreator.py b/jet_tools/JetCreator/JetCreator.py
new file mode 100755
index 0000000..a6f9eb6
--- /dev/null
+++ b/jet_tools/JetCreator/JetCreator.py
@@ -0,0 +1,1431 @@
+"""
+ File:
+ JetCreator.py
+
+ Contents and purpose:
+ Jet file creation utility for JET sound engine
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from __future__ import with_statement
+
+import wx
+import sys
+import thread
+import copy
+import wx.html
+import operator
+
+from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
+from eas import *
+from JetFile import *
+from JetUtils import *
+from JetCtrls import *
+from JetDialogs import *
+from JetSegGraph import SegmentGraph, Marker
+from JetAudition import *
+from JetStatusEvent import *
+
+import img_favicon
+import img_New
+
+provider = wx.SimpleHelpProvider()
+wx.HelpProvider_Set(provider)
+
+
+class JetCreator(wx.Frame):
+ """ Main window of JetCreator utility """
+ def __init__(self, parent, id, jetConfigFile, importFlag=False):
+ wx.Frame.__init__(self, parent, id, size=(1050, 720), style=wx.DEFAULT_FRAME_STYLE | wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX)
+
+ self.myicon = img_favicon.getIcon()
+ self.SetIcon(self.myicon)
+ self.UndoStack = []
+ self.RedoStack = []
+ self.queueSegs = []
+ self.clipBoard = None
+ self.jet = None
+ self.playerLock = threading.RLock()
+ self.SetKeepPlayingFlag(True)
+ self.currentSegmentName = None
+ self.currentSegmentIndex = None
+ self.currentEventName = None
+ self.currentEventIndex = None
+ self.currentCtrl = ""
+ self.currentJetConfigFile = jetConfigFile
+ self.paused = False
+ self.eventlistSort = (0, 1)
+ self.seglistSort = (0, 1)
+ if self.currentJetConfigFile == "":
+ FileKillClean(JetDefs.UNTITLED_FILE)
+ self.currentJetConfigFile = JetDefs.UNTITLED_FILE
+
+ self.jet_file = JetFile(self.currentJetConfigFile, "")
+
+ if not ValidateConfig(self.jet_file):
+ FileKillClean(JetDefs.UNTITLED_FILE)
+ self.currentJetConfigFile = JetDefs.UNTITLED_FILE
+ self.jet_file = JetFile(self.currentJetConfigFile, "")
+
+ if self.currentJetConfigFile == JetDefs.UNTITLED_FILE:
+ self.LoadDefaultProperties()
+
+ self.initLayout()
+ self.initStatusBar()
+ self.createMenuBar()
+ self.createToolbar()
+ self.SetCurrentFile(self.currentJetConfigFile)
+ self.initHelp()
+
+ self.graph.ClickCallbackFct = self.GraphTriggerClip
+
+ EVT_JET_STATUS(self, self.OnJetStatusUpdate)
+
+ wx.EVT_CLOSE(self, self.OnClose)
+
+ self.Centre()
+ self.Show(True)
+
+ if importFlag:
+ self.OnJetImportArchive(None)
+
+ self.eventList.OnSortOrderChangedAlert = self.OnEventSortOrderChanged
+ self.segList.OnSortOrderChangedAlert = self.OnSegSortOrderChanged
+
+ def initLayout(self):
+ """ Initializes the screen layout """
+ panel = wx.Panel(self, -1)
+
+ hboxMain = wx.BoxSizer(wx.HORIZONTAL)
+
+ leftPanel = wx.Panel(panel, -1)
+ leftTopPanel = wx.Panel(leftPanel, -1)
+ leftBotPanel = wx.Panel(leftPanel, -1)
+ rightPanel = wx.Panel(panel, -1)
+
+ self.segList = JetCheckListCtrl(rightPanel)
+ for title, width, fld in JetDefs.SEGMENT_GRID:
+ self.segList.AddCol(title, width)
+
+ self.eventList = JetListCtrl(rightPanel)
+ for title, width, fld in JetDefs.CLIPS_GRID:
+ self.eventList.AddCol(title, width)
+
+ self.eventList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnEventListClick)
+ self.segList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSegListClick)
+ self.segList.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnSegmentUpdate)
+ self.eventList.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnEventUpdate)
+
+ self.segList.BindCheckBox(self.OnSegmentChecked)
+
+ BUT_SIZE = (95, 25)
+ self.btnAddSeg = wx.Button(leftTopPanel, -1, JetDefs.BUT_ADD, size=BUT_SIZE)
+ self.btnRevSeg = wx.Button(leftTopPanel, -1, JetDefs.BUT_REVISE, size=BUT_SIZE)
+ self.btnDelSeg = wx.Button(leftTopPanel, -1, JetDefs.BUT_DELETE, size=BUT_SIZE)
+ self.btnMoveSeg = wx.Button(leftTopPanel, -1, JetDefs.BUT_MOVE, size=BUT_SIZE)
+
+ self.btnQueueAll = wx.Button(leftTopPanel, -1, JetDefs.BUT_QUEUEALL, size=BUT_SIZE)
+ self.btnDequeueAll = wx.Button(leftTopPanel, -1, JetDefs.BUT_DEQUEUEALL, size=BUT_SIZE)
+ self.btnPlay = wx.Button(leftTopPanel, -1, JetDefs.BUT_PLAY, size=BUT_SIZE)
+ self.btnPause = wx.Button(leftTopPanel, -1, JetDefs.BUT_PAUSE, size=BUT_SIZE)
+ self.btnAudition = wx.Button(leftTopPanel, -1, JetDefs.BUT_AUDITION, size=BUT_SIZE)
+
+ self.btnAddEvt = wx.Button(leftBotPanel, -1, JetDefs.BUT_ADD, size=BUT_SIZE)
+ self.btnRevEvt = wx.Button(leftBotPanel, -1, JetDefs.BUT_REVISE, size=BUT_SIZE)
+ self.btnDelEvt = wx.Button(leftBotPanel, -1, JetDefs.BUT_DELETE, size=BUT_SIZE)
+ self.btnMoveEvents = wx.Button(leftBotPanel, -1, JetDefs.BUT_MOVE, size=BUT_SIZE)
+
+ self.Bind(wx.EVT_BUTTON, self.OnSegmentAdd, id=self.btnAddSeg.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnSegmentUpdate, id=self.btnRevSeg.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnSegmentDelete, id=self.btnDelSeg.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnSegmentsMove, id=self.btnMoveSeg.GetId())
+
+ self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=self.btnQueueAll.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=self.btnDequeueAll.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnPlay, id=self.btnPlay.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnPause, id=self.btnPause.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnAudition, id=self.btnAudition.GetId())
+
+ self.Bind(wx.EVT_BUTTON, self.OnEventAdd, id=self.btnAddEvt.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnEventUpdate, id=self.btnRevEvt.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnEventDelete, id=self.btnDelEvt.GetId())
+ self.Bind(wx.EVT_BUTTON, self.OnEventsMove, id=self.btnMoveEvents.GetId())
+
+ BORDER = 5
+ BUT_SPACE = 3
+ vBoxLeftTop = wx.BoxSizer(wx.VERTICAL)
+ vBoxLeftBot = wx.BoxSizer(wx.VERTICAL)
+
+ vBoxLeftTop.Add(self.btnAddSeg, 0, wx.TOP, BORDER)
+ vBoxLeftTop.Add(self.btnRevSeg, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnDelSeg, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnMoveSeg, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add((-1, 12))
+ vBoxLeftTop.Add(self.btnQueueAll, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnDequeueAll, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnPlay, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnPause, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftTop.Add(self.btnAudition, 0, wx.TOP, BUT_SPACE)
+
+ vBoxLeftBot.Add(self.btnAddEvt, 0)
+ vBoxLeftBot.Add(self.btnRevEvt, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftBot.Add(self.btnDelEvt, 0, wx.TOP, BUT_SPACE)
+ vBoxLeftBot.Add(self.btnMoveEvents, 0, wx.TOP, BUT_SPACE)
+
+ leftTopPanel.SetSizer(vBoxLeftTop)
+ leftBotPanel.SetSizer(vBoxLeftBot)
+
+ vboxLeft = wx.BoxSizer(wx.VERTICAL)
+ vboxLeft.Add(leftTopPanel, 1, wx.EXPAND)
+ vboxLeft.Add(leftBotPanel, 1, wx.EXPAND)
+ vboxLeft.Add((-1, 25))
+
+ leftPanel.SetSizer(vboxLeft)
+
+ self.log = wx.TextCtrl(rightPanel, -1)
+ self.graph = SegmentGraph(rightPanel, size=(-1, 50))
+
+ vboxRight = wx.BoxSizer(wx.VERTICAL)
+ vboxRight.Add(self.segList, 4, wx.EXPAND | wx.TOP, BORDER)
+ vboxRight.Add((-1, 10))
+ vboxRight.Add(self.eventList, 3, wx.EXPAND | wx.TOP, BORDER)
+ vboxRight.Add((-1, 10))
+ vboxRight.Add(self.log, 0, wx.EXPAND)
+ vboxRight.Add((-1, 5))
+ vboxRight.Add(self.graph, 1, wx.EXPAND)
+ vboxRight.Add((-1, 10))
+
+ rightPanel.SetSizer(vboxRight)
+
+ hboxMain.Add(leftPanel, 0, wx.EXPAND | wx.RIGHT | wx.LEFT, BORDER)
+ hboxMain.Add(rightPanel, 1, wx.EXPAND)
+ hboxMain.Add((BORDER, -1))
+
+ panel.SetSizer(hboxMain)
+
+ pnlGraph = wx.Panel(leftBotPanel, -1)
+ graphSizer1 = wx.BoxSizer(wx.VERTICAL)
+ pnlGraph.SetSizer(graphSizer1)
+
+ graphBox = wx.StaticBox(pnlGraph, wx.ID_ANY, label='Graph')
+ graphSizer2 = wx.StaticBoxSizer(graphBox, wx.VERTICAL)
+
+ self.chkGraphLabels = wx.CheckBox(pnlGraph, -1, JetDefs.GRAPH_LBLS)
+ self.chkGraphClips = wx.CheckBox(pnlGraph, -1, JetDefs.GRAPH_TRIGGER)
+ self.chkGraphAppEvts = wx.CheckBox(pnlGraph, -1, JetDefs.GRAPH_APP)
+
+ graphSizer2.Add(self.chkGraphLabels, 0, wx.TOP, BUT_SPACE)
+ graphSizer2.Add(self.chkGraphClips, 0, wx.TOP, BUT_SPACE)
+ graphSizer2.Add(self.chkGraphAppEvts, 0, wx.TOP | wx.BOTTOM, BUT_SPACE)
+ graphSizer1.Add((-1, 10))
+ graphSizer1.Add(graphSizer2)
+
+ vBoxLeftBot.Add(pnlGraph, 0, wx.TOP, BUT_SPACE)
+
+ self.Bind(wx.EVT_CHECKBOX, self.OnSetGraphOptions, id=self.chkGraphLabels.GetId())
+ self.Bind(wx.EVT_CHECKBOX, self.OnSetGraphOptions, id=self.chkGraphClips.GetId())
+ self.Bind(wx.EVT_CHECKBOX, self.OnSetGraphOptions, id=self.chkGraphAppEvts.GetId())
+
+
+ def initHelp(self):
+ """ Initializes the help text for screen elements """
+ self.SetHelpText(GetJetHelpText(JetDefs.MAIN_DLG_CTRLS, ''))
+ self.segList.SetHelpText(GetJetHelpText(JetDefs.MAIN_DLG_CTRLS, JetDefs.MAIN_SEGLIST))
+ self.eventList.SetHelpText(GetJetHelpText(JetDefs.MAIN_DLG_CTRLS, JetDefs.MAIN_EVENTLIST))
+ self.graph.SetHelpText(GetJetHelpText(JetDefs.AUDITION_CTRLS, JetDefs.AUDITION_GRAPH))
+
+ def initStatusBar(self):
+ """ Initializes the status bar """
+ self.statusbar = self.CreateStatusBar()
+
+ def OnSelectAll(self, event):
+ """ Called from select all button """
+ num = self.segList.GetItemCount()
+ for i in range(num-1, -1, -1):
+ self.segList.CheckItem(i)
+
+ def OnDeselectAll(self, event):
+ """ Called from deselect all button """
+ num = self.segList.GetItemCount()
+ for i in range(num-1, -1, -1):
+ self.segList.CheckItem(i, False)
+
+ def LoadSegList(self):
+ """ Loads up the list of segments """
+ self.seglistSort = (IniGetValue(self.currentJetConfigFile, JetDefs.INI_SEGSORT, JetDefs.INI_SEGSORT_0, 'int', 0), IniGetValue(self.currentJetConfigFile, JetDefs.INI_SEGSORT, JetDefs.INI_SEGSORT_1, 'int', 1))
+ segments = self.jet_file.GetSegments()
+ if self.seglistSort[0] == 0:
+ self.SegSort(segments, "segname")
+ elif self.seglistSort[0] == 1:
+ self.SegSort(segments, "filename")
+ elif self.seglistSort[0] == 2:
+ self.SegSort(segments, "dlsfile")
+ elif self.seglistSort[0] == 3:
+ self.SegSort(segments, "start")
+ elif self.seglistSort[0] == 4:
+ self.SegSort(segments, "end")
+ elif self.seglistSort[0] == 5:
+ self.SegSort(segments, "quantize")
+ elif self.seglistSort[0] == 6:
+ self.SegSort(segments, "transpose")
+ elif self.seglistSort[0] == 7:
+ self.SegSort(segments, "repeat")
+ elif self.seglistSort[0] == 8:
+ self.SegSort(segments, "mute_flags")
+ listDataMap = []
+ self.currentSegmentIndex = None
+ self.currentSegmentName = None
+ self.segList.DeleteAllItems()
+ self.eventList.DeleteAllItems()
+ self.menuItems[JetDefs.MNU_UPDATE_SEG].Enable(False)
+ self.menuItems[JetDefs.MNU_DELETE_SEG].Enable(False)
+ self.menuItems[JetDefs.MNU_MOVE_SEG].Enable(False)
+
+ self.menuItems[JetDefs.MNU_ADD_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_MOVE_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_UPDATE_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_DELETE_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_MOVE_EVENT].Enable(False)
+ for segment in self.jet_file.GetSegments():
+ index = self.segList.InsertStringItem(sys.maxint, StrNoneChk(segment.segname))
+ self.segList.SetStringItem(index, 1, FileJustName(StrNoneChk(segment.filename)))
+ self.segList.SetStringItem(index, 2, FileJustName(StrNoneChk(segment.dlsfile)))
+ self.segList.SetStringItem(index, 3, mbtFct(segment.start, 1))
+ self.segList.SetStringItem(index, 4, mbtFct(segment.end, 1))
+ self.segList.SetStringItem(index, 5, StrNoneChk(segment.quantize))
+ self.segList.SetStringItem(index, 6, StrNoneChk(segment.transpose))
+ self.segList.SetStringItem(index, 7, StrNoneChk(segment.repeat))
+ self.segList.SetStringItem(index, 8, StrNoneChk(segment.mute_flags))
+
+ self.segList.SetItemData(index, index)
+ listDataMap.append((getColumnText(self.segList, index, 0).upper(), getColumnText(self.segList, index, 1).upper(), getColumnText(self.segList, index, 2).upper(), MbtVal(getColumnText(self.segList, index, 3)), MbtVal(getColumnText(self.segList, index, 4)), int(getColumnText(self.segList, index, 5)), int(getColumnText(self.segList, index, 6)), int(getColumnText(self.segList, index, 7)), int(getColumnText(self.segList, index, 8))))
+
+ self.segList.itemDataMap = listDataMap
+ self.segList.InitSorting(9)
+ self.graph.ClearGraph()
+
+ def LoadEventsForSeg(self, segName):
+ """ Loads up the events associated with a segment """
+ self.currentEventIndex = None
+ self.eventList.DeleteAllItems()
+ self.menuItems[JetDefs.MNU_UPDATE_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_DELETE_EVENT].Enable(False)
+ self.menuItems[JetDefs.MNU_MOVE_EVENT].Enable(False)
+ self.eventlistSort = (IniGetValue(self.currentJetConfigFile, JetDefs.INI_EVENTSORT, JetDefs.INI_EVENTSORT_0, 'int', 0), IniGetValue(self.currentJetConfigFile, JetDefs.INI_EVENTSORT, JetDefs.INI_EVENTSORT_1, 'int', 1))
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment is not None:
+ if self.eventlistSort[0] == 0:
+ self.EventSort(segment.jetevents, "event_name")
+ elif self.eventlistSort[0] == 1:
+ self.EventSort(segment.jetevents, "event_type")
+ elif self.eventlistSort[0] == 2:
+ self.EventSort(segment.jetevents, "event_start")
+ elif self.eventlistSort[0] == 3:
+ self.EventSort(segment.jetevents, "event_end")
+ elif self.eventlistSort[0] == 4:
+ self.EventSort(segment.jetevents, "track_num")
+ elif self.eventlistSort[0] == 5:
+ self.EventSort(segment.jetevents, "channel_num")
+ elif self.eventlistSort[0] == 6:
+ self.EventSort(segment.jetevents, "event_id")
+ listDataMap = []
+ for jet_event in self.jet_file.GetEvents(segName):
+ index = self.eventList.InsertStringItem(sys.maxint, StrNoneChk(jet_event.event_name))
+ self.eventList.SetStringItem(index, 1, StrNoneChk(jet_event.event_type))
+ self.eventList.SetStringItem(index, 2, mbtFct(jet_event.event_start, 1))
+ self.eventList.SetStringItem(index, 3, mbtFct(jet_event.event_end, 1))
+ self.eventList.SetStringItem(index, 4, StrNoneChk(jet_event.track_num))
+ self.eventList.SetStringItem(index, 5, StrNoneChk(jet_event.channel_num + 1))
+ self.eventList.SetStringItem(index, 6, StrNoneChk(jet_event.event_id))
+
+ self.eventList.SetItemData(index, index)
+ listDataMap.append((getColumnText(self.eventList, index, 0).upper(), getColumnText(self.eventList, index, 1).upper(), MbtVal(getColumnText(self.eventList, index, 2)), MbtVal(getColumnText(self.eventList, index, 3)), int(getColumnText(self.eventList, index, 4)), int(getColumnText(self.eventList, index, 5)), int(getColumnText(self.eventList, index, 6))))
+
+ self.eventList.itemDataMap = listDataMap
+ self.eventList.InitSorting(7)
+
+ def OnEventListClick(self, event):
+ """ Sets the current event variable when selecting from the list """
+ self.currentCtrl = "eventList"
+ self.currentEventIndex = event.m_itemIndex
+ self.currentEventName = getColumnText(self.eventList, event.m_itemIndex, 0)
+ self.menuItems[JetDefs.MNU_UPDATE_EVENT].Enable(True)
+ self.menuItems[JetDefs.MNU_DELETE_EVENT].Enable(True)
+ self.menuItems[JetDefs.MNU_MOVE_EVENT].Enable(True)
+
+ def OnSegmentChecked(self, index, checked):
+ """ Selects the segment when checkbox clicked """
+ ClearRowSelections(self.segList)
+ SetRowSelection(self.segList, index, True)
+
+ def SelectSegment(self, segName):
+ """ Selects a segment by segment name """
+ itm = self.segList.FindItem(-1, segName)
+ self.segList.EnsureVisible(itm)
+ ClearRowSelections(self.segList)
+ SetRowSelection(self.segList, itm, True)
+
+ def SelectEvent(self, eventName):
+ """ Selects an event by event name """
+ itm = self.eventList.FindItem(-1, eventName)
+ self.eventList.EnsureVisible(itm)
+ ClearRowSelections(self.eventList)
+ SetRowSelection(self.eventList, itm, True)
+
+ def OnSegListClick(self, event):
+ """ Loads up a segment when the list is clicked """
+ self.currentCtrl = "segList"
+ self.currentSegmentIndex = event.m_itemIndex
+ self.currentSegmentName = getColumnText(self.segList, event.m_itemIndex, 0)
+ self.LoadEventsForSeg(getColumnText(self.segList, event.m_itemIndex, 0))
+ self.menuItems[JetDefs.MNU_UPDATE_SEG].Enable(True)
+ self.menuItems[JetDefs.MNU_DELETE_SEG].Enable(True)
+ self.menuItems[JetDefs.MNU_MOVE_SEG].Enable(True)
+ self.menuItems[JetDefs.MNU_ADD_EVENT].Enable(True)
+ info = self.graph.LoadSegment(self.jet_file.GetSegment(self.currentSegmentName), showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+ if info == None:
+ self.log.SetValue("")
+ else:
+ iLength = info.iLengthInMs
+ if iLength > 0:
+ self.log.SetValue("%s %.2f Seconds" % (self.currentSegmentName, iLength / 1000.00))
+ else:
+ self.log.SetValue("%s" % (self.currentSegmentName))
+
+ def OnSegmentAdd(self, event):
+ """ Calls the dialog box for adding a segment """
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ dlg = SegEdit(JetDefs.MAIN_ADDSEGTITLE, self.currentJetConfigFile)
+
+ for filename in self.jet_file.GetMidiFiles():
+ dlg.je.ctrls[JetDefs.F_MIDIFILE].Append(filename)
+ for library in self.jet_file.GetLibraries():
+ dlg.je.ctrls[JetDefs.F_DLSFILE].Append(library)
+
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ if len(dlg.lstReplicate) > 0:
+ if dlg.chkReplaceMatching:
+ self.jet_file.DeleteSegmentsMatchingPrefix(dlg.replicatePrefix)
+
+ for replicate in dlg.lstReplicate:
+ self.jet_file.AddSegment(replicate[0], dlg.GetValue(JetDefs.F_MIDIFILE),
+ mbtFct(replicate[1],-1), mbtFct(replicate[2],-1),
+ JetDefs.MBT_ZEROSTR,
+ SegmentOutputFile(dlg.GetValue(JetDefs.F_SEGNAME), self.currentJetConfigFile),
+ dlg.GetValue(JetDefs.F_QUANTIZE),
+ [], dlg.GetValue(JetDefs.F_DLSFILE),
+ None,
+ dlg.GetValue(JetDefs.F_TRANSPOSE),
+ dlg.GetValue(JetDefs.F_REPEAT),
+ dlg.GetValue(JetDefs.F_MUTEFLAGS))
+
+ self.LoadSegList()
+ self.SelectSegment(dlg.lstReplicate[0][0])
+ else:
+ self.jet_file.AddSegment(dlg.GetValue(JetDefs.F_SEGNAME), dlg.GetValue(JetDefs.F_MIDIFILE),
+ dlg.GetValue(JetDefs.F_START), dlg.GetValue(JetDefs.F_END),
+ JetDefs.MBT_ZEROSTR,
+ SegmentOutputFile(dlg.GetValue(JetDefs.F_SEGNAME), self.currentJetConfigFile),
+ dlg.GetValue(JetDefs.F_QUANTIZE),
+ [], dlg.GetValue(JetDefs.F_DLSFILE),
+ None,
+ dlg.GetValue(JetDefs.F_TRANSPOSE),
+ dlg.GetValue(JetDefs.F_REPEAT),
+ dlg.GetValue(JetDefs.F_MUTEFLAGS))
+ self.LoadSegList()
+ self.SelectSegment(dlg.GetValue(JetDefs.F_SEGNAME))
+ self.UndoAdd(saveState)
+ dlg.Destroy()
+
+ def OnSegmentUpdate(self, event):
+ """ Calls the dialog box for updating a segment """
+ if self.currentSegmentName is None:
+ return
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return
+
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ dlg = SegEdit(JetDefs.MAIN_REVSEGTITLE, self.currentJetConfigFile)
+
+ for filename in self.jet_file.GetMidiFiles():
+ dlg.je.ctrls[JetDefs.F_MIDIFILE].Append(filename)
+ for library in self.jet_file.GetLibraries():
+ dlg.je.ctrls[JetDefs.F_DLSFILE].Append(library)
+
+ dlg.SetValue(JetDefs.F_SEGNAME, segment.segname)
+ dlg.SetValue(JetDefs.F_MUTEFLAGS, segment.mute_flags)
+ dlg.SetValue(JetDefs.F_MIDIFILE, segment.filename)
+ dlg.SetValue(JetDefs.F_DLSFILE, segment.dlsfile)
+ dlg.SetValue(JetDefs.F_START, segment.start)
+ dlg.SetValue(JetDefs.F_END, segment.end)
+ dlg.SetValue(JetDefs.F_QUANTIZE, segment.quantize)
+ dlg.SetValue(JetDefs.F_TRANSPOSE, segment.transpose)
+ dlg.SetValue(JetDefs.F_REPEAT, segment.repeat)
+ dlg.jetevents = segment.jetevents
+
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ self.jet_file.UpdateSegment(self.currentSegmentName, dlg.GetValue(JetDefs.F_SEGNAME),
+ dlg.GetValue(JetDefs.F_MIDIFILE),
+ dlg.GetValue(JetDefs.F_START), dlg.GetValue(JetDefs.F_END),
+ JetDefs.MBT_ZEROSTR, #dlg.GetValue(JetDefs.F_LENGTH),
+ SegmentOutputFile(dlg.GetValue(JetDefs.F_SEGNAME), self.currentJetConfigFile),
+ dlg.GetValue(JetDefs.F_QUANTIZE),
+ [], dlg.GetValue(JetDefs.F_DLSFILE),
+ None,
+ dlg.GetValue(JetDefs.F_TRANSPOSE),
+ dlg.GetValue(JetDefs.F_REPEAT),
+ dlg.GetValue(JetDefs.F_MUTEFLAGS))
+
+ if len(dlg.lstReplicate) > 0:
+ if dlg.chkReplaceMatching:
+ self.jet_file.DeleteSegmentsMatchingPrefix(dlg.replicatePrefix)
+
+ for replicate in dlg.lstReplicate:
+ self.jet_file.AddSegment(replicate[0], dlg.GetValue(JetDefs.F_MIDIFILE),
+ mbtFct(replicate[1],-1), mbtFct(replicate[2],-1),
+ JetDefs.MBT_ZEROSTR,
+ SegmentOutputFile(dlg.GetValue(JetDefs.F_SEGNAME), self.currentJetConfigFile),
+ dlg.GetValue(JetDefs.F_QUANTIZE),
+ [], dlg.GetValue(JetDefs.F_DLSFILE),
+ None,
+ dlg.GetValue(JetDefs.F_TRANSPOSE),
+ dlg.GetValue(JetDefs.F_REPEAT),
+ dlg.GetValue(JetDefs.F_MUTEFLAGS))
+
+ self.LoadSegList()
+ self.SelectSegment(dlg.lstReplicate[0][0])
+ else:
+ self.LoadSegList()
+ self.SelectSegment(dlg.GetValue(JetDefs.F_SEGNAME))
+ self.UndoAdd(saveState)
+ dlg.Destroy()
+
+ def OnSegmentDelete(self, event):
+ """ Confirms the deletion segment(s) by user action """
+ if self.currentSegmentName is None:
+ return
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return
+
+ count = 0
+ deleteMsg = ''
+ item = self.segList.GetFirstSelected()
+ while item != -1:
+ if count == 0:
+ deleteMsg = getColumnText(self.segList,item,0)
+ else:
+ if count == 40:
+ deleteMsg = deleteMsg + "\n" + "....more"
+ elif count < 40:
+ deleteMsg = deleteMsg + "\n" + getColumnText(self.segList,item,0)
+ count = count + 1
+ item = self.segList.GetNextSelected(item)
+
+ if YesNo(JetDefs.MAIN_CONFIRM, deleteMsg + JetDefs.MAIN_CONFIRM_SEG_DLT, False):
+ item = self.segList.GetFirstSelected()
+ while item != -1:
+ segName = getColumnText(self.segList,item,0)
+ self.SegmentDelete(segName)
+ item = self.segList.GetNextSelected(item)
+
+ self.graph.ClearGraph()
+ self.LoadSegList()
+
+ def SegmentDelete(self, segName):
+ """ Deletes a segment """
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+ self.jet_file.DeleteSegment(segName)
+ self.UndoAdd(saveState)
+
+ def OnSegmentsMove(self, event):
+ """ Move segment(s) """
+ if self.currentSegmentName is None:
+ return
+
+ lstMoveItems = []
+ count = 0
+ item = self.segList.GetFirstSelected()
+ while item != -1:
+ max = GetMidiInfo(self.jet_file.GetSegment(getColumnText(self.segList,item,0)).filename).endMbtStr
+ lstMoveItems.append((getColumnText(self.segList,item,0), mbtFct(getColumnText(self.segList,item,3),-1), mbtFct(getColumnText(self.segList,item,4),-1), max))
+ count = count + 1
+ item = self.segList.GetNextSelected(item)
+
+ if count == 0:
+ InfoMsg("Move", "Select one or more items to move.")
+ return
+
+ dlg = JetMove("Move Segments")
+ dlg.lstMoveItems = lstMoveItems
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ if len(dlg.lstMoveMbt) > 0:
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ for moveitem in dlg.lstMoveMbt:
+ self.jet_file.MoveSegment(moveitem[0], moveitem[1], moveitem[2])
+
+ self.LoadSegList()
+ self.UndoAdd(saveState)
+
+ dlg.Destroy()
+
+ def UndoAdd(self, saveState):
+ """ Adds the current state to the undo stack """
+ self.UndoStack.append(saveState)
+ self.menuItems[JetDefs.MNU_UNDO].Enable(True)
+
+ def RedoAdd(self, saveState):
+ """ Adds the current state the the redo stack """
+ self.RedoStack.append(saveState)
+ self.menuItems[JetDefs.MNU_REDO].Enable(True)
+
+ def OnRedo(self, event):
+ """ Redo if there's one in the stack """
+ if len(self.RedoStack) > 0:
+ self.UndoAdd(JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex))
+ state = self.RedoStack.pop()
+ self.jet_file = copy.deepcopy(state.jet_file)
+ self.LoadSegList()
+ self.currentSegmentIndex = state.currentSegmentIndex
+ if self.currentSegmentIndex != None:
+ SetRowSelection(self.segList, self.currentSegmentIndex, True)
+ if len(self.RedoStack) == 0:
+ self.menuItems[JetDefs.MNU_REDO].Enable(False)
+
+ def OnUndo(self, event):
+ """ Undo if there's one in the stack """
+ if len(self.UndoStack) > 0:
+ self.RedoAdd(JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex))
+ state = self.UndoStack.pop()
+ self.jet_file = copy.deepcopy(state.jet_file)
+ self.LoadSegList()
+ self.currentSegmentIndex = state.currentSegmentIndex
+ if self.currentSegmentIndex != None:
+ SetRowSelection(self.segList, self.currentSegmentIndex, True)
+ if len(self.UndoStack) == 0:
+ self.menuItems[JetDefs.MNU_UNDO].Enable(False)
+
+ def OnEventAdd(self, event):
+ """ Calls a dialog box to add an event to the current segment """
+ if self.currentSegmentName is None:
+ return
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return
+
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ dlg = EventEdit(JetDefs.MAIN_ADDEVENTTITLE, self.currentJetConfigFile)
+ editSegment = copy.deepcopy(segment)
+ dlg.SetSegment(editSegment)
+ dlg.SetEventId()
+
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ if dlg.GetValue(JetDefs.F_ETYPE) == JetDefs.E_EOS:
+ #check for an existing EOS event
+ events = self.jet_file.GetEvents(self.currentSegmentName)
+ for evt in events:
+ if evt.event_type == JetDefs.E_EOS:
+ self.jet_file.DeleteEvent(self.currentSegmentName, evt.event_name)
+ dlg.SetValue(JetDefs.F_ESTART, dlg.GetValue(JetDefs.F_EEND))
+
+ if len(dlg.lstReplicate) > 0:
+ if dlg.chkReplaceMatching:
+ self.jet_file.DeleteEventsMatchingPrefix(self.currentSegmentName, dlg.replicatePrefix)
+
+ for replicate in dlg.lstReplicate:
+ self.jet_file.AddEvent(self.currentSegmentName, replicate[0],
+ dlg.GetValue(JetDefs.F_ETYPE),
+ dlg.GetValue(JetDefs.F_EEVENTID),
+ dlg.GetValue(JetDefs.F_ETRACK),
+ dlg.GetValue(JetDefs.F_ECHANNEL),
+ mbtFct(replicate[1],-1),
+ mbtFct(replicate[2],-1))
+ self.SelectSegment(self.currentSegmentName)
+ self.SelectEvent(dlg.lstReplicate[0][0])
+ else:
+ self.jet_file.AddEvent(self.currentSegmentName, dlg.GetValue(JetDefs.F_ENAME),
+ dlg.GetValue(JetDefs.F_ETYPE),
+ dlg.GetValue(JetDefs.F_EEVENTID),
+ dlg.GetValue(JetDefs.F_ETRACK),
+ dlg.GetValue(JetDefs.F_ECHANNEL),
+ dlg.GetValue(JetDefs.F_ESTART),
+ dlg.GetValue(JetDefs.F_EEND))
+
+ self.SelectSegment(self.currentSegmentName)
+ self.SelectEvent(dlg.GetValue(JetDefs.F_ENAME))
+
+ self.UndoAdd(saveState)
+ dlg.Destroy()
+
+ def OnEventUpdate(self, event):
+ """ Calls the dialog box to update the current event """
+ if self.currentSegmentName is None:
+ return
+
+ if self.currentEventName is None:
+ return
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return
+
+ curEvent = copy.deepcopy(self.jet_file.GetEvent(self.currentSegmentName, self.currentEventName))
+ if curEvent == None:
+ return
+
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ #only want the event we are editing to show up in graph
+ editSegment = copy.deepcopy(segment)
+ editSegment.jetevents = []
+ editSegment.jetevents.append(curEvent)
+
+ dlg = EventEdit(JetDefs.MAIN_REVEVENTTITLE, self.currentJetConfigFile)
+ dlg.SetSegment(editSegment)
+ dlg.SetValue(JetDefs.F_ENAME, curEvent.event_name)
+ dlg.SetValue(JetDefs.F_ETYPE, curEvent.event_type)
+ dlg.SetValue(JetDefs.F_ESTART, curEvent.event_start)
+ dlg.SetValue(JetDefs.F_EEND, curEvent.event_end)
+ dlg.SetValue(JetDefs.F_ETRACK, curEvent.track_num)
+ dlg.SetValue(JetDefs.F_ECHANNEL, curEvent.channel_num)
+ dlg.SetValue(JetDefs.F_EEVENTID, curEvent.event_id)
+ dlg.OnEventSelect()
+
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ if dlg.GetValue(JetDefs.F_ETYPE) == JetDefs.E_EOS:
+ dlg.SetValue(JetDefs.F_ESTART, dlg.GetValue(JetDefs.F_EEND))
+
+ self.jet_file.UpdateEvent(self.currentSegmentName,
+ self.currentEventName,
+ dlg.GetValue(JetDefs.F_ENAME),
+ dlg.GetValue(JetDefs.F_ETYPE),
+ dlg.GetValue(JetDefs.F_EEVENTID),
+ dlg.GetValue(JetDefs.F_ETRACK),
+ dlg.GetValue(JetDefs.F_ECHANNEL),
+ dlg.GetValue(JetDefs.F_ESTART),
+ dlg.GetValue(JetDefs.F_EEND))
+
+ if len(dlg.lstReplicate) > 0:
+ if dlg.chkReplaceMatching:
+ self.jet_file.DeleteEventsMatchingPrefix(self.currentSegmentName, dlg.replicatePrefix)
+
+ for replicate in dlg.lstReplicate:
+ self.jet_file.AddEvent(self.currentSegmentName, replicate[0],
+ dlg.GetValue(JetDefs.F_ETYPE),
+ dlg.GetValue(JetDefs.F_EEVENTID),
+ dlg.GetValue(JetDefs.F_ETRACK),
+ dlg.GetValue(JetDefs.F_ECHANNEL),
+ mbtFct(replicate[1],-1),
+ mbtFct(replicate[2],-1))
+ self.SelectSegment(self.currentSegmentName)
+ self.SelectEvent(dlg.lstReplicate[0][0])
+ else:
+ self.SelectSegment(self.currentSegmentName)
+ self.SelectEvent(dlg.GetValue(JetDefs.F_ENAME))
+ self.UndoAdd(saveState)
+ dlg.Destroy()
+
+ def OnEventDelete(self, event):
+ """ Confirms the deletion of event(s) """
+ if self.currentSegmentName is None:
+ return
+
+ if self.currentEventName is None:
+ return
+
+ curEvent = self.jet_file.GetEvent(self.currentSegmentName, self.currentEventName)
+ if curEvent == None:
+ return
+
+ count = 0
+ deleteMsg = ''
+ item = self.eventList.GetFirstSelected()
+ while item != -1:
+ if count == 0:
+ deleteMsg = getColumnText(self.eventList,item,0)
+ else:
+ if count == 40:
+ deleteMsg = deleteMsg + "\n" + "....more"
+ elif count < 40:
+ deleteMsg = deleteMsg + "\n" + getColumnText(self.eventList,item,0)
+ count = count + 1
+ item = self.eventList.GetNextSelected(item)
+
+
+ if YesNo(JetDefs.MAIN_CONFIRM, deleteMsg + JetDefs.MAIN_CONRIRM_EVT_DLT, False):
+ item = self.eventList.GetFirstSelected()
+ while item != -1:
+ eventName = getColumnText(self.eventList,item,0)
+ self.EventDelete(eventName)
+ item = self.eventList.GetNextSelected(item)
+
+ self.SelectSegment(self.currentSegmentName)
+ self.LoadEventsForSeg(self.currentSegmentName)
+
+ def EventDelete(self, eventName):
+ """ Deletes an event """
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+ self.jet_file.DeleteEvent(self.currentSegmentName, eventName)
+ self.UndoAdd(saveState)
+
+ def OnEventsMove(self, event):
+ """ Move event(s) """
+ if self.currentSegmentName is None:
+ return
+
+ if self.currentEventName is None:
+ return
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return
+
+ curEvent = self.jet_file.GetEvent(self.currentSegmentName, self.currentEventName)
+ if curEvent == None:
+ return
+
+ lstMoveItems = []
+ count = 0
+ item = self.eventList.GetFirstSelected()
+ while item != -1:
+ lstMoveItems.append((getColumnText(self.eventList,item,0), mbtFct(getColumnText(self.eventList,item,2),-1), mbtFct(getColumnText(self.eventList,item,3),-1), segment.end))
+ count = count + 1
+ item = self.eventList.GetNextSelected(item)
+
+ if count == 0:
+ InfoMsg("Move", "Select one or more items to move.")
+ return
+
+ dlg = JetMove("Move Events")
+ dlg.lstMoveItems = lstMoveItems
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ if len(dlg.lstMoveMbt) > 0:
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+
+ for moveitem in dlg.lstMoveMbt:
+ self.jet_file.MoveEvent(self.currentSegmentName, moveitem[0], moveitem[1], moveitem[2])
+
+ self.SelectSegment(self.currentSegmentName)
+ self.LoadEventsForSeg(self.currentSegmentName)
+
+ self.UndoAdd(saveState)
+
+ dlg.Destroy()
+
+ def OnJetOpen(self, event):
+ """ Calls a dialog box to get a jet config file to open """
+ dlg = JetOpen()
+ result = dlg.ShowModal()
+ if result == JetDefs.ID_JET_OPEN:
+ self.jet_file = JetFile(dlg.fileName , "")
+ if not ValidateConfig(self.jet_file):
+ FileKillClean(JetDefs.UNTITLED_FILE)
+ self.currentJetConfigFile = JetDefs.UNTITLED_FILE
+ self.jet_file = JetFile(self.currentJetConfigFile, "")
+ else:
+ self.SetCurrentFile(dlg.fileName)
+ elif result == JetDefs.ID_JET_NEW:
+ self.jet_file = JetFile("" , "")
+ self.SetCurrentFile(JetDefs.UNTITLED_FILE)
+ self.LoadDefaultProperties()
+ elif result == JetDefs.ID_JET_IMPORT:
+ self.OnJetImportArchive(event)
+ dlg.Destroy()
+
+ def OnJetSaveAs(self, event):
+ """ Calls a dialog box to allow saving the current jet file as another name """
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.JTC_FILE_SPEC, 'str', str(os.getcwd()))
+ dialog = wx.FileDialog(None, JetDefs.SAVE_PROMPT, defDir, "", JetDefs.JTC_FILE_SPEC, wx.SAVE | wx.OVERWRITE_PROMPT )
+ if dialog.ShowModal() == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.JTC_FILE_SPEC, str(FileJustPath(dialog.GetPath())))
+ self.currentJetConfigFile = FileJustRoot(dialog.GetPath()) + ".jtc"
+ self.jet_file.config.filename = FileJustRoot(self.currentJetConfigFile) + ".jet"
+ self.jet_file.SaveJetConfig(self.currentJetConfigFile)
+ self.jet_file.WriteJetFileFromConfig(self.currentJetConfigFile)
+ self.SetCurrentFile(self.currentJetConfigFile)
+ dialog.Destroy()
+
+ def OnJetSave(self, event):
+ """ Saves the current jet file to disk """
+ if self.currentJetConfigFile == JetDefs.UNTITLED_FILE:
+ self.OnJetSaveAs(event)
+ else:
+ self.jet_file.SaveJetConfig(self.currentJetConfigFile)
+ self.jet_file.WriteJetFileFromConfig(self.currentJetConfigFile)
+
+ def OnJetNew(self, event):
+ """ Initializes the state to a new jet file """
+ self.jet_file = JetFile("" , "")
+ self.SetCurrentFile(JetDefs.UNTITLED_FILE)
+ self.LoadDefaultProperties()
+
+ def SetCurrentFile(self, fileName):
+ """ Sets the state for the current jet file """
+ self.clipBoard = None
+ self.currentJetConfigFile = fileName
+ self.SetTitle(JetDefs.MAIN_TITLEPREFIX + FileJustName(fileName))
+ AppendRecentJetFile(fileName)
+ self.LoadSegList()
+ self.graph.ClearGraph()
+ self.chkGraphLabels.SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'))
+ self.chkGraphClips.SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'))
+ self.chkGraphAppEvts.SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def createMenuBar(self):
+ """ Creates a menu bar """
+ self.menuItems = {}
+ menuBar = wx.MenuBar()
+ for eachMenuData in JetDefs.MENU_SPEC:
+ menuLabel = eachMenuData[0]
+ menuItems = eachMenuData[1:]
+ menuBar.Append(self.createMenu(menuItems), menuLabel)
+ self.SetMenuBar(menuBar)
+
+ def createMenu(self, menuData):
+ """ Creates a menu from the structure menuData in JetDefs """
+ menu = wx.Menu()
+ for eachLabel, eachStatus, eachHandler, eachEnable in menuData:
+ if not eachLabel:
+ menu.AppendSeparator()
+ continue
+ self.menuItems[eachLabel] = menu.Append(-1, eachLabel, eachStatus)
+ self.menuItems[eachLabel].Enable(eachEnable)
+ try:
+ self.Bind(wx.EVT_MENU, getattr(self, eachHandler) , self.menuItems[eachLabel])
+ except:
+ print("def " + eachHandler + "(self, event): pass")
+ return menu
+
+ def createToolbar(self):
+ """ Creates the toolbar """
+ toolbar = self.CreateToolBar()
+ toolbar.SetToolBitmapSize((32,32))
+ self.toolItems = {}
+ for eachTool in JetDefs.TOOLBAR_SPEC:
+ if eachTool[0] == '-':
+ toolbar.AddSeparator()
+ else:
+ b = __import__(eachTool[1])
+ bitMap = b.getBitmap()
+ self.toolItems[eachTool[0]] = toolbar.AddLabelTool(-1, label=eachTool[0],
+ bitmap=bitMap,
+ shortHelp=eachTool[0], longHelp=eachTool[2])
+ self.Bind(wx.EVT_TOOL, getattr(self, eachTool[3]) , self.toolItems[eachTool[0]])
+ toolbar.Realize()
+
+ def OnAudition(self, event):
+ """ Calls the audition window for simple preview of jet file """
+ jet_file = CreateTempJetFile(self.jet_file)
+
+ w, h = self.GetSize()
+ w = w - 50
+ if w < 900:
+ w = 900
+ h = h - 50
+ if h < 650:
+ h = 650
+ dlg = Audition(jet_file, (w,h))
+ dlg.ShowModal()
+ CleanupTempJetFile(jet_file)
+
+ def SetKeepPlayingFlag(self, val):
+ """ Sets a flag to communicate playing state to the play thread """
+ with self.playerLock:
+ self.keepPlaying = val
+
+ def GetKeepPlayingFlag(self):
+ """ Gets the playing state flag """
+ with self.playerLock:
+ return self.keepPlaying
+
+ def GraphTriggerClip(self, sClipName, iEventId):
+ """ Triggers a clip when they click on the graph """
+ with self.playerLock:
+ try:
+ self.jet.TriggerClip(iEventId)
+ self.log.SetValue(JetDefs.PLAY_TRIGGERCLIP_MSG % (iEventId, sClipName))
+ return True
+ except:
+ return False
+
+ def OnHelpJet(self, event):
+ """ Loads the jet help file """
+ import webbrowser
+ webbrowser.open(JetDefs.MAIN_HELPFILE)
+ return
+
+ def OnHelpJetGuidelines(self, event):
+ """ Loads the authoring guidelines file """
+ import webbrowser
+ webbrowser.open(JetDefs.MAIN_HELPGUIDELINESFILE)
+ return
+
+ def OnAbout(self, event):
+ """ Loads the about dialog box """
+ dlg = JetAbout()
+ result = dlg.ShowModal()
+ dlg.Destroy()
+
+ def OnJetImportArchive(self, event):
+ """ Imports a jet archive file """
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC, 'str', str(os.getcwd()))
+ dialog = wx.FileDialog(None, JetDefs.IMPORT_ARCHIVE_PROMPT, defDir, "", JetDefs.ARCHIVE_FILE_SPEC, wx.OPEN)
+ if dialog.ShowModal() == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC, str(FileJustPath(dialog.GetPath())))
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC + "Dir", 'str', str(os.getcwd()))
+ dlg1 = wx.DirDialog(self, JetDefs.IMPORT_ARCHIVEDIR_PROMPT, style=wx.DD_DEFAULT_STYLE, defaultPath=defDir)
+ if dlg1.ShowModal() == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC + "Dir", str(FileJustPath(dlg1.GetPath())))
+ if YesNo(JetDefs.MAIN_IMPORTTITLE, JetDefs.MAIN_IMPORTMSG % (dialog.GetPath(),dlg1.GetPath()), False):
+ projectPath = dlg1.GetPath()
+ zipFile = dialog.GetPath()
+ z = __import__('zipfile')
+
+ if not z.is_zipfile(zipFile):
+ wx.MessageBox(JetDefs.IMPORT_ARCHIVE_NO_JTC)
+ else:
+ zip = z.ZipFile(zipFile, 'r')
+
+ jtcFile = ""
+ fileList = zip.namelist()
+
+ isArchive = False
+ for myFile in fileList:
+ if myFile == 'JetArchive':
+ isArchive = True
+ break
+ if not isArchive:
+ wx.MessageBox(JetDefs.IMPORT_NOT_JET_ARCHIVE)
+ else:
+ for myFile in fileList:
+ if FileJustExt(myFile) == '.JTC':
+ jtcFile = myFile
+ break
+ if jtcFile == "":
+ wx.MessageBox(JetDefs.IMPORT_ARCHIVE_NO_JTC)
+ else:
+ for name in zip.namelist():
+ ext = FileJustExt(name)
+ if ext == '.MID' or ext == '.DLS' or ext == '.JET':
+ file(FileFixPath(projectPath + "/" + name), 'wb').write(zip.read(name))
+ else:
+ if len(ext) > 0 and ext != '.DS_STORE':
+ file(FileFixPath(projectPath + "/" + name), 'w').write(zip.read(name))
+ zip.close()
+ self.currentJetConfigFile = FileFixPath(projectPath + "/") + jtcFile
+ self.jet_file = JetFile(self.currentJetConfigFile, "")
+
+ #fix paths
+ self.jet_file.config.filename = FileJustRoot(self.currentJetConfigFile) + ".JET"
+ for index, segment in enumerate(self.jet_file.segments):
+ self.jet_file.segments[index].filename = FileFixPath(projectPath + "/" + segment.filename)
+ if segment.dlsfile > "":
+ self.jet_file.segments[index].dlsfile = FileFixPath(projectPath + "/" + segment.dlsfile)
+ self.jet_file.segments[index].output = FileFixPath(projectPath + "/" + segment.output)
+
+ for index, library in enumerate(self.jet_file.libraries):
+ self.jet_file.libraries[index] = FileFixPath(projectPath + "/" + library)
+
+ if ValidateConfig(self.jet_file):
+ self.jet_file.SaveJetConfig(self.currentJetConfigFile)
+ self.jet_file.WriteJetFileFromConfig(self.currentJetConfigFile)
+ self.jet_file = JetFile(self.currentJetConfigFile , "")
+ self.SetCurrentFile(self.currentJetConfigFile)
+
+ dlg1.Destroy()
+ dialog.Destroy()
+
+ def OnJetExportArchive(self, event):
+ """ Exports the current setup to an archive file """
+ self.OnJetSave(event)
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC, 'str', str(os.getcwd()))
+ dialog = wx.FileDialog(None, JetDefs.EXPORT_ARCHIVE_PROMPT, defDir, "", JetDefs.ARCHIVE_FILE_SPEC, wx.SAVE | wx.OVERWRITE_PROMPT )
+ if dialog.ShowModal() == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, JetDefs.ARCHIVE_FILE_SPEC, str(FileJustPath(dialog.GetPath())))
+ ExportJetArchive(FileJustRoot(dialog.GetPath()) + ".zip", self.currentJetConfigFile, self.jet_file)
+ dialog.Destroy()
+
+ def OnCopy(self, event):
+ """ Copies the current segment or event to the clipboard """
+ if self.currentCtrl == JetDefs.MAIN_SEGLIST:
+ if self.currentSegmentName is None:
+ return ""
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return ""
+ self.clipBoard = JetCutCopy(self.currentCtrl, segment, self.currentSegmentName)
+ return self.currentCtrl
+ elif self.currentCtrl == JetDefs.MAIN_EVENTLIST:
+ if self.currentSegmentName is None:
+ return ""
+
+ if self.currentEventName is None:
+ return ""
+
+ segment = self.jet_file.GetSegment(self.currentSegmentName)
+ if segment == None:
+ return ""
+
+ curEvent = self.jet_file.GetEvent(self.currentSegmentName, self.currentEventName)
+ if curEvent == None:
+ return ""
+ self.clipBoard = JetCutCopy(self.currentCtrl, curEvent, self.currentSegmentName)
+ return self.currentCtrl
+
+ def OnCut(self, event):
+ """ Cuts the current segment or event to the clipboard """
+ retCopy = self.OnCopy(event)
+ if retCopy == JetDefs.MAIN_SEGLIST:
+ self.SegmentDelete(self.currentSegmentName)
+ self.LoadSegList()
+ elif retCopy == JetDefs.MAIN_EVENTLIST:
+ self.EventDelete(self.currentEventName)
+ self.LoadEventsForSeg(self.currentSegmentName)
+
+ def OnPaste(self, event):
+ """ Pastes the current segment or event from the clipboard """
+ if self.clipBoard is not None:
+ if self.currentCtrl == JetDefs.MAIN_SEGLIST and self.clipBoard.objType == JetDefs.MAIN_SEGLIST:
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+ self.jet_file.segments.append(self.clipBoard.GetObj(self.jet_file.segments))
+ self.UndoAdd(saveState)
+ self.LoadSegList()
+ elif self.currentCtrl == JetDefs.MAIN_EVENTLIST and self.clipBoard.objType == JetDefs.MAIN_EVENTLIST and self.clipBoard.currentSegmentName == self.currentSegmentName:
+ for segment in self.jet_file.segments:
+ if segment.segname == self.currentSegmentName:
+ saveState = JetState(self.jet_file, self.currentSegmentIndex, self.currentEventIndex)
+ segment.jetevents.append(self.clipBoard.GetObj(segment.jetevents))
+ self.UndoAdd(saveState)
+ self.LoadEventsForSeg(self.currentSegmentName)
+ self.graph.LoadSegment(self.jet_file.GetSegment(self.currentSegmentName), showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def OnJetProperties(self, event):
+ """ Opens a dialog box for jetfile properties """
+ dlg = JetPropertiesDialog()
+ dlg.SetValue(JetDefs.F_JETFILENAME, self.jet_file.config.filename)
+ dlg.SetValue(JetDefs.F_COPYRIGHT, StrNoneChk(self.jet_file.config.copyright))
+ dlg.SetValue(JetDefs.F_CHASECONTROLLERS, StrNoneChk(self.jet_file.config.chase_controllers))
+ dlg.SetValue(JetDefs.F_DELETEEMPTYTRACKS, StrNoneChk(self.jet_file.config.delete_empty_tracks))
+
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ self.jet_file.config.filename = dlg.je.ctrls[JetDefs.F_JETFILENAME].GetValue()
+ self.jet_file.config.copyright = dlg.je.ctrls[JetDefs.F_COPYRIGHT].GetValue()
+ self.jet_file.config.chase_controllers = dlg.je.ctrls[JetDefs.F_CHASECONTROLLERS].GetValue()
+ self.jet_file.config.delete_empty_tracks = dlg.je.ctrls[JetDefs.F_DELETEEMPTYTRACKS].GetValue()
+ dlg.Destroy()
+
+ def OnPreferences(self, event):
+ """ Opens a dialog box to capture preferences and saves them to a configuration file """
+ dlg = JetPreferences()
+ dlg.SetValue(JetDefs.F_COPYRIGHT, IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_COPYRIGHT))
+ dlg.SetValue(JetDefs.F_CHASECONTROLLERS, IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_CHASECONTROLLERS, 'bool', True))
+ dlg.SetValue(JetDefs.F_DELETEEMPTYTRACKS, IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_DELETEEMPTYTRACKS, 'bool', False))
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_COPYRIGHT, dlg.GetValue(JetDefs.F_COPYRIGHT))
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_CHASECONTROLLERS, dlg.GetValue(JetDefs.F_CHASECONTROLLERS))
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_DELETEEMPTYTRACKS, dlg.GetValue(JetDefs.F_DELETEEMPTYTRACKS))
+ dlg.Destroy()
+
+ def LoadDefaultProperties(self):
+ """ Loads default properties from the a configuration file """
+ self.jet_file.config.copyright = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_COPYRIGHT)
+ self.jet_file.config.chase_controllers = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_CHASECONTROLLERS, 'bool', True)
+ self.jet_file.config.delete_empty_tracks = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_PREF_SECTION, JetDefs.F_DELETEEMPTYTRACKS, 'bool', False)
+
+ def OnClose(self, event):
+ """ Called upon closing the JetCreator main window """
+ if self.isDirty():
+ ret = YesNoCancel(JetDefs.MAIN_JETCREATOR, JetDefs.MAIN_SAVEBEFOREEXIT, True)
+ if ret == wx.ID_YES:
+ self.OnJetSave(None)
+ if ret == wx.ID_CANCEL:
+ return
+
+ if self.jet is not None:
+ SafeJetShutdown(self.playerLock, self.jet)
+ self.Destroy()
+
+ def OnPlay(self, event):
+ """ Plays the currently queued segments """
+ if self.btnPlay.GetLabel() == JetDefs.BUT_PLAY:
+ if not ValidateConfig(self.jet_file):
+ return
+
+ #make sure something is queued
+ iQueued = False
+ num = self.segList.GetItemCount()
+ for seg_num in range(num):
+ if self.segList.IsChecked(seg_num):
+ iQueued = True
+ if not iQueued:
+ InfoMsg(JetDefs.MAIN_PLAYSEG, JetDefs.MAIN_PLAYSEGMSG)
+ return
+
+ for segment in self.jet_file.segments:
+ if FileExists(segment.dlsfile):
+ if not segment.dlsfile in self.jet_file.libraries:
+ self.jet_file.libraries.append(segment.dlsfile)
+
+ self.eventList.DeleteAllItems()
+ num = self.segList.GetItemCount()
+ for seg_num in range(num):
+ if seg_num == 0: self.log.Clear()
+ if self.segList.IsChecked(seg_num):
+ segment = self.jet_file.GetSegment(getColumnText(self.segList, seg_num, 0))
+ if segment != None:
+ #so we can determine which segment is playing, make these the same
+ userID = seg_num
+ if FileExists(segment.dlsfile):
+ dls_num = FindDlsNum(self.jet_file.libraries, segment.dlsfile)
+ else:
+ dls_num = -1
+ self.queueSegs.append(QueueSeg(segment.segname, userID, seg_num, dls_num, segment.repeat, segment.transpose, segment.mute_flags))
+
+ if len(self.queueSegs) == 0:
+ return
+
+ self.btnPlay.SetLabel(JetDefs.BUT_STOP)
+
+ thread.start_new_thread(self.PlaySegs, ())
+ else:
+ with self.playerLock:
+ self.jet.Clear_Queue()
+ self.SetKeepPlayingFlag(False)
+
+ def PlaySegs(self):
+ """ Thread writes a temporary copy of the jet file, and calls the library to play it """
+ if len(self.queueSegs) == 0:
+ return
+
+ jet_file = CreateTempJetFile(self.jet_file)
+
+ if not ValidateConfig(jet_file):
+ CleanupTempJetFile(jet_file)
+ return
+
+ self.jet = JET()
+ self.jet.eas.StartWave()
+ self.jet.OpenFile(jet_file.config.filename)
+
+ lastID = -1
+
+ # queue first segment and start playback
+ Queue(self.jet, self.queueSegs[0])
+ index = 1
+ self.jet.Play()
+ self.paused = False
+
+ # continue playing until all segments are done
+ self.SetKeepPlayingFlag(True)
+ while self.GetKeepPlayingFlag():
+ self.jet.Render()
+ status = self.jet.Status()
+
+ if status.currentUserID <> lastID and status.currentUserID <> -1:
+ wx.PostEvent(self, JetStatusEvent(JetDefs.PST_PLAY, status.currentUserID))
+ lastID = status.currentUserID
+
+ # if no more segments - we're done
+ if status.numQueuedSegments == 0:
+ break
+
+ self.jet.GetAppEvent()
+
+ # if less than 2 segs queued - queue another one
+ if (index < len(self.queueSegs)) and (status.numQueuedSegments < 2):
+ Queue(self.jet, self.queueSegs[index])
+ index += 1
+
+ wx.PostEvent(self, JetStatusEvent(JetDefs.PST_UPD_LOCATION, status.location))
+
+
+ SafeJetShutdown(self.playerLock, self.jet)
+
+ self.queueSegs = []
+
+ CleanupTempJetFile(jet_file)
+
+ wx.PostEvent(self, JetStatusEvent(JetDefs.PST_DONE, None))
+
+ def OnJetStatusUpdate(self, evt):
+ """ These are screen updates called for from within the thread. Communication
+ is via a postevent call.
+ """
+ if evt.mode == JetDefs.PST_PLAY:
+ segName = getColumnText(self.segList, evt.data, 0)
+ self.LoadEventsForSeg(segName)
+ self.log.SetValue(segName)
+ ClearRowSelections(self.segList)
+ SetRowSelection(self.segList, evt.data, True)
+ elif evt.mode == JetDefs.PST_UPD_LOCATION:
+ self.graph.UpdateLocation(evt.data)
+ elif evt.mode == 3:
+ self.graph.UpdateLocation(0)
+ ClearRowSelections(self.segList)
+ self.eventList.DeleteAllItems()
+ self.SetKeepPlayingFlag(False)
+ self.btnPlay.SetLabel(JetDefs.BUT_PLAY)
+ self.btnPause.SetLabel(JetDefs.BUT_PAUSE)
+
+ def OnPause(self, evt):
+ """ Pauses the playback """
+ if self.jet is None:
+ return
+ if not self.paused:
+ self.jet.Pause()
+ self.paused = True
+ self.btnPause.SetLabel(JetDefs.BUT_RESUME)
+ else:
+ self.jet.Play()
+ self.paused = False
+ self.btnPause.SetLabel(JetDefs.BUT_PAUSE)
+
+ def isDirty(self):
+ if len(self.UndoStack) == 0 and len(self.RedoStack) == 0:
+ return False
+ else:
+ return True
+
+ def OnSetGraphOptions(self, evt):
+ """ Sets graph options """
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, self.chkGraphLabels.GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, self.chkGraphClips.GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, self.chkGraphAppEvts.GetValue())
+ self.graph.LoadSegment(self.jet_file.GetSegment(self.currentSegmentName), showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def OnEventSortOrderChanged(self):
+ """ Called when sort order has changed """
+ IniSetValue(self.currentJetConfigFile, JetDefs.INI_EVENTSORT, JetDefs.INI_EVENTSORT_0, self.eventList.GetSortState()[0])
+ IniSetValue(self.currentJetConfigFile, JetDefs.INI_EVENTSORT, JetDefs.INI_EVENTSORT_1, self.eventList.GetSortState()[1])
+ self.LoadEventsForSeg(self.currentSegmentName)
+ self.graph.LoadSegment(self.jet_file.GetSegment(self.currentSegmentName), showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def EventSortCmp(self, a, b):
+ """ Sorts based on selected sort order """
+ if self.eventlistSort[0] == 0 or self.eventlistSort[0] == 1:
+ if self.eventlistSort[1] == 1:
+ return cmp(a[0].upper(), b[0].upper())
+ else:
+ return cmp(b[0].upper(), a[0].upper())
+ elif self.eventlistSort[0] == 2 or self.eventlistSort[0] == 3:
+ if self.eventlistSort[1] == 1:
+ return cmp(MbtVal(a[0]), MbtVal(b[0]))
+ else:
+ return cmp(MbtVal(b[0]), MbtVal(a[0]))
+ else:
+ return cmp(a[0], b[0])
+
+ def EventSort2(self, seq, attr):
+ """ Does Sorting """
+ intermed = map(None, map(getattr, seq, (attr,)*len(seq)), xrange(len(seq)), seq)
+ intermed.sort(self.EventSortCmp)
+ return map(operator.getitem, intermed, (-1,) * len(intermed))
+
+ def EventSort(self, lst, attr):
+ """ Does Sorting """
+ lst[:] = self.EventSort2(lst, attr)
+
+ def OnSegSortOrderChanged(self):
+ """ Called when sort order has changed """
+ IniSetValue(self.currentJetConfigFile, JetDefs.INI_SEGSORT, JetDefs.INI_SEGSORT_0, self.segList.GetSortState()[0])
+ IniSetValue(self.currentJetConfigFile, JetDefs.INI_SEGSORT, JetDefs.INI_SEGSORT_1, self.segList.GetSortState()[1])
+ self.LoadEventsForSeg(self.currentSegmentName)
+ self.graph.LoadSegment(self.jet_file.GetSegment(self.currentSegmentName), showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def SegSortCmp(self, a, b):
+ """ Sorts based on selected sort order """
+ if self.seglistSort[0] == 0:
+ if self.seglistSort[1] == 1:
+ return cmp(a[0].upper(), b[0].upper())
+ else:
+ return cmp(b[0].upper(), a[0].upper())
+ elif self.seglistSort[0] == 1 or self.seglistSort[0] == 2:
+ if self.seglistSort[1] == 1:
+ return cmp(FileJustName(a[0]).upper(), FileJustName(b[0]).upper())
+ else:
+ return cmp(FileJustName(b[0]).upper(), FileJustName(a[0]).upper())
+ elif self.seglistSort[0] == 3 or self.seglistSort[0] == 4:
+ if self.seglistSort[1] == 1:
+ return cmp(MbtVal(a[0]), MbtVal(b[0]))
+ else:
+ return cmp(MbtVal(b[0]), MbtVal(a[0]))
+ else:
+ return cmp(a[0], b[0])
+
+ def SegSort2(self, seq, attr):
+ """ Does Sorting """
+ intermed = map(None, map(getattr, seq, (attr,)*len(seq)), xrange(len(seq)), seq)
+ intermed.sort(self.SegSortCmp)
+ return map(operator.getitem, intermed, (-1,) * len(intermed))
+
+ def SegSort(self, lst, attr):
+ """ Does Sorting """
+ lst[:] = self.SegSort2(lst, attr)
+
+if __name__ == '__main__':
+ """ Sets the logging level, then calls the open dialog box before initializing main window"""
+
+ logLevel = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_LOGGING, JetDefs.INI_LOGGING)
+ if logLevel == 'DEBUG':
+ logging.basicConfig(level=logging.DEBUG, format='%(funcName)s[%(lineno)d]: %(message)s')
+ elif logLevel == 'INFO':
+ logging.basicConfig(level=logging.INFO, format='%(funcName)s[%(lineno)d]: %(message)s')
+ elif logLevel == 'ERROR':
+ logging.basicConfig(level=logging.ERROR, format='%(funcName)s[%(lineno)d]: %(message)s')
+ elif logLevel == 'WARNING':
+ logging.basicConfig(level=logging.WARNING, format='%(funcName)s[%(lineno)d]: %(message)s')
+ else:
+ install_release_loggers()
+
+ app = wx.App(None)
+
+ if not os.path.isdir(JetDefs.TEMP_JET_DIR):
+ os.mkdir(JetDefs.TEMP_JET_DIR)
+
+ openFile = ""
+ dlg = JetOpen()
+ result = dlg.ShowModal()
+ if result == wx.ID_CANCEL:
+ dlg.Destroy()
+ elif result == JetDefs.ID_JET_IMPORT:
+ dlg.Destroy()
+
+ au = JetCreator(None, -1, "", importFlag=True)
+ app.MainLoop()
+ else:
+ openFile = dlg.fileName
+ dlg.Destroy()
+
+ au = JetCreator(None, -1, openFile)
+ app.MainLoop()
+
diff --git a/jet_tools/JetCreator/JetCreatorhlp.dat b/jet_tools/JetCreator/JetCreatorhlp.dat
new file mode 100755
index 0000000..4a08b07
--- /dev/null
+++ b/jet_tools/JetCreator/JetCreatorhlp.dat
@@ -0,0 +1,119 @@
+[EVTDLG_CTRLS]
+labels =
+play = Plays the segment with associated event attributes. To trigger the event, click the event in the timeline.
+midi file = Displays the MIDI file associated with the segment. This is pulled in from the segment attributes.
+quantize = Displays the quantization associated with the segment. This is pulled in from the segment attributes.
+cancel =
+event type = Selects the type of trigger event.
+track mutes = Displays the track mute assignments associated with the segment. This is pulled in from the segment attributes.
+event =
+pause =
+graph =
+trigger =
+replicate =
+starting m/b/t = Sets the starting measure, beat, and tick for the event.
+channel = Selects the MIDI channel for the event.
+eventid = Sets the ID of the event. Each event must have a unique ID.
+app events =
+repeat = Displays the repeat count of the segment. This is pulled in from the segment attributes.
+ok =
+track = Selects the MIDI track (not the channel) for the event.
+transpose = Displays the transpose value of the segment. This is pulled in from the segment attributes.
+un-mute =
+segment = Displays the name of the segment. This is pulled in from the segment attributes.
+event name = Identifies the name of the event.
+segment name = Identifies the name of the segment. This is pulled in from the segment attributes.
+trigger clips =
+length =
+ending m/b/t = Sets the ending measure, beat, and tick for the segment.
+dls file = Displays the DLS file associated with the segment. This is pulled in from the segment attributes.
+
+[MOVE_CTRLS]
+ok =
+move =
+starting m/b/t =
+cancel =
+preview =
+increment m/b/t =
+
+[MAIN_DIALOG_CTRLS]
+seglist = The list of segments display here. You may add, revise, or delete segments using the buttons at the left. You may also double click to edit a segment.
+eventlist = The list of events for the currently selected segment display here. Double click to edit, or use the buttons at the left to add, revise, or delete and event.
+
+[SEGDLG_CTRLS]
+labels =
+app events =
+midi file = Selects the source MIDI file from which the segment will be created.
+quantize = Selects the quantization amount for the segment. Use 0 for no quantization.
+display empty tracks =
+play segment =
+cancel = Cancels any changes and exits the segment window.
+track mutes = Checking a mute box will mute the associated track. You should mute a track if you intent to add a trigger event for that track.
+pause =
+graph =
+replicate =
+starting m/b/t = Sets the starting measure, beat, and tick for the segment.
+play midi file =
+play = Plays the segment.
+repeat = Sets the number of repeats for the segment.
+graph type =
+transpose = Sets the transposition value for the segment, in semitones (half-steps).
+audition = Click this button to audition the JET file. This is an interactive playback mode that simulates a how the JET file will work in production.
+ok = Confirms segment attributes and closes segment window.
+segment =
+segment name = Identifies the name of the segment.
+trigger clips =
+length =
+ending m/b/t = Sets the ending measure, beat, and tick for the segment.
+dls file = Selects the source DLS file, if any, for the given segment.
+
+[PREFERENCES_CTRLS]
+ok =
+preferences =
+copyright = The copyright in this field will default on new JET files. It can be overridden in the Properties setting for a specific JET file.
+chase controllers = This option will force JET to chase controllers up to the beginning of a given segment. This help keep a segment in sync with its parent MIDI file.
+cancel =
+delete empty tracks = This option will tell JET to delete any empty tracks from the output file.
+
+[REPLICATE_CTRLS]
+ok =
+replace existing items matching prefix =
+replicate =
+number =
+name prefix =
+starting m/b/t =
+cancel =
+preview =
+increment m/b/t =
+
+[JET_PROPERTIES_CTRLS]
+omit empty tracks =
+jet project properties =
+ok =
+copyright =
+chase controllers = If on, MIDI CC messages and program changes will be read from the beginning of the segment when playing or triggering events in the middle of the segment. This should almost always be checked.
+jet file =
+export = Export will collect all the source MIDI, DLS and JET files into a single .ZIP file.
+cancel =
+delete empty tracks = Selecting this will delete any empty MIDI tracks.
+
+[AUDITION_CTRLS]
+seglist = The list of segments in the JET file display here. Double click on a segment to add it to the play queue, or use the buttons below.
+tracklist = The tracks in the segment currently playing segment display here. Tracks which are muted are checked. Click on a track to toggle its mute status.
+queuelist = The currently queued segments display here. Each segment displays its current run status. You may modify the playback state with the buttons below.
+graph = The currently playing segment is displayed graphically here. You may trigger clips by clicking within their boundaries on the graph.
+
+[EXPORT_CTRLS]
+omit empty tracks = Selecting this will delete any empty MIDI tracks.
+copyright =
+chase controllers = If on, MIDI CC messages and program changes will be read from the beginning of the segment when playing or triggering events in the middle of the segment. This should almost always be checked.
+jet file =
+export = Export will collect all the source MIDI, DLS and JET files into a single .ZIP file.
+cancel =
+
+[PREFERENCES]
+cancel =
+ok =
+preferences =
+use project directories =
+
diff --git a/jet_tools/JetCreator/JetCtrls.py b/jet_tools/JetCreator/JetCtrls.py
new file mode 100755
index 0000000..9b3ca5d
--- /dev/null
+++ b/jet_tools/JetCreator/JetCtrls.py
@@ -0,0 +1,567 @@
+"""
+ File:
+ JetCtrls.py
+
+ Contents and purpose:
+ Auditions a jet file to simulate interactive music functions
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import wx
+import sys
+
+from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin, ColumnSorterMixin
+from JetUtils import *
+from JetDefs import *
+
+class JetSpin(wx.SpinCtrl):
+ """ Spin control """
+ def __init__(self, parent, id=-1,value=wx.EmptyString,pos=wx.DefaultPosition,size=wx.DefaultSize,style=wx.SP_ARROW_KEYS,min=0,max=100,initial=0):
+ wx.SpinCtrl.__init__(self, parent, id=id,value=value,pos=(pos[0]-MacOffset(),pos[1]),size=size,style=style,min=min,max=max,initial=initial)
+
+ def SetValue(self, val):
+ try:
+ if type(val).__name__=='str':
+ wx.SpinCtrl.SetValue(self, int(val))
+ else:
+ wx.SpinCtrl.SetValue(self, val)
+ except:
+ wx.SpinCtrl.SetValue(self, 0)
+
+class JetSpinOneBased(JetSpin):
+ """ Spin control that's one based """
+ def __init__(self, parent, id=-1,value=wx.EmptyString,pos=wx.DefaultPosition,size=wx.DefaultSize,style=wx.SP_ARROW_KEYS,min=0,max=100,initial=0):
+ wx.SpinCtrl.__init__(self, parent, id=id,value=value,pos=(pos[0]-MacOffset(),pos[1]),size=size,style=style,min=min,max=max,initial=initial)
+
+ def SetValue(self, val):
+ try:
+ if type(val).__name__=='str':
+ wx.SpinCtrl.SetValue(self, int(val) + 1)
+ else:
+ wx.SpinCtrl.SetValue(self, val + 1)
+ except:
+ wx.SpinCtrl.SetValue(self, 1)
+
+ def GetValue(self):
+ val = wx.SpinCtrl.GetValue(self)
+ val = val - 1
+ return val
+
+class JetCheckBox(wx.CheckBox):
+ """ Checkbox control """
+ def __init__(self, parent, id=-1,label=wx.EmptyString,pos=wx.DefaultPosition,size=wx.DefaultSize):
+ wx.CheckBox.__init__(self, parent, id=id, label=label, pos=pos, size=size)
+
+ def SetValue(self, val):
+ try:
+ if type(val).__name__=='str':
+ if val == 'True':
+ val = True
+ else:
+ val = False
+ wx.CheckBox.SetValue(self, val)
+ else:
+ wx.CheckBox.SetValue(self, val)
+ except:
+ wx.CheckBox.SetValue(self, False)
+
+class JetRadioButton(wx.RadioButton):
+ """ Radio button control """
+ def __init__(self, parent, id=-1,label=wx.EmptyString,pos=wx.DefaultPosition,size=wx.DefaultSize):
+ wx.RadioButton.__init__(self, parent, id=id, label=label, pos=pos, size=size)
+
+ def SetValue(self, val):
+ try:
+ if type(val).__name__=='str':
+ if val == 'True':
+ val = True
+ else:
+ val = False
+ wx.RadioButton.SetValue(self, val)
+ else:
+ wx.RadioButton.SetValue(self, val)
+ except:
+ wx.RadioButton.SetValue(self, False)
+
+class JetListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin):
+ """ List control """
+ def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize):
+ wx.ListCtrl.__init__(self, parent, id, pos=pos, size=size, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
+ ListCtrlAutoWidthMixin.__init__(self)
+ self.iCol = 0
+ self.iWidth = 0
+ self.OnSortOrderChangedAlert = None
+ self.iInitialized = False
+
+ def AddCol(self, title, width):
+ self.InsertColumn(self.iCol, title)
+ if width > 0:
+ self.SetColumnWidth(self.iCol, width)
+ else:
+ width = self.GetColumnWidth(self.iCol)
+ self.iCol += 1
+ self.iWidth = self.iWidth + width
+ self.SetSize((self.iWidth + 10, -1))
+
+ def AddRows(self, values):
+ for value in values:
+ iCol = 0
+ for row in value:
+ if iCol == 0:
+ index = self.InsertStringItem(sys.maxint, row)
+ else:
+ self.SetStringItem(index, iCol, row)
+ iCol = iCol + 1
+
+ # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+ def GetListCtrl(self):
+ return self
+
+ def InitSorting(self, cols):
+ if not self.iInitialized:
+ ColumnSorterMixin.__init__(self, cols)
+ self.iInitialized = True
+
+ def OnSortOrderChanged(self):
+ if self.OnSortOrderChangedAlert is not None:
+ self.OnSortOrderChangedAlert()
+
+ def __OnColClick(self, evt):
+ oldCol = self._col
+ self._col = col = evt.GetColumn()
+ self._colSortFlag[col] = int(not self._colSortFlag[col])
+ self.OnSortOrderChanged()
+
+class JetCheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin, ColumnSorterMixin):
+ """ List control with checkboxes on each line """
+ def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT | wx.SUNKEN_BORDER):
+ wx.ListCtrl.__init__(self, parent, id, pos=pos, size=size, style=style)
+ CheckListCtrlMixin.__init__(self)
+ ListCtrlAutoWidthMixin.__init__(self)
+
+ self.iCol = 0
+ self.iWidth = 0
+ self.OnSortOrderChangedAlert = None
+ self.iInitialized = False
+
+ def AddCol(self, title, width):
+ self.InsertColumn(self.iCol, title)
+ if width > 0:
+ self.SetColumnWidth(self.iCol, width)
+ else:
+ width = self.GetColumnWidth(self.iCol)
+ self.iCol += 1
+ self.iWidth = self.iWidth + width
+ self.SetSize((self.iWidth + 10, -1))
+
+ def OnCheckItem(self, index, flag):
+ if hasattr(self, 'BindCheckBoxFct'):
+ self.BindCheckBoxFct(index, flag)
+
+ def BindCheckBox(self, fct):
+ self.BindCheckBoxFct = fct
+
+ def AddRows(self, values):
+ for value in values:
+ iCol = 0
+ for row in value:
+ if iCol == 0:
+ index = self.InsertStringItem(sys.maxint, row)
+ else:
+ self.SetStringItem(index, iCol, row)
+ iCol = iCol + 1
+
+ # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+ def GetListCtrl(self):
+ return self
+
+ def InitSorting(self, cols):
+ if not self.iInitialized:
+ ColumnSorterMixin.__init__(self, cols)
+ self.iInitialized = True
+
+ def OnSortOrderChanged(self):
+ if self.OnSortOrderChangedAlert is not None:
+ self.OnSortOrderChangedAlert()
+
+ def __OnColClick(self, evt):
+ oldCol = self._col
+ self._col = col = evt.GetColumn()
+ self._colSortFlag[col] = int(not self._colSortFlag[col])
+ self.OnSortOrderChanged()
+
+class JetTrackCtrl(JetCheckListCtrl):
+ """ List control specifically designed to show tracks in midi file """
+ def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT | wx.SUNKEN_BORDER):
+ wx.ListCtrl.__init__(self, parent, id, pos=pos, size=size, style=style)
+ CheckListCtrlMixin.__init__(self)
+ ListCtrlAutoWidthMixin.__init__(self)
+
+ self.iCol = 0
+ self.iWidth = 0
+ self.muteFlags = 0
+
+ def SetValue(self, muteFlags):
+ self.muteFlags = muteFlags
+
+ def GetValue(self):
+ return self.muteFlags
+
+ def CheckTracks(self, muteFlags):
+ num = self.GetItemCount()
+ for iRow in range(num):
+ track_num = self.GetTrackNumber(iRow)
+ self.CheckItem(iRow, GetMute(track_num, muteFlags))
+
+ def AddTrackRow(self, track, loadEmpty=False):
+ if loadEmpty or not track.empty:
+ index = self.InsertStringItem(sys.maxint, str(track.track))
+ self.SetStringItem(index, 1, str(track.channel))
+ self.SetStringItem(index, 2, str(track.name))
+
+ def GetTrackNumber(self, index):
+ return getColumnValue(self, index, 0)
+
+class JetFileCombo():
+ """ Combo box with file open button """
+ def __init__(self, parent, pos=(0,0), size=(200,-1), title='Open File', spec='*.*', id=-1):
+ self.spec = spec
+ self.title = title
+ self.EventFire = False
+ BUTWIDTH = 20
+ BORDER = 5
+ w = size[0] - (BUTWIDTH + BORDER)
+ col = pos[0] + w + BORDER
+
+ self.cmb = wx.ComboBox(parent, id, "", pos=(pos[0]-MacOffset(),pos[1]), size=(w, -1), style=wx.CB_DROPDOWN)
+ self.btn = wx.Button(parent, -1, "...", pos=(col, pos[1]+MacOffset()), size=(BUTWIDTH,self.cmb.GetSize()[1]))
+ self.btn.Bind(wx.EVT_BUTTON, self.OnBrowse, self.btn)
+
+ def OnBrowse(self, event):
+ os = __import__('os')
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, self.spec, 'str', str(os.getcwd()))
+ if OsWindows():
+ defDir = defDir.replace('/','\\')
+ else:
+ defDir = defDir.replace('\\', '/')
+
+ dlg = wx.FileDialog(None, self.title, defDir, '', self.spec, wx.FD_OPEN)
+ ret = dlg.ShowModal()
+ if ret == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, self.spec, str(FileJustPath(dlg.GetPath())))
+ val = dlg.GetPath()
+
+ self.Append(val)
+ self.cmb.SetValue(val)
+
+ if self.EventFire:
+ SendEvent(self.cmb, wx.EVT_COMBOBOX.evtType[0])
+ dlg.Destroy()
+
+ def SetEventFire(self, fire):
+ self.EventFire = fire
+
+ def GetValue(self):
+ return StrNoneChk(self.cmb.GetValue())
+
+ def SetValue(self, val):
+ try:
+ self.cmb.SetValue(val)
+ except:
+ pass
+
+ def Append(self, val):
+ try:
+ self.cmb.Append(val)
+ except:
+ pass
+
+ def SetFocus(self):
+ self.cmb.SetFocus()
+
+ def SetListValues(self, list):
+ self.cmb.AppendItems(list)
+
+ def Enable(self, enable):
+ self.cmb.Enable(enable)
+ self.btn.Enable(enable)
+
+ def SetHelpText(self, Lbl):
+ self.cmb.SetHelpText(Lbl)
+ self.btn.SetHelpText(Lbl)
+
+class JetFileText():
+ """ Capture a filename with a button to browse for a file """
+ def __init__(self, parent, pos=(0,0), size=(200,-1), title='Open File', spec='*.*', id=-1):
+ self.spec = spec
+ self.title = title
+ BUTWIDTH = 20
+ BORDER = 5
+ w = size[0] - (BUTWIDTH + BORDER)
+ col = pos[0] + w + BORDER
+
+ self.txt = wx.TextCtrl(parent, id, "", pos=(pos[0]-MacOffset(),pos[1]), size=(w, -1))
+ self.btn = wx.Button(parent, -1, "...", pos=(col, pos[1]), size=(BUTWIDTH,self.txt.GetSize()[1]))
+ self.btn.Bind(wx.EVT_BUTTON, self.OnBrowse, self.btn)
+
+ def OnBrowse(self, event):
+ os = __import__('os')
+ defDir = IniGetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, self.spec, 'str', str(os.getcwd()))
+ if OsWindows():
+ defDir = defDir.replace('/','\\')
+ else:
+ defDir = defDir.replace('\\', '/')
+
+ dlg = wx.FileDialog(None, self.title, defDir, '', self.spec, wx.FD_OPEN)
+ ret = dlg.ShowModal()
+ if ret == wx.ID_OK:
+ IniSetValue(JetDefs.JETCREATOR_INI, JetDefs.INI_DEFAULTDIRS, self.spec, str(FileJustPath(dlg.GetPath())))
+ val = dlg.GetPath()
+ self.txt.SetValue(val)
+ dlg.Destroy()
+
+ def GetValue(self):
+ return StrNoneChk(self.txt.GetValue())
+
+ def SetValue(self, val):
+ try:
+ self.txt.SetValue(val)
+ except:
+ pass
+
+ def Append(self, val):
+ try:
+ self.txt.Append(val)
+ except:
+ pass
+
+ def SetFocus(self):
+ self.txt.SetFocus()
+
+ def Enable(self, enable):
+ self.txt.Enable(enable)
+ self.btn.Enable(enable)
+
+ def SetHelpText(self, Lbl):
+ self.txt.SetHelpText(Lbl)
+ self.btn.SetHelpText(Lbl)
+
+def YesNo(title, question, default):
+ """ Simple Yes/No question box """
+ dlg = wx.MessageDialog(None, question, title, wx.YES_NO | wx.ICON_QUESTION)
+ if dlg.ShowModal() == wx.ID_YES:
+ result = True
+ else:
+ result = False
+ dlg.Destroy()
+ return result
+
+def YesNoCancel(title, question, default):
+ """ Simple Yes/No question box """
+ dlg = wx.MessageDialog(None, question, title, wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION)
+ result = dlg.ShowModal()
+ dlg.Destroy()
+ return result
+
+def ErrorMsg(title, message):
+ """ Dipslay an error message """
+ dlg = wx.MessageDialog(None, message, title, wx.ICON_ERROR)
+ dlg.ShowModal()
+ dlg.Destroy()
+
+def InfoMsg(title, message):
+ """ Displays an informational message """
+ dlg = wx.MessageDialog(None, message, title, wx.ICON_INFORMATION)
+ dlg.ShowModal()
+ dlg.Destroy()
+
+class TimeCtrl(wx.Frame):
+ """ Combination of controls to capture measure, beat, tick times """
+ def __init__(self, parent, pos=(0,0), minimums=(1,1,0), maximums=(999,4,480), value=JetDefs.MBT_DEFAULT, ctlName=''):
+ wx.Frame.__init__(self, parent, -1)
+
+ self.ChangeCallbackFct = None
+ self.ctlName = ctlName
+ self.mx = maximums
+ self.mn = minimums
+ self.maxTicks = 0
+ self.iCtrl = 0
+ p1 = pos[0]
+ top = pos[1] + MacOffset()
+ w1 = 30
+ self.time = (wx.TextCtrl(parent, -1, str(value[0]), pos=(p1, top), size=(w1, -1), style=wx.TE_NOHIDESEL),
+ wx.TextCtrl(parent, -1, str(value[1]), pos=(p1 + (w1 + 3), top), size=(w1, -1), style=wx.TE_NOHIDESEL),
+ wx.TextCtrl(parent, -1, str(value[2]), pos=(p1 + (w1 + 3) *2, top), size=(w1, -1), style=wx.TE_NOHIDESEL),
+ )
+ h = self.time[2].GetSize().height
+ w = self.time[2].GetSize().width + self.time[2].GetPosition().x + 8
+
+ self.spin = wx.SpinButton(parent, -1, (w, top), (h*2/3, h), wx.SP_VERTICAL)
+ self.spin.SetValue(1)
+ self.spin.SetRange(-999,999)
+
+ self.spin.Bind(wx.EVT_SPIN_UP, self.OnSpinUp, self.spin)
+ self.spin.Bind(wx.EVT_SPIN_DOWN, self.OnSpinDown, self.spin)
+
+ self.time[0].Bind(wx.EVT_SET_FOCUS, self.OnFocusMeasure, self.time[0] )
+ self.time[1].Bind(wx.EVT_SET_FOCUS, self.OnFocusBeat, self.time[1] )
+ self.time[2].Bind(wx.EVT_SET_FOCUS, self.OnFocusTick, self.time[2] )
+
+ self.time[0].Bind(wx.EVT_KILL_FOCUS, self.OnChangeVal, self.time[0] )
+ self.time[1].Bind(wx.EVT_KILL_FOCUS, self.OnChangeVal, self.time[1] )
+ self.time[2].Bind(wx.EVT_KILL_FOCUS, self.OnChangeVal, self.time[2] )
+
+ self.SetValue(value)
+
+ def UnBindKillFocus(self):
+ self.time[0].Unbind(wx.EVT_KILL_FOCUS, self.time[0])
+ self.time[1].Unbind(wx.EVT_KILL_FOCUS, self.time[1])
+ self.time[2].Unbind(wx.EVT_KILL_FOCUS, self.time[2])
+
+ def SetChangeCallbackFct(self, ChangeCallbackFct):
+ self.ChangeCallbackFct = ChangeCallbackFct
+
+ def OnChangeVal(self, event=None):
+ if not OsWindows():
+ self.time[self.iCtrl].SetSelection(-1,-1)
+ if int(self.time[self.iCtrl].GetValue()) > self.mx[self.iCtrl]:
+ self.time[self.iCtrl].SetValue(str(self.mx[self.iCtrl]))
+ if int(self.time[self.iCtrl].GetValue()) < self.mn[self.iCtrl]:
+ self.time[self.iCtrl].SetValue(str(self.mn[self.iCtrl]))
+ if self.ChangeCallbackFct is not None:
+ self.ChangeCallbackFct()
+ if event is not None:
+ event.Skip()
+
+ def OnSpinUp(self, event):
+ if int(self.time[self.iCtrl].GetValue()) < self.mx[self.iCtrl]:
+ self.time[self.iCtrl].SetValue(str(int(self.time[self.iCtrl].GetValue()) + 1))
+ self.OnChangeVal()
+
+ def OnSpinDown(self, event):
+ if int(self.time[self.iCtrl].GetValue()) > self.mn[self.iCtrl]:
+ self.time[self.iCtrl].SetValue(str(int(self.time[self.iCtrl].GetValue()) - 1))
+ self.OnChangeVal()
+
+ def OnFocusMeasure(self, event):
+ self.iCtrl = 0
+
+ def OnFocusBeat(self, event):
+ self.iCtrl = 1
+
+ def OnFocusTick(self, event):
+ self.iCtrl = 2
+
+ def SetValue(self, mbt):
+ try:
+ if type(mbt).__name__=='str' or type(mbt).__name__=='unicode':
+ mbt = ConvertStrTimeToTuple(mbt)
+ mbt = mbtFct(mbt, 1)
+ self.time[0].SetValue(str(mbt[0]))
+ self.time[1].SetValue(str(mbt[1]))
+ self.time[2].SetValue(str(mbt[2]))
+ except:
+ self.time[0].SetValue(str(self.mn[0]))
+ self.time[1].SetValue(str(self.mn[1]))
+ self.time[2].SetValue(str(self.mn[2]))
+ if not OsWindows():
+ self.time[0].SetSelection(-1,-1)
+ self.time[1].SetSelection(-1,-1)
+ self.time[2].SetSelection(-1,-1)
+
+ def GetValue(self, typ='str'):
+ try:
+ if typ == 'str':
+ ret = "%d:%d:%d" % (int(self.time[0].GetValue()), int(self.time[1].GetValue()), int(self.time[2].GetValue()))
+ else:
+ ret = (int(self.time[0].GetValue()), int(self.time[1].GetValue()), int(self.time[2].GetValue()))
+ except:
+ ret = self.minimums
+ return mbtFct(ret, -1)
+
+ def Enable(self, enable):
+ self.time[0].Enable(enable)
+ self.time[1].Enable(enable)
+ self.time[2].Enable(enable)
+ self.spin.Enable(enable)
+
+ def SetFocus(self):
+ self.time[0].SetFocus()
+
+ def SetMaxMbt(self, m, b, t):
+ self.mx = (m,b,t)
+
+ def GetMaxMbt(self):
+ return "%d:%d:%d" % self.mx
+
+ def SetMinMbt(self, m, b, t):
+ self.mn = (m,b,t)
+
+ def SetMaxTicks(self, maxTicks):
+ self.maxTicks = maxTicks
+
+ def GetMaxTicks(self):
+ return self.maxTicks
+
+ def SetHelpText(self, Lbl):
+ self.spin.SetHelpText(Lbl)
+ self.time[0].SetHelpText(Lbl)
+ self.time[1].SetHelpText(Lbl)
+ self.time[2].SetHelpText(Lbl)
+
+ def GetMeasure(self):
+ return int(self.time[0].GetValue())
+
+ def GetBeat(self):
+ return int(self.time[1].GetValue())
+
+ def GetTick(self):
+ return int(self.time[2].GetValue())
+
+if __name__ == '__main__':
+ """ Test code for controls """
+ class TestFrame(wx.Frame):
+ def __init__(self, parent, id, title):
+ wx.Frame.__init__(self, parent, id, title, size=(350, 220))
+
+ panel = wx.Panel(self, -1)
+
+ self.tc = TimeCtrl(panel, pos=(30, 20), maximums=(25, 4, 120), value=(2, 3, 4))
+ #tc.Enable(True)
+ #tc.SetValue((2,3,4))
+ #tc.SetValue("1:2:3")
+ #print(tc.GetValue())
+
+ js = JetSpin(panel, -1, pos=(30, 100))
+ js.SetValue("1")
+ #js.SetValue(1)
+
+ #fl = JetFileCombo(panel)
+
+ wx.EVT_CLOSE(self, self.OnClose)
+
+ self.Centre()
+ self.Show(True)
+
+ def OnClose(self, event):
+ self.tc.UnBindKillFocus()
+ self.Destroy()
+
+ app = wx.App(None)
+ TestFrame(None, -1, 'TestFrame')
+ app.MainLoop()
+
+
diff --git a/jet_tools/JetCreator/JetDebug.py b/jet_tools/JetCreator/JetDebug.py
new file mode 100755
index 0000000..dcff7a6
--- /dev/null
+++ b/jet_tools/JetCreator/JetDebug.py
@@ -0,0 +1,71 @@
+"""
+ File:
+ JetDebug.py
+
+ Contents and purpose:
+ Dumps info from various jet file structures for debugging
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from JetUtils import *
+
+def DumpEvent(evt):
+ print("event_name: %s" % evt.event_name)
+ print("event_type: %s" % evt.event_type)
+ print("event_id: %d" % evt.event_id)
+ print("track_num: %d" % evt.track_num)
+ print("channel_num: %d" % evt.channel_num)
+ print("event_start: %s" % evt.event_start)
+ print("event_end: %s" % evt.event_end)
+
+def DumpQueueSeg(queueSeg):
+ print("name: %s" % queueSeg.name)
+ print("userID: %d" % queueSeg.userID)
+ print("seg_num: %d" % queueSeg.seg_num)
+ print("dls_num: %d" % queueSeg.dls_num)
+ print("repeat: %d" % queueSeg.repeat)
+ print("transpose: %d" % queueSeg.transpose)
+ print("mute_flags: %d" % queueSeg.mute_flags)
+
+def DumpSegments1(segments):
+ for segment in segments:
+ DumpSegment(segment)
+
+def DumpSegment(segment):
+ print("userID: %d" % segment.userID)
+ print("name: %s" % segment.name)
+ print("seg_num: %d" % segment.seg_num)
+ print("dls_num: %d" % segment.dls_num)
+ print("repeat: %d" % segment.repeat)
+ print("transpose: %d" % segment.transpose)
+ print("mute_flags: %d" % segment.mute_flags)
+
+def DumpSegments(segments):
+ for segment in segments:
+ DumpSegment1(segment)
+
+def DumpSegment1(segment):
+ print(segment.segname)
+ print(segment.filename)
+ print(segment.start)
+ print(segment.end)
+ print(segment.length)
+ print(segment.output)
+ print(segment.quantize)
+ print(segment.dlsfile)
+ print(segment.dump_file)
+
+
diff --git a/jet_tools/JetCreator/JetDefs.py b/jet_tools/JetCreator/JetDefs.py
new file mode 100755
index 0000000..b1e73cd
--- /dev/null
+++ b/jet_tools/JetCreator/JetDefs.py
@@ -0,0 +1,560 @@
+"""
+ File:
+ JetDefs.py
+
+ Contents and purpose:
+ Holds definitions used throughout JetCreator
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import wx
+
+class JetDefs():
+ def CreateHelpIniFile(self):
+ """ Used to create the help data file for context sensitive help """
+ self.CreateHelpIniForDialog("SEGDLG_CTRLS")
+ self.CreateHelpIniForDialog("EVTDLG_CTRLS")
+ self.CreateHelpIniForDialog("PREFERENCES_CTRLS")
+ self.CreateHelpIniForDialog("JET_PROPERTIES_CTRLS")
+ self.CreateHelpIniForDialog("REPLICATE_CTRLS")
+ self.CreateHelpIniForDialog("MOVE_CTRLS")
+
+ def CreateHelpIniForDialog(self, dlgName):
+ """ Used to create the help data file for context sensitive help """
+ print("\n" + dlgName)
+ lst = getattr(self, dlgName)
+ u = __import__('JetUtils')
+ for ctrl in lst:
+ fld = ctrl[0]
+ if fld[0:2] != "fr":
+ if u.IniGetValue(self.JETCREATOR_HLP, dlgName, fld) == "":
+ u.IniSetValue(self.JETCREATOR_HLP, dlgName, fld, "")
+ print(fld)
+
+ DEFAULT_MUTE_SYNC = False
+
+ TEMP_JET_DIR = "./Tmp/"
+ TEMP_JET_CONFIG_FILE = "./Tmp/Temp.jtc"
+ UNTITLED_FILE = "Untitled.jtc"
+ JETCREATOR_INI = "JetCreator.ini"
+ JETMIDIFILES_INI = "JetMidiFiles.ini"
+ JETCREATOR_HLP = "JetCreatorhlp.dat"
+
+ #Postevent message defines
+ PST_UPD_LOCATION = 1
+ PST_PLAY = 2
+ PST_DONE = 3
+ PST_PAUSE = 4
+ PST_RESUME = 5
+ PST_MIDI_INFO = 6
+
+ #Dialog titles and prompts
+ DLG_JETOPEN = "Open Jet File"
+ DLG_PREFERENCES = "Preferences"
+ DLG_ABOUT = "About"
+ DLG_PROPERTIES = "Jet Project Properties"
+ DLG_AUDITION = "Audition Jet File"
+ DLG_REPLICATE = "Replicate Event"
+ DLG_MOVE = "Move Events"
+ MAIN_TITLEPREFIX = 'Jet Creator - '
+ MAIN_DLG_CTRLS = 'JET_CREATOR'
+ MAIN_SEGLIST = 'segList'
+ MAIN_EVENTLIST = 'eventList'
+ MAIN_ADDSEGTITLE = "Add Segments"
+ MAIN_REVSEGTITLE = "Revise Segments"
+ MAIN_ADDEVENTTITLE = "Add Event"
+ MAIN_REVEVENTTITLE = "Revise Event"
+ MAIN_CONFIRM = "Confirm Deletion"
+ MAIN_CONFIRM_SEG_DLT = "\n\nOkay to delete segment(s)?"
+ MAIN_CONRIRM_EVT_DLT = "\n\nOkay to delete event(s)?"
+ MAIN_PLAYSEG = "Play Segments"
+ MAIN_PLAYSEGMSG = "Queue one or more segments by checking them in the list, then play."
+ MAIN_HELPTITLE = "Jet Creator Help"
+ MAIN_HELPFILE = "JET Creator User Manual.htm"
+ MAIN_HELPGUIDELINESTITLE = "Jet Authoring Guidelines"
+ MAIN_HELPGUIDELINESFILE = "JET Authoring Guidelines.htm"
+ MAIN_IMPORTTITLE = "Import Project"
+ MAIN_IMPORTMSG = "Okay to import\n\n%s\n\ninto\n\n%s?"
+ MAIN_SAVEBEFOREEXIT = "Save project before exiting?"
+ MAIN_JETCREATOR = "Jet Creator"
+
+ #Audition window defines
+ AUDITION_CTRLS = 'AUDITION_CTRLS'
+ AUDITION_SEGLIST = 'segList'
+ AUDITION_QUEUELIST = 'queueList'
+ AUDITION_TRACKLIST = 'trackList'
+ AUDITION_GRAPH = 'graph'
+
+ PLAY_TRIGGERCLIP_MSG = 'Triggered Clip %d: %s'
+
+ #Config file defines
+ RECENT_SECTION = "Recent"
+ DIR_SECTION = "Directories"
+ IMAGES_DIR = "ImagesDir"
+ INI_PREF_SECTION = "Preferences"
+ INI_PROJECT_DIRS = "chkProjectDir"
+ INI_LOGGING = "Logging"
+ INI_DEFAULTDIRS = "Directories"
+ INI_DISPEMPTYTRACKS = "DisplayEmptyTracks"
+ INI_EVENTSORT = "EventSort"
+ INI_EVENTSORT_0 = "EventSort0"
+ INI_EVENTSORT_1 = "EventSort1"
+ INI_SEGSORT = "SegSort"
+ INI_SEGSORT_0 = "SegSort0"
+ INI_SEGSORT_1 = "SegSort1"
+
+ #Mbt defines
+ MBT_DEFAULT = (0,0,0)
+ MBT_MIN = 0
+ MBT_ZEROSTR = "0:0:0"
+
+ #File open dialog specs
+ APPLICATION_TITLE = "Jet Creator"
+ MIDI_FILE_SPEC = 'MIDI Files (*.mid)|*.mid|All Files (*.*)|*.*'
+ DLS_FILE_SPEC = 'DLS Files (*.dls)|*.dls|All Files (*.*)|*.*'
+ JTC_FILE_SPEC = 'Jet Content Files (*.jtc)|*.jtc|All Files (*.*)|*.*'
+ ARCHIVE_FILE_SPEC = 'Jet Archive Files (*.zip)|*.zip|All Files (*.*)|*.*'
+ OPEN_PROMPT = "Open Jet Creator File"
+ SAVE_PROMPT = "Save Jet Creator File"
+ EXPORT_ARCHIVE_PROMPT = "Save Jet Archive"
+ MUST_SAVE_FIRST = "You must save your JetCreator project before exporting it."
+ IMPORT_ARCHIVE_PROMPT = "Select the Jet Archive to import"
+ IMPORT_ARCHIVEDIR_PROMPT = "Choose a directory:\n\nYour imported project files will be placed there."
+ IMPORT_ARCHIVE_NO_JTC = "This does not appear to be a JetCreator archive file."
+ IMPORT_NOT_JET_ARCHIVE = "Not a recognized Jet Archive file."
+
+ #Button texts
+ BUT_ADD = 'Add'
+ BUT_REVISE = 'Revise'
+ BUT_DELETE = 'Delete'
+ BUT_PLAY = 'Play'
+ BUT_STOP = 'Stop'
+ BUT_MOVE = 'Move'
+ BUT_QUEUEALL = 'Queue All'
+ BUT_DEQUEUEALL = 'Dequeue All'
+ BUT_UNMUTE = 'Un-Mute'
+ BUT_MUTE = 'Mute'
+ BUT_AUDITION = 'Audition'
+ BUT_QUEUE = 'Queue'
+ BUT_MUTEALL = 'Mute All'
+ BUT_MUTENONE = 'Mute None'
+ BUT_ORGMUTES = 'Original Mutes'
+ BUT_CANCELANDQUEUE = 'Cancel && Queue'
+ BUT_CANCELCURRENT = 'Next'
+ BUT_PAUSE = 'Pause'
+ BUT_RESUME = 'Resume'
+ BUT_PLAYSEG = 'Play Segment'
+ BUT_PLAYMIDI = 'Play MIDI File'
+
+ #Grid defines
+ GRD_TRACK = "Track"
+ GRD_CHANNEL = "Channel"
+ GRD_NAME = "Name"
+ GRD_SEGMENTS = "Segments"
+ GRD_LENGTH = "Length"
+ GRD_QUEUE = "Queue"
+ GRD_STATUS = "Status"
+
+ #Menu defines
+ MNU_ADD_SEG = "Add Segment"
+ MNU_UPDATE_SEG = "Revise Segment"
+ MNU_DELETE_SEG = "Delete Segment"
+ MNU_MOVE_SEG = "Move Segment(s)"
+ MNU_ADD_EVENT = "Add Event"
+ MNU_UPDATE_EVENT = "Revise Event"
+ MNU_DELETE_EVENT = "Delete Event"
+ MNU_MOVE_EVENT = "Move Events(s)"
+ MNU_UNDO = "Undo\tctrl+z"
+ MNU_REDO = "Redo\tctrl+y"
+
+ HLP_QUANTIZE = "The quantize element is optional and defaults to 0 if omitted.\nThis value sets a window size in ticks for the breaks in\n a segment when notes are extracted from a larger file. \nSee the section on Quantization for further detail \non the operation of this parameter."
+
+ #Status bar messages
+ SB_NEW = "New JET Creator file"
+ SB_OPEN = "Open JET Creator file"
+ SB_SAVE = "Save Jet Creator file and generate .JET output file"
+ SB_SAVEAS = "Save JET Creator file as another file"
+ SB_EXIT = "Exit the application"
+ SB_CUT = "Cuts the current segment or event to the clipboard"
+ SB_COPY = "Copies the current segment or event to the clipboard"
+ SB_PASTE = "Pastes the current segment or event from the clipboard"
+ SB_UNDO = "Undo the last segment or event edit."
+ SB_REDO = "Reverse the last segment or event undo edit."
+ SB_IMPORT_PROJ = "Imports a JetCreator project archive."
+ SB_EXPORT_PROJ = "Saves all project files to an archive."
+
+ #Defines the menus
+ MENU_SPEC = (("&File",
+ ("&New", SB_NEW, 'OnJetNew', True),
+ ("&Open...", SB_OPEN, 'OnJetOpen', True),
+ ("&Save", SB_SAVE, 'OnJetSave', True),
+ ("Save As...", SB_SAVEAS, 'OnJetSaveAs', True),
+ ("", "", "", True),
+ ("Import Project...", SB_IMPORT_PROJ, "OnJetImportArchive", True),
+ ("Export Project...", SB_EXPORT_PROJ, "OnJetExportArchive", True),
+ ("Properties...", "Sets properties specific to this Jet project", 'OnJetProperties', True),
+ ("", "", "", True),
+ ("Exit", SB_EXIT, 'OnClose', True)),
+ ("&Edit",
+ (MNU_UNDO, "Undo", 'OnUndo', False),
+ (MNU_REDO, "Redo", 'OnRedo', False),
+ ("C&ut\tctrl+x", "Cut", 'OnCut', True),
+ ("&Copy\tctrl+c", "Copy", 'OnCopy', True),
+ ("&Paste\tctrl+v", "Paste", 'OnPaste', True)),
+ ("Jet",
+ ("Preferences", "Set user preferences including defaults for new project files.", 'OnPreferences', True)),
+ ("Segments",
+ (MNU_ADD_SEG, "Add a new segment to the segment list", 'OnSegmentAdd', True),
+ (MNU_UPDATE_SEG, "Revise the segment attributes", 'OnSegmentUpdate', False),
+ (MNU_DELETE_SEG, "Delete the segment from the segment list", 'OnSegmentDelete', False),
+ (MNU_MOVE_SEG, "Move one or more segments by incrementing or decrementing their time values", 'OnSegmentsMove', False)),
+ ("Events",
+ (MNU_ADD_EVENT, "Add a new event for the currently selected segment", 'OnEventAdd', False),
+ (MNU_UPDATE_EVENT, "Revise the current event's attributes", 'OnEventUpdate', False),
+ (MNU_DELETE_EVENT, "Delete the event from the event list for this segment", 'OnEventDelete', False),
+ (MNU_MOVE_EVENT, "Move one or more events by incrementing or decrementing their time values", 'OnEventsMove', False)),
+ ("Help",
+ ("JET Creator User Manual", "Get help on the JET Creator", "OnHelpJet", True),
+ ("JET Authoring Guidelines", "Guidelines helpful for JET content creation", "OnHelpJetGuidelines", True),
+ ("About", "About the JET Creator", "OnAbout", True))
+ )
+
+ #Define the toolbar
+ TOOLBAR_SPEC = (
+ ("-", "", "", ""),
+ ("New", "img_New", SB_NEW, "OnJetNew"),
+ ("Open", "img_Open", SB_OPEN, "OnJetOpen"),
+ ("Save", "img_Save", SB_SAVE, "OnJetSave"),
+ ("-", "", "", ""),
+ ("Cut", "img_Cut", SB_CUT, "OnCut"),
+ ("Copy", "img_Copy", SB_COPY, "OnCopy"),
+ ("Paste", "img_Paste", SB_PASTE, "OnPaste"),
+ ("-", "", "", ""),
+ ("Undo", "img_Undo", SB_UNDO, "OnUndo"),
+ ("Redo", "img_Redo", SB_REDO, "OnRedo"),
+ )
+
+ F_HLPBUT = "hlpButton"
+ F_OK = "btnOk"
+ F_CANCEL = "btnCancel"
+ F_MIDIFILE = "filecmbMidiFile"
+ F_DLSFILE = "filecmbDlsFile"
+ F_SEGNAME = "txtSegName"
+ F_START = "tmStart"
+ F_END = "tmEnd"
+ F_QUANTIZE = "spnQuantize"
+ F_REPEAT = "spnRepeat"
+ F_TRANSPOSE = "spnTranspose"
+ F_MUTEFLAGS = "grd2MuteFlags"
+ F_SYNCMUTE = "chkSync"
+ F_ETYPE = "cmbEventType"
+ F_ENAME = "txtEventName"
+ F_ESTART = "tmEventStart"
+ F_EEND = "tmEventEnd"
+ F_EID = "spnEventID"
+ F_ETRACK = "spnEventTrack"
+ F_ECHANNEL = "spn1EventChannel"
+ F_EEVENTID = "spnEventID"
+ F_EMUTEBUT = "btnMute"
+ F_ETRIGGERBUT = "btnTriggerClip"
+ F_GRAPH = "graphPlay"
+ F_PAUSE = "btnPause"
+ F_ADDSEG = "btnAddSeg"
+ F_UPDSEG = "btnUpdateSeg"
+ F_DELSEG = "btnDeleteSeg"
+ F_PLAY = "btnPlay"
+ F_PLAYMIDI = "btnPlayMidi"
+ F_EASPLAY = "btnEasPlay"
+ F_ADDCLIP = "btnAddEvent"
+ F_UPDCLIP = "btnUpdateEvent"
+ F_DELCLIP = "btnDeleteEvent"
+ F_EXPORT = "btnOkExport"
+ F_JETFILENAME = "filecmbJetFileName"
+ F_COPYRIGHT = "txtCopyright"
+ F_JFILE = "filetxtJetFileName"
+ F_JOPEN = "btnOpen"
+ F_JNEW = "btnNew"
+ F_JIMPORT = "btnImport"
+ F_JLIST = "lstRecent"
+ F_ERRGRID = "grdErrors"
+ F_CHASECONTROLLERS = "chkChaseControllers"
+ F_DELETEEMPTYTRACKS = "chkDeleteEmptyTracks"
+ F_OPTMIDI = "optMidiGraph"
+ F_OPTSEG = "optSegGraph"
+ F_RDOGRAPH = "rdoboxGraphType"
+ F_DISPEMPTYTRACKS = "chkDisplayEmptyTracks"
+ F_GRAPHLABELS = "chkGraphLabels"
+ F_GRAPHCLIPS = "chkGraphClips"
+ F_GRAPHAPPEVTS = "chkGraphAppEvts"
+ F_REPLICATE = "btnReplicate"
+
+ GRAPH_LBLS = "Labels"
+ GRAPH_TRIGGER = "Trigger Clips"
+ GRAPH_APP = "App Events"
+
+ #IDs for dialogs
+ ID_JET_OPEN = 0
+ ID_JET_NEW = 1
+ ID_JET_IMPORT = 2
+
+ #Event types
+ E_CLIP = 'TriggerClip'
+ E_EOS = 'End of Segment'
+ E_APP = 'App Controller'
+
+ INTWIDTH = 70
+ TIMEWIDTH = 70
+
+ #Definitions of fields in the edit frame
+ TM_WIDTH = 100
+ TRACK_MIN = 1
+ TRACK_MAX = 32
+ EVENTID_MIN = 1
+ EVENTID_MAX = 63
+ APPCONTROLLERID_MIN = 80
+ APPCONTROLLERID_MAX = 83
+ #NEEDS TO DEFAULT TO RANGE OF BOTH POSSIBLE TYPES
+ DEFAULTID_MIN = 1
+ DEFAULTID_MAX = 100
+
+ #Mins and maxs for dialog values
+ QUANTIZE_MIN = 0
+ QUANTIZE_MAX = 9
+ CHANNEL_MIN = 1
+ CHANNEL_MAX = 16
+ TRANSPOSE_MIN = -12
+ TRANSPOSE_MAX = 12
+ REPEAT_MIN = -1
+ REPEAT_MAX = 100
+
+ #Standardize the columns
+ BUTSIZE = wx.DefaultSize
+ COLSIZE = 120
+ COL1 = 30
+ COL2 = COL1 + COLSIZE
+ COL3 = COL2 + COLSIZE
+ COL4 = COL3 + COLSIZE
+ COL5 = COL4 + COLSIZE
+ COL6 = COL5+ COLSIZE
+ COL7 = COL6 + COLSIZE
+ ROWSIZE = 50
+ ROW1 = 40
+ ROW2 = ROW1 + ROWSIZE
+ ROW3 = ROW2 + ROWSIZE
+ ROW4 = ROW3 + ROWSIZE
+ ROW5 = ROW4 + ROWSIZE
+ ROW6 = ROW5 + ROWSIZE
+ ROW7 = ROW6 + ROWSIZE
+ BUTOFF = 25
+ BUTROW1 = 25
+ FILEPATH_GRIDWIDTH = 120
+ FILEPATH_WIDTH = 250
+
+ #Segment grid column definitions
+ SEGMENT_GRID = [('Segment Name', 200, F_SEGNAME),
+ ('MIDI File', FILEPATH_GRIDWIDTH, F_MIDIFILE),
+ ('DLS File', FILEPATH_GRIDWIDTH, F_DLSFILE),
+ ('Start', TIMEWIDTH, F_START),
+ ('End', TIMEWIDTH, F_END),
+ ('Quantize', 0, F_QUANTIZE),
+ ('Transpose', 0, F_TRANSPOSE),
+ ('Repeat', 0, F_REPEAT),
+ ('Mute Flags', 0, F_MUTEFLAGS)
+ ]
+
+ #Clips grid column definitions
+ CLIPS_GRID = [('Event Name', 200, F_ENAME),
+ ('Type', 100, F_ETYPE),
+ ('Start',TIMEWIDTH, F_ESTART),
+ ('End',TIMEWIDTH, F_EEND),
+ ('Track',0, F_ETRACK),
+ ('Channel',0, F_ECHANNEL),
+ ('EventID',0, F_EEVENTID)
+ ]
+
+ #Jet open dialog control definitions
+ JETOPEN_SIZE = (365+200,360)
+ JETOPEN_CTRLS = [
+ ('Jet Creator Files', 'frCreator', 20, 20, (234+200, 244 + ROWSIZE), 0, 0, -1, [], "", True, ""),
+ ('Open', F_JOPEN, BUTROW1, COL3+200, BUTSIZE, 0, 0, ID_JET_OPEN, [], "OnOk", True, ""),
+ ('New', F_JNEW, BUTROW1+BUTOFF*1, COL3+200, BUTSIZE, 0, 0, ID_JET_NEW, [], "OnNew", True, ""),
+ ('Import', F_JIMPORT, BUTROW1+BUTOFF*2, COL3+200, BUTSIZE, 0, 0, ID_JET_IMPORT, [], "OnJetImport", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*3, COL3+200, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "", True, ""),
+ ('', F_JFILE, ROW1, COL1, 200+200, 0, 0, -1, JTC_FILE_SPEC, "", True, ""),
+ ('Recent Files', F_JLIST, ROW2, COL1, (200+200,200), 0, 0, -1, [], "", True, ""),
+ ]
+
+ #Jet properties dialog control definitions
+ JET_PROPERTIES_SIZE = (465,460)
+ JET_PROPERTIES_CTRLS = [
+ ('Jet Project Properties', 'frProperties', 20, 20, (334, 344 + ROWSIZE), 0, 0, -1, [], "", True, ""),
+ ('Ok', F_OK, BUTROW1, COL3+100, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3+100, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "", True, ""),
+ ('Jet File', F_JETFILENAME, ROW1, COL1, 300, 0, 0, -1, JTC_FILE_SPEC, "", True, ""),
+ ('Copyright', F_COPYRIGHT, ROW2, COL1, 300, 0, 0, -1, [], "", True, ""),
+ ('Chase Controllers', F_CHASECONTROLLERS, ROW3, COL1, 200, 0, 0, -1, [], "", True, ""),
+ ('Delete Empty Tracks', F_DELETEEMPTYTRACKS, ROW4 - ROWSIZE/2, COL1, 200, 0, 0, -1, [], "", True, ""),
+ ]
+
+ #Preferences dialog control definitions
+ PREFERENCES_SIZE = (465,460)
+ PREFERENCES_CTRLS = [
+ ('Preferences', 'frPreferences', 20, 20, (334, 344 + ROWSIZE), 0, 0, -1, [], "", True, ""),
+ ('Ok', F_OK, BUTROW1, COL3+100, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3+100, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "", True, ""),
+ ('Copyright', F_COPYRIGHT, ROW1, COL1, 300, 0, 0, -1, [], "", True, ""),
+ ('Chase Controllers', F_CHASECONTROLLERS, ROW2, COL1, 200, 0, 0, -1, [], "", True, ""),
+ ('Delete Empty Tracks', F_DELETEEMPTYTRACKS, ROW3 - ROWSIZE/2, COL1, 200, 0, 0, -1, [], "", True, ""),
+# ('Use Project Directories', INI_PROJECT_DIRS, ROW1, COL1, 150, 0, 0, -1, [], "", True, ""),
+ ]
+
+ #Error dialog control definitions
+ ERRORCOLW = 220
+ ERRORDLG_SIZE = (600,400)
+ ERRORDLG = [
+ ('Ok', F_OK, BUTROW1, 500, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('', F_ERRGRID, BUTROW1, COL1, (200,300), 0, 0, -1, [], "", True, ""),
+ ]
+
+ #Event dialog control definitions
+ BGR = 100
+ EVT_OFFSET = 525+BGR
+ EVTDLG_SIZE = (375+EVT_OFFSET,530)
+ ID_MUTE = 124
+ ID_MIDIFILE = 123
+ ID_TRIGGERCLIP = 122
+ SEGFRAME_SIZE = (500+BGR, 344 + ROWSIZE)
+ TRACKGRD_SIZE = (70, SEGFRAME_SIZE[1]-50)
+ GRAPH_SIZE = (760, 50)
+ AUDCOL=190
+ EVTDLG_CTRLS = [
+ ('Segment', 'frSeg', 20, 20, SEGFRAME_SIZE, 0, 0, -1, [], "", False, ""),
+ ('Segment Name', F_SEGNAME, ROW1, COL1, 200+BGR, 0, 0, -1, [], "", False, ""),
+ ('MIDI File', F_MIDIFILE, ROW2, COL1, FILEPATH_WIDTH+BGR, 0, 0, ID_MIDIFILE, MIDI_FILE_SPEC, "", False, ""),
+ ('DLS File', F_DLSFILE, ROW3, COL1, FILEPATH_WIDTH+BGR, 0, 0, -1, DLS_FILE_SPEC, "", False, ""),
+ ('Starting M/B/T', F_START, ROW4, COL1, TM_WIDTH, 0, 0, -1, [], "", False, ""),
+ ('Ending M/B/T', F_END, ROW5, COL1, TM_WIDTH, 0, 0, -1, [], "", False, ""),
+ ('Quantize', F_QUANTIZE, ROW6, COL1, INTWIDTH, QUANTIZE_MIN, QUANTIZE_MAX, -1, [], "", False, HLP_QUANTIZE),
+ ('Repeat', F_REPEAT, ROW4, AUDCOL, INTWIDTH, REPEAT_MIN, REPEAT_MAX, -1, [], "", False, ""),
+ ('Transpose', F_TRANSPOSE, ROW5, AUDCOL, INTWIDTH, TRANSPOSE_MIN, TRANSPOSE_MAX, -1, [], "", False, ""),
+ ('Event', 'frEventg', 20+EVT_OFFSET, 20, (234, 344 + ROWSIZE), 0, 0, -1, [], "", True, ""),
+ ('Ok', F_ADDCLIP, BUTROW1, COL3+EVT_OFFSET, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3+EVT_OFFSET, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "OnClose", True, ""),
+ ('Replicate', F_REPLICATE, BUTROW1+BUTOFF*2, COL3+EVT_OFFSET, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "OnReplicate", True, ""),
+ ('Event Name', F_ENAME, ROW1, COL1+EVT_OFFSET, 200, 0, 0, -1, [], "", True, ""),
+ ('Event Type', F_ETYPE, ROW2, COL1+EVT_OFFSET, 120, 0, 0, -1, [E_CLIP, E_EOS, E_APP], "OnEventSelect", True, ""),
+ ('Starting M/B/T', F_ESTART, ROW3, COL1+EVT_OFFSET, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Ending M/B/T', F_EEND, ROW4, COL1+EVT_OFFSET, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Track', F_ETRACK, ROW5, COL1+EVT_OFFSET, INTWIDTH, TRACK_MIN, TRACK_MAX, -1, [], "", True, ""),
+ ('Track Mutes', F_MUTEFLAGS, ROW1, COL3 + 15+BGR, TRACKGRD_SIZE, 0, 0, -1, [], "", False, ""),
+ ('Channel', F_ECHANNEL, ROW6, COL1+EVT_OFFSET, INTWIDTH, CHANNEL_MIN, CHANNEL_MAX, -1, [], "", True, ""),
+ ('EventID', F_EEVENTID, ROW7, COL1+EVT_OFFSET, INTWIDTH, DEFAULTID_MIN, DEFAULTID_MAX, -1, [], "", True, ""),
+ ('Play', F_PLAY, BUTROW1+BUTOFF*4, COL3+EVT_OFFSET, BUTSIZE, 0, 0, -1, [], "OnPlay", True, ""),
+ ('Trigger', F_ETRIGGERBUT, BUTROW1+BUTOFF*5, COL3+EVT_OFFSET, BUTSIZE, 0, 0, ID_TRIGGERCLIP, [], "OnTriggerClip", False, ""),
+ ('Un-Mute', F_EMUTEBUT, BUTROW1+BUTOFF*6, COL3+EVT_OFFSET, BUTSIZE, 0, 0, ID_MUTE, [], "OnMute", False, ""),
+ ('Pause', F_PAUSE, BUTROW1+BUTOFF*7, COL3+EVT_OFFSET, BUTSIZE, 0, 0, -1, [], "OnPause", False, ""),
+ ('Graph', F_GRAPH, 430, 20, (EVTDLG_SIZE[0]-40,60), 0, 0, -1, [], "", True, ""),
+ (GRAPH_LBLS, F_GRAPHLABELS, (BUTROW1+BUTOFF*10)+70, COL3+EVT_OFFSET+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ (GRAPH_APP, F_GRAPHCLIPS, (BUTROW1+BUTOFF*10)+90, COL3+EVT_OFFSET+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ (GRAPH_TRIGGER, F_GRAPHAPPEVTS, (BUTROW1+BUTOFF*10)+110, COL3+EVT_OFFSET+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ ("Graph", "boxGraph", (BUTROW1+BUTOFF*10)+45, COL3+EVT_OFFSET, (90,95), 0, 0, -1, [], "", True, ""),
+
+ ]
+
+ #Segment dialog control definitions
+ BGR = 100
+ AUDCOL = 560
+ COLADD = 500 + BGR
+ SEGDLG_SIZE = (890+BGR,530)
+ SEGFRAME_SIZE = (375+BGR, 394)
+ AUDFRAME_SIZE = (350, 394)
+ TRACKGRD_SIZE = (200, AUDFRAME_SIZE[1]-60)
+ MUTEGRD_TRACK = 50
+ MUTEGRD_CHANNEL = 60
+ MUTEGRD_NAME = 100
+ BIGBUT = (100, 25)
+ FILEPATH_WIDTH = 350
+ SEGDLG_CTRLS = [
+ ('Segment', 'frSeg', 20, 20, SEGFRAME_SIZE, 0, 0, -1, [], "", True, ""),
+ ('Audition', 'frAudition', SEGFRAME_SIZE[0]+30, 20, AUDFRAME_SIZE, 0, 0, -1, [], "", True, ""),
+ ('Segment Name', F_SEGNAME, ROW1, COL1, 200+BGR, 0, 0, -1, [], "", True, ""),
+ ('MIDI File', F_MIDIFILE, ROW2, COL1, FILEPATH_WIDTH+BGR, 0, 0, ID_MIDIFILE, MIDI_FILE_SPEC, "", True, ""),
+ ('DLS File', F_DLSFILE, ROW3, COL1, FILEPATH_WIDTH+BGR, 0, 0, -1, DLS_FILE_SPEC, "", True, ""),
+ ('Starting M/B/T', F_START, ROW4, COL1, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Ending M/B/T', F_END, ROW5, COL1, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Quantize', F_QUANTIZE, ROW6, COL1, INTWIDTH, QUANTIZE_MIN, QUANTIZE_MAX, -1, [], "", True, HLP_QUANTIZE),
+ ('Repeat', F_REPEAT, ROW1, AUDCOL+100+BGR, INTWIDTH, REPEAT_MIN, REPEAT_MAX, -1, [], "", True, ""),
+ ('Transpose', F_TRANSPOSE, ROW2, AUDCOL+100+BGR, INTWIDTH, TRANSPOSE_MIN, TRANSPOSE_MAX, -1, [], "", True, ""),
+ ('Track Mutes', F_MUTEFLAGS, ROW1, COL3 + 145+BGR, TRACKGRD_SIZE, 0, 0, -1, [], "", True, ""),
+ ('Display Empty Tracks', F_DISPEMPTYTRACKS, ROW1+TRACKGRD_SIZE[1]+20, COL3 + 145+BGR, 200, 0, 0, -1, [], "OnSetTrackDisplayOption", True, ""),
+ ('Ok', F_ADDSEG, BUTROW1, COL3 + COLADD, BIGBUT, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3 + COLADD, BIGBUT, 0, 0, wx.ID_CANCEL, [], "OnClose", True, ""),
+ ('Replicate', F_REPLICATE, BUTROW1+BUTOFF*2, COL3 + COLADD, BIGBUT, 0, 0, wx.ID_CANCEL, [], "OnReplicate", True, ""),
+
+ ('Play Segment', F_PLAY, BUTROW1+BUTOFF*4, COL3 + COLADD, BIGBUT, 0, 0, -1, [], "OnPlay", True, ""),
+ ('Play MIDI File', F_PLAYMIDI, BUTROW1+BUTOFF*5, COL3 + COLADD, BIGBUT, 0, 0, -1, [], "OnPlayMidi", True, ""),
+ ('Pause', F_PAUSE, BUTROW1+BUTOFF*6, COL3 + COLADD, BIGBUT, 0, 0, -1, [], "OnPause", False, ""),
+ ('Graph', F_GRAPH, 430, 20, (SEGDLG_SIZE[0]-40,60), 0, 0, -1, [], "", True, ""),
+ ('Graph', F_RDOGRAPH, (BUTROW1+BUTOFF*10), COL3 + COLADD, (100,140), 0, 0, -1, ["MIDI File", "Segment"], "OnSetGraphType", True, ""),
+
+ (GRAPH_LBLS, F_GRAPHLABELS, (BUTROW1+BUTOFF*10)+70, COL3 + COLADD+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ (GRAPH_APP, F_GRAPHCLIPS, (BUTROW1+BUTOFF*10)+90, COL3 + COLADD+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ (GRAPH_TRIGGER, F_GRAPHAPPEVTS, (BUTROW1+BUTOFF*10)+110, COL3 + COLADD+5, 200, 0, 0, -1, [], "OnSetGraphOptions", True, ""),
+ ]
+
+
+ REPLICATE_MAX = 999
+ F_RPINCREMENT = "tmIncrement"
+ F_RPGRDPREVIEW = "grdPreview"
+ F_RPPREFIX = "txtPrefix"
+ F_RPREPLACE = "chkReplaceMatching"
+ F_RPMOVE = "chkMoveMatching"
+ F_RPNUMBER = "spnNumber"
+ F_RPBUT = "btnPreview"
+ REPLICATE_GRID = [('Event Name', 200, F_ENAME),
+ ('Start',TIMEWIDTH, F_ESTART),
+ ('End',TIMEWIDTH, F_EEND)
+ ]
+ REPLICATE_SIZE = (515,550)
+ REPLICATEGRID_SIZE = (350,310)
+ REPLICATE_CTRLS = [
+ ('Replicate', 'frRep', 20, 20, (384, 480), 0, 0, -1, [], "", True, ""),
+ ('Ok', F_OK, BUTROW1, COL3+150, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3+150, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "", True, ""),
+ ('Preview', F_RPBUT , BUTROW1+BUTOFF*2, COL3+150, BUTSIZE, 0, 0, -1, [], "OnPreview", True, ""),
+ ('Name Prefix', F_RPPREFIX, ROW1, COL1, 300, 0, 0, -1, [], "", True, ""),
+ ('Replace Existing Items Matching Prefix', F_RPREPLACE, ROW3, COL1, 200, 0, 0, -1, [], "", True, ""),
+ ('Preview', F_RPGRDPREVIEW, ROW4-20, COL1, REPLICATEGRID_SIZE, 0, 0, -1, [], "", True, ""),
+ ('Starting M/B/T', F_ESTART, ROW2, COL1, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Increment M/B/T', F_RPINCREMENT, ROW2, COL2+20, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Number', F_RPNUMBER, ROW2, COL3+40, INTWIDTH, 1, REPLICATE_MAX, -1, [], "", True, ""),
+ ]
+
+
+ MOVE_SIZE = (350,390)
+ MOVE_CTRLS = [
+ ('Move', 'frRep', 20, 20, (384, 480), 0, 0, -1, [], "", True, ""),
+ ('Ok', F_OK, BUTROW1, COL3+150, BUTSIZE, 0, 0, wx.ID_OK, [], "OnOk", True, ""),
+ ('Cancel', F_CANCEL, BUTROW1+BUTOFF*1, COL3+150, BUTSIZE, 0, 0, wx.ID_CANCEL, [], "", True, ""),
+ ('Preview', F_RPBUT , BUTROW1+BUTOFF*2, COL3+150, BUTSIZE, 0, 0, -1, [], "OnPreview", True, ""),
+ ('Starting M/B/T', F_ESTART, ROW1, COL1, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Increment M/B/T', F_RPINCREMENT, ROW1, COL2+20, TM_WIDTH, 0, 0, -1, [], "", True, ""),
+ ('Preview', F_RPGRDPREVIEW, ROW2, COL1, MOVE_SIZE, 0, 0, -1, [], "", True, ""),
+ ]
+
+if __name__ == '__main__':
+ jd = JetDefs()
+ jd.CreateHelpIniFile()
diff --git a/jet_tools/JetCreator/JetDialogs.py b/jet_tools/JetCreator/JetDialogs.py
new file mode 100755
index 0000000..67d689b
--- /dev/null
+++ b/jet_tools/JetCreator/JetDialogs.py
@@ -0,0 +1,1031 @@
+"""
+ File:
+ JetDialogs.py
+
+ Contents and purpose:
+ Dialog boxes used in JetCreator
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import wx
+import thread
+import wx.lib.newevent
+
+from JetDefs import *
+from JetCtrls import *
+from JetFile import *
+from JetUtils import *
+from JetPreview import *
+from JetSegGraph import *
+from eas import *
+from JetStatusEvent import *
+
+PLAY_SEGMENT = 1
+PLAY_MIDI = 2
+
+class JetEdit():
+ """ Class used to build dialog box controls from the definitions in JetDefs """
+ def __init__(self, panel, ctrlList, callbackClass):
+ LBL_OFFSET = 15
+
+ ctrls = getattr(JetDefs, ctrlList)
+ self.ctrls = {}
+ for Lbl, Text, Row, Col, Len, Min, Max, Id, Lst, Fct, Enabled, HelpText in ctrls:
+ try:
+ iDisplayLbl = True
+ if Text[0:3] == "btn":
+ self.ctrls[Text] = wx.Button(panel, Id, Lbl, wx.Point(Col, Row), size=Len)
+ if Fct > "":
+ self.ctrls[Text].Bind(wx.EVT_BUTTON, getattr(callbackClass, Fct), self.ctrls[Text], id=Id)
+ if Id == wx.ID_OK:
+ self.ctrls[Text].SetDefault()
+ iDisplayLbl = False
+ else:
+ if Text[0:3] == "txt":
+ self.ctrls[Text] = wx.TextCtrl(panel, Id, "", wx.Point(Col, Row + LBL_OFFSET +3), wx.Size(Len,-1))
+ elif Text[0:4] == "spn1":
+ self.ctrls[Text] = JetSpinOneBased(panel, Id, "", wx.Point(Col, Row + LBL_OFFSET), wx.Size(Len,-1), min=Min, max=Max)
+ elif Text[0:3] == "spn":
+ self.ctrls[Text] = JetSpin(panel, Id, "", wx.Point(Col, Row + LBL_OFFSET), wx.Size(Len,-1), min=Min, max=Max)
+ elif Text[0:3] == "cmb":
+ self.ctrls[Text] = wx.ComboBox(panel, Id, "", wx.Point(Col, Row + LBL_OFFSET), wx.Size(Len,-1), Lst, wx.CB_DROPDOWN | wx.CB_READONLY )
+ self.ctrls[Text].SetValue(Lst[0])
+ if Fct > "":
+ self.ctrls[Text].Bind(wx.EVT_COMBOBOX, getattr(callbackClass, Fct), self.ctrls[Text])
+ elif Text[0:2] == "tm":
+ self.ctrls[Text] = TimeCtrl(panel, pos=(Col, Row + LBL_OFFSET), ctlName=Text)
+ elif Text[0:7] == "filecmb":
+ self.ctrls[Text] = JetFileCombo(panel, pos=(Col, Row + LBL_OFFSET), size=wx.Size(Len,-1), title=Lbl, spec=Lst, id=Id)
+ elif Text[0:7] == "filetxt":
+ self.ctrls[Text] = JetFileText(panel, pos=(Col, Row + LBL_OFFSET), size=wx.Size(Len,-1), title=Lbl, spec=Lst, id=Id)
+ elif Text[0:2] == "fr":
+ self.ctrls[Text] = wx.StaticBox(parent=panel, id=wx.ID_ANY, label=Lbl, pos=(Row, Col), size=Len)
+ iDisplayLbl = False
+ elif Text[0:3] == "chk":
+ self.ctrls[Text] = JetCheckBox(panel, Id, label=Lbl, pos=(Col, Row), size=wx.Size(Len,-1))
+ iDisplayLbl = False
+ if Fct > "":
+ self.ctrls[Text].Bind(wx.EVT_CHECKBOX , getattr(callbackClass, Fct), self.ctrls[Text])
+ elif Text[0:6] == "rdobox":
+ self.ctrls[Text] = wx.RadioBox(panel, Id, label=Lbl, pos=(Col, Row), size=Len, choices=Lst, majorDimension=1, style=wx.RA_SPECIFY_COLS)
+ iDisplayLbl = False
+ if Fct > "":
+ self.ctrls[Text].Bind(wx.EVT_RADIOBOX , getattr(callbackClass, Fct), self.ctrls[Text])
+ elif Text[0:3] == "opt":
+ self.ctrls[Text] = JetRadioButton(panel, Id, label=Lbl, pos=(Col, Row), size=wx.Size(Len,-1))
+ iDisplayLbl = False
+ self.ctrls[Text].SetValue(Lst)
+ if Fct > "":
+ self.ctrls[Text].Bind(wx.EVT_RADIOBUTTON , getattr(callbackClass, Fct), self.ctrls[Text])
+ elif Text[0:3] == "lst":
+ self.ctrls[Text] = wx.ListBox(panel, Id, pos=(Col, Row), size=Len)
+ iDisplayLbl = False
+ elif Text[0:4] == "grd2":
+ self.ctrls[Text] = JetTrackCtrl(panel, Id, pos=(Col, Row + LBL_OFFSET), size=Len, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
+ iDisplayLbl = True
+ elif Text[0:3] == "grd":
+ self.ctrls[Text] = JetListCtrl(panel, Id, pos=(Col, Row), size=Len)
+ iDisplayLbl = False
+ elif Text[0:5] == "graph":
+ self.ctrls[Text] = SegmentGraph(panel, pos=(Col, Row), size=Len)
+ iDisplayLbl = False
+ elif Text[0:3] == "hlp":
+ self.ctrls[Text] = wx.ContextHelpButton(panel, Id, pos=(Col, Row))
+ iDisplayLbl = False
+ elif Text[0:3] == "lbl":
+ self.ctrls[Text] = wx.StaticText(panel, Id, Lbl, wx.Point(Col, Row), size=wx.Size(Len[0],Len[1]))
+ iDisplayLbl = False
+ elif Text[0:3] == "box":
+ self.ctrls[Text] = wx.StaticBox(panel, wx.ID_ANY, Lbl, pos=(Col, Row), size=Len)
+ iDisplayLbl = False
+
+ if iDisplayLbl:
+ self.ctrls[Lbl] = wx.StaticText(panel, Id, Lbl, wx.Point(Col, Row))
+ if not Enabled:
+ self.ctrls[Text].Enable(False)
+
+ helpText = IniGetValue(JetDefs.JETCREATOR_HLP, ctrlList, Lbl)
+ if helpText > "":
+ self.ctrls[Text].SetHelpText(helpText)
+
+ #except AttributeError:
+ #No stub function for testing
+ #print("def " + Fct + "(self, event): pass")
+ except:
+ raise
+
+ def GetValue(self, fld):
+ """ Gets the value of a control """
+ return self.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ """ Sets the value of a control """
+ self.ctrls[fld].SetValue(val)
+
+class JetOpen(wx.Dialog):
+ """ Opens a jet definition file """
+ def __init__(self):
+ wx.Dialog.__init__(self, None, -1, JetDefs.DLG_JETOPEN)
+ self.fileName = ""
+ self.je = JetEdit(self, "JETOPEN_CTRLS", self)
+ fileList = GetRecentJetFiles()
+ self.je.ctrls[JetDefs.F_JLIST].AppendItems(fileList)
+ if len(fileList) > 0:
+ self.je.ctrls[JetDefs.F_JFILE].SetValue(fileList[0])
+ self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnOpen)
+ self.Bind(wx.EVT_LISTBOX, self.OnClick)
+ self.SetSize(JetDefs.JETOPEN_SIZE)
+ self.CenterOnParent()
+
+ def OnJetImport(self, event):
+ """ Exit the dialog with flag to import """
+ self.EndModal(JetDefs.ID_JET_IMPORT)
+
+ def OnClick(self, event):
+ """ Clicking on item in listbox """
+ sValue = self.je.ctrls[JetDefs.F_JLIST].GetString(self.je.ctrls[JetDefs.F_JLIST].GetSelection())
+ self.je.ctrls[JetDefs.F_JFILE].SetValue(sValue)
+
+ def OnOpen(self, event):
+ """ Double clicking on item in listbox """
+ sValue = self.je.ctrls[JetDefs.F_JLIST].GetString(self.je.ctrls[JetDefs.F_JLIST].GetSelection())
+ self.je.ctrls[JetDefs.F_JFILE].SetValue(sValue)
+ if self.Validate():
+ self.fileName = self.je.ctrls[JetDefs.F_JFILE].GetValue()
+ AppendRecentJetFile(self.fileName)
+ self.EndModal(JetDefs.ID_JET_OPEN)
+
+ def OnOk(self, event):
+ """ Clicking the ok button """
+ if self.Validate():
+ self.fileName = self.je.ctrls[JetDefs.F_JFILE].GetValue()
+ AppendRecentJetFile(self.fileName)
+ self.EndModal(JetDefs.ID_JET_OPEN)
+
+ def OnNew(self, event):
+ """ Exit the dialog with flag to create new blank jet file """
+ self.EndModal(JetDefs.ID_JET_NEW)
+
+ def Validate(self):
+ """ Validates the filename """
+ if len(self.je.ctrls[JetDefs.F_JFILE].GetValue()) == 0:
+ InfoMsg("Jet Filename", "The Jet filename is blank.")
+ self.je.ctrls[JetDefs.F_JFILE].SetFocus()
+ return False
+ if not FileExists(self.je.ctrls[JetDefs.F_JFILE].GetValue()):
+ InfoMsg("MIDI File", "The file does not exist.")
+ self.je.ctrls[JetDefs.F_JFILE].SetFocus()
+ return False
+ return True
+
+class JetPreferences(wx.Dialog):
+ """ Preferences dialog box """
+ def __init__(self):
+ wx.Dialog.__init__(self, None, -1, JetDefs.DLG_PREFERENCES)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.je = JetEdit(self, "PREFERENCES_CTRLS", self)
+ self.SetSize(JetDefs.PREFERENCES_SIZE)
+ self.CenterOnParent()
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self, fld):
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ self.je.ctrls[fld].SetValue(val)
+
+
+class JetAbout(wx.Dialog):
+ """ About dialog box """
+ def __init__(self):
+ wx.Dialog.__init__(self, None, -1, JetDefs.DLG_ABOUT)
+ img = __import__('img_splash')
+ bmp = img.getBitmap()
+ b = wx.StaticBitmap(self, -1, bmp)
+ self.SetSize((bmp.GetWidth(), bmp.GetHeight()))
+ self.CenterOnParent()
+ s = __import__('sys')
+ print(s.platform)
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self, fld):
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ self.je.ctrls[fld].SetValue(val)
+
+
+class JetPropertiesDialog(wx.Dialog):
+ """ Properties dialog box """
+ def __init__(self):
+ wx.Dialog.__init__(self, None, -1, JetDefs.DLG_PROPERTIES)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.je = JetEdit(self, "JET_PROPERTIES_CTRLS", self)
+ self.SetSize(JetDefs.JET_PROPERTIES_SIZE)
+ self.CenterOnParent()
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self, fld):
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ self.je.ctrls[fld].SetValue(val)
+
+
+class JetErrors(wx.Dialog):
+ """ Errors dialog box """
+ def __init__(self, title):
+ wx.Dialog.__init__(self, None, -1, title)
+ self.je = JetEdit(self, "ERRORDLG", self)
+ self.SetSize(JetDefs.ERRORDLG_SIZE)
+ self.CenterOnParent()
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def SetErrors(self, errors):
+ self.je.ctrls[JetDefs.F_ERRGRID].AddCol("Error", JetDefs.ERRORCOLW)
+ self.je.ctrls[JetDefs.F_ERRGRID].AddCol("Description", JetDefs.ERRORCOLW)
+ self.je.ctrls[JetDefs.F_ERRGRID].AddRows(errors)
+
+
+class SegEdit(wx.Dialog):
+ """ Dialog box to edit segments """
+ def __init__(self, title, currentJetConfigFile):
+ wx.Dialog.__init__(self, None, -1, title)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.currentJetConfigFile = currentJetConfigFile
+ self.je = JetEdit(self, "SEGDLG_CTRLS", self)
+ self.je.ctrls[JetDefs.F_MIDIFILE].cmb.Bind(wx.EVT_KILL_FOCUS, self.OnMidiChanged)
+ self.je.ctrls[JetDefs.F_MIDIFILE].cmb.Bind(wx.EVT_COMBOBOX, self.OnMidiChanged)
+ self.je.ctrls[JetDefs.F_MIDIFILE].SetEventFire(True)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_TRACK, JetDefs.MUTEGRD_TRACK)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_CHANNEL, JetDefs.MUTEGRD_CHANNEL)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_NAME, JetDefs.MUTEGRD_NAME)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].BindCheckBox(self.OnEventChecked)
+ self.je.ctrls[JetDefs.F_START].SetChangeCallbackFct(self.UpdateGraph)
+ self.je.ctrls[JetDefs.F_END].SetChangeCallbackFct(self.UpdateGraph)
+ self.je.ctrls[JetDefs.F_DISPEMPTYTRACKS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_DISPEMPTYTRACKS, JetDefs.F_DISPEMPTYTRACKS, 'bool', 'False'))
+ self.je.ctrls[JetDefs.F_GRAPHLABELS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'))
+ self.je.ctrls[JetDefs.F_GRAPHCLIPS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'))
+ self.je.ctrls[JetDefs.F_GRAPHAPPEVTS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+ self.replicatePrefix = ""
+ self.lstReplicate = []
+ self.chkReplaceMatching = False
+
+ EVT_JET_STATUS(self, self.OnJetStatusUpdate)
+ wx.EVT_CLOSE(self, self.OnClose)
+
+ self.Player = None
+ self.segment = None
+ self.graphSegment = None
+ self.jetevents = []
+ self.lastMidiFile = ""
+ self.lastMidiInfo = None
+ self.playMode = PLAY_SEGMENT
+ self.graphMode = PLAY_MIDI
+ self.SetSize(JetDefs.SEGDLG_SIZE)
+ self.CenterOnParent()
+
+ def OnClose(self, event):
+ """ Closing the dialog box """
+ self.ShutdownPlayer()
+ self.je.ctrls[JetDefs.F_START].UnBindKillFocus()
+ self.je.ctrls[JetDefs.F_END].UnBindKillFocus()
+ self.EndModal(wx.ID_CANCEL)
+
+ def ShutdownPlayer(self):
+ """ Shutdown player flag """
+ if self.Player is not None:
+ self.Player.SetKeepPlayingFlag(False)
+
+ def OnMidiChanged(self, event):
+ """ When new midi file selected, get its info """
+ self.UpdateMaxMbt()
+ event.Skip()
+
+ def UpdateMaxMbt(self):
+ """ Get midi info in thread so UI smoother """
+ thread.start_new_thread(self.UpdateMaxMbtThread, ())
+
+ def UpdateMaxMbtThread(self):
+ """ Thread to get midi file info """
+ if self.je.ctrls[JetDefs.F_MIDIFILE].GetValue() == self.lastMidiFile:
+ return
+ self.lastMidiFile = self.je.ctrls[JetDefs.F_MIDIFILE].GetValue()
+ self.lastMidiInfo = GetMidiInfo(self.je.ctrls[JetDefs.F_MIDIFILE].GetValue())
+ wx.PostEvent(self, JetStatusEvent(JetDefs.PST_MIDI_INFO, self.lastMidiInfo))
+
+ def GetValue(self, fld):
+ """ Gets a control value """
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ """ Sets a control value """
+ self.je.ctrls[fld].SetValue(val)
+ if self.je.ctrls[fld] == self.je.ctrls[JetDefs.F_MIDIFILE]:
+ self.UpdateMaxMbt()
+
+ def OnOk(self, event):
+ """ Exits dialog box """
+ self.ShutdownPlayer()
+ if self.Validate():
+ self.je.ctrls[JetDefs.F_START].UnBindKillFocus()
+ self.je.ctrls[JetDefs.F_END].UnBindKillFocus()
+ self.EndModal(wx.ID_OK)
+
+ def Validate(self):
+ """ Validates the control values before exiting """
+ if not CompareMbt(self.je.ctrls[JetDefs.F_START].GetValue(), self.je.ctrls[JetDefs.F_END].GetValue()):
+ InfoMsg("Start/End", "The segment starting and ending times are illogical.")
+ self.je.ctrls[JetDefs.F_START].SetFocus()
+ return False
+ if len(self.je.ctrls[JetDefs.F_SEGNAME].GetValue()) == 0:
+ InfoMsg("Segment Name", "The segment must have a name.")
+ self.je.ctrls[JetDefs.F_SEGNAME].SetFocus()
+ return False
+ if len(self.je.ctrls[JetDefs.F_MIDIFILE].GetValue()) == 0:
+ InfoMsg("MIDI File", "The segment must have a midi file selected.")
+ self.je.ctrls[JetDefs.F_MIDIFILE].SetFocus()
+ return False
+ if not FileExists(self.je.ctrls[JetDefs.F_MIDIFILE].GetValue()):
+ InfoMsg("MIDI File", "The MIDI file does not exist.")
+ self.je.ctrls[JetDefs.F_MIDIFILE].SetFocus()
+ return False
+ if len(self.je.ctrls[JetDefs.F_DLSFILE].GetValue()) > 0:
+ if not FileExists(self.je.ctrls[JetDefs.F_DLSFILE].GetValue()):
+ InfoMsg("DLS File", "The DLS file does not exist.")
+ self.je.ctrls[JetDefs.F_DLSFILE].SetFocus()
+ return False
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].SetValue(ComputeMuteFlagsFromList(self.je.ctrls[JetDefs.F_MUTEFLAGS]))
+ return True
+
+ def SetSegment(self, mode):
+ """ Builds the segment info for graphing """
+ if mode == PLAY_SEGMENT:
+ jetevents = self.jetevents
+ segment = JetSegment(self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_MIDIFILE),
+ self.GetValue(JetDefs.F_START),
+ self.GetValue(JetDefs.F_END),
+ JetDefs.MBT_ZEROSTR,
+ self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_QUANTIZE),
+ jetevents,
+ self.GetValue(JetDefs.F_DLSFILE),
+ None,
+ self.GetValue(JetDefs.F_TRANSPOSE),
+ self.GetValue(JetDefs.F_REPEAT),
+ self.GetValue(JetDefs.F_MUTEFLAGS))
+ else:
+ jetevents = []
+ segment = JetSegment(self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_MIDIFILE),
+ JetDefs.MBT_ZEROSTR,
+ self.lastMidiInfo.endMbtStr,
+ JetDefs.MBT_ZEROSTR,
+ self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_QUANTIZE),
+ jetevents,
+ self.GetValue(JetDefs.F_DLSFILE),
+ None,
+ 0,
+ 0,
+ 0)
+ return segment
+
+ def OnEventChecked(self, index, checked):
+ """ Track is checked so mute or unmute it """
+ if self.Player is not None:
+ trackNum = self.je.ctrls[JetDefs.F_MUTEFLAGS].GetTrackNumber(index)
+ self.Player.SetMuteFlag(trackNum, checked)
+
+ def OnPlay(self, event):
+ """ Play the segment button pressed """
+ if self.je.ctrls[JetDefs.F_PLAY].GetLabel() == JetDefs.BUT_STOP:
+ self.Player.SetKeepPlayingFlag(False)
+ return
+
+ if not self.Validate():
+ return
+
+ self.playMode = PLAY_SEGMENT
+ self.graphSegment = self.SetSegment(self.graphMode)
+ self.UpdateGraph()
+ self.Player = PreviewPlayer(self.je.ctrls[JetDefs.F_PLAY], self.SetSegment(self.playMode))
+ self.Player.SetGraphCtrl(self.je.ctrls[JetDefs.F_GRAPH], self)
+ self.PlayerThread = thread.start_new_thread(self.Player .Start, ())
+
+ def OnPlayMidi(self, event):
+ """ Play the whole midi file pressed """
+ if self.je.ctrls[JetDefs.F_PLAYMIDI].GetLabel() == JetDefs.BUT_STOP:
+ self.Player.SetKeepPlayingFlag(False)
+ return
+
+ if not self.Validate():
+ return
+
+ self.playMode = PLAY_MIDI
+ self.graphSegment = self.SetSegment(self.graphMode)
+ self.UpdateGraph()
+ self.Player = PreviewPlayer(self.je.ctrls[JetDefs.F_PLAYMIDI], self.SetSegment(self.playMode))
+ self.Player.SetGraphCtrl(self.je.ctrls[JetDefs.F_GRAPH], self)
+ self.PlayerThread = thread.start_new_thread(self.Player .Start, ())
+
+ def OnSetGraphType(self, event):
+ """ Sets the type of graph """
+ self.SetGraphType(event.GetInt())
+
+ def SetGraphType(self, iMode):
+ """ Sets the type of graph """
+ if iMode == 1:
+ self.graphMode = PLAY_SEGMENT
+ else:
+ self.graphMode = PLAY_MIDI
+ self.graphSegment = self.SetSegment(self.graphMode)
+ self.UpdateGraph()
+
+ def OnGraphUpdate(self, evt):
+ """ Calls graph control to draw """
+ self.je.ctrls[JetDefs.F_GRAPH].DoDrawing()
+
+ def UpdateGraph(self):
+ """ Updates values for graph control """
+ if self.graphMode == PLAY_SEGMENT:
+ self.je.ctrls[JetDefs.F_GRAPH].LoadSegment(self.graphSegment, showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+ else:
+ if self.playMode == PLAY_SEGMENT:
+ iMidiMode = True
+ else:
+ iMidiMode = False
+ self.je.ctrls[JetDefs.F_GRAPH].LoadSegment(self.graphSegment,(self.GetValue(JetDefs.F_SEGNAME), self.GetValue(JetDefs.F_START), self.GetValue(JetDefs.F_END)), iMidiMode, showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def OnJetStatusUpdate(self, evt):
+ """ All UI needed by thread must be called via Postevent or OS X crashes """
+ if evt.mode == JetDefs.PST_UPD_LOCATION:
+ self.je.ctrls[JetDefs.F_GRAPH].UpdateLocation(evt.data)
+ elif evt.mode == JetDefs.PST_PLAY:
+ if self.playMode == PLAY_SEGMENT:
+ self.je.ctrls[JetDefs.F_PLAY].SetLabel(JetDefs.BUT_STOP)
+ self.je.ctrls[JetDefs.F_PLAYMIDI].Enabled = False
+ else:
+ self.je.ctrls[JetDefs.F_RDOGRAPH].Enabled = False
+ self.je.ctrls[JetDefs.F_PLAYMIDI].SetLabel(JetDefs.BUT_STOP)
+ self.je.ctrls[JetDefs.F_PLAY].Enabled = False
+
+ self.je.ctrls[JetDefs.F_PAUSE].Enabled = True
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+ elif evt.mode == JetDefs.PST_DONE:
+ self.je.ctrls[JetDefs.F_RDOGRAPH].Enabled = True
+ if self.playMode == PLAY_SEGMENT:
+ self.je.ctrls[JetDefs.F_PLAY].SetLabel(JetDefs.BUT_PLAYSEG)
+ self.je.ctrls[JetDefs.F_PLAYMIDI].Enabled = True
+ else:
+ self.je.ctrls[JetDefs.F_PLAYMIDI].SetLabel(JetDefs.BUT_PLAYMIDI)
+ self.je.ctrls[JetDefs.F_PLAY].Enabled = True
+
+ self.je.ctrls[JetDefs.F_PAUSE].Enabled = False
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+ elif evt.mode == JetDefs.PST_PAUSE:
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_RESUME)
+ elif evt.mode == JetDefs.PST_RESUME:
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+ elif evt.mode == JetDefs.PST_MIDI_INFO:
+ ClearRowSelections(self.je.ctrls[JetDefs.F_MUTEFLAGS])
+ self.md = evt.data
+ if self.md.err == 0:
+ self.je.ctrls[JetDefs.F_END].SetMaxMbt(self.md.maxMeasures+1,self.md.maxBeats,self.md.maxTicks)
+ if self.je.ctrls[JetDefs.F_END].GetValue() == JetDefs.MBT_ZEROSTR:
+ self.je.ctrls[JetDefs.F_END].SetValue((self.md.maxMeasures,0,0))
+ self.je.ctrls[JetDefs.F_START].SetMaxMbt(self.md.maxMeasures+1,self.md.maxBeats,self.md.maxTicks)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].DeleteAllItems()
+ loadEmpty = IniGetValue(self.currentJetConfigFile, JetDefs.INI_DISPEMPTYTRACKS, JetDefs.INI_DISPEMPTYTRACKS, 'bool', 'False')
+ for track in self.md.trackList:
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddTrackRow(track, loadEmpty)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].CheckTracks(self.je.ctrls[JetDefs.F_MUTEFLAGS].GetValue())
+ self.graphSegment = self.SetSegment(self.graphMode)
+ self.UpdateGraph()
+
+ def OnSetTrackDisplayOption(self, evt):
+ IniSetValue(self.currentJetConfigFile, JetDefs.INI_DISPEMPTYTRACKS, JetDefs.INI_DISPEMPTYTRACKS, self.je.ctrls[JetDefs.F_DISPEMPTYTRACKS].GetValue())
+ loadEmpty = IniGetValue(self.currentJetConfigFile, JetDefs.INI_DISPEMPTYTRACKS, JetDefs.INI_DISPEMPTYTRACKS, 'bool', 'False')
+ if self.md is not None:
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].DeleteAllItems()
+ if self.md.err == 0:
+ for track in self.md.trackList:
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddTrackRow(track, loadEmpty)
+
+ def OnPause(self, evt):
+ """ Pauses the playback """
+ self.Player.Pause()
+
+ def OnSetGraphOptions(self, evt):
+ """ Sets graph options """
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, self.je.ctrls[JetDefs.F_GRAPHLABELS].GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, self.je.ctrls[JetDefs.F_GRAPHAPPEVTS].GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, self.je.ctrls[JetDefs.F_GRAPHCLIPS].GetValue())
+ self.UpdateGraph()
+
+ def OnReplicate(self, evt):
+ dlg = JetReplicate("Replicate Segment")
+ dlg.SetValue(JetDefs.F_RPREPLACE, True)
+ dlg.SetName(self.GetValue(JetDefs.F_SEGNAME))
+ dlg.event_type = "SEGMENT"
+ dlg.event_max = self.je.ctrls[JetDefs.F_START].GetMaxMbt()
+ dlg.length = MbtDifference(ConvertStrTimeToTuple(self.GetValue(JetDefs.F_START)), ConvertStrTimeToTuple(self.GetValue(JetDefs.F_END)))
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ self.replicatePrefix = dlg.GetValue(JetDefs.F_RPPREFIX)
+ self.lstReplicate = dlg.lstReplicate
+ self.chkReplaceMatching = dlg.GetValue(JetDefs.F_RPREPLACE)
+ self.EndModal(wx.ID_OK)
+ else:
+ dlg.Destroy()
+
+class EventEdit(wx.Dialog):
+ """ Event edit dialog box """
+ def __init__(self, title, currentJetConfigFile):
+ wx.Dialog.__init__(self, None, -1, title)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.title = title
+ self.currentJetConfigFile = currentJetConfigFile
+ self.je = JetEdit(self, "EVTDLG_CTRLS", self)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_TRACK, JetDefs.MUTEGRD_TRACK)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_CHANNEL, JetDefs.MUTEGRD_CHANNEL)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddCol(JetDefs.GRD_NAME, JetDefs.MUTEGRD_NAME)
+ self.je.ctrls[JetDefs.F_ESTART].SetChangeCallbackFct(self.UpdateGraph)
+ self.je.ctrls[JetDefs.F_EEND].SetChangeCallbackFct(self.UpdateGraph)
+ self.je.ctrls[JetDefs.F_GRAPHLABELS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'))
+ self.je.ctrls[JetDefs.F_GRAPHCLIPS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'))
+ self.je.ctrls[JetDefs.F_GRAPHAPPEVTS].SetValue(IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+ EVT_JET_STATUS(self, self.OnJetStatusUpdate)
+ self.segment = None
+ self.Player = None
+ self.event_id = 1
+ self.replicatePrefix = ""
+ self.lstReplicate = []
+ self.chkReplaceMatching = False
+
+ wx.EVT_CLOSE(self, self.OnClose)
+ self.SetSize(JetDefs.EVTDLG_SIZE)
+ self.CenterOnParent()
+
+ def OnGraphUpdate(self, evt):
+ """ Calls the graph module to update the graph """
+ self.je.ctrls[JetDefs.F_GRAPH].DoDrawing()
+
+ def OnJetStatusUpdate(self, evt):
+ """ Updates to UI needed by play thread come through here otherwise OS X crashes """
+ if evt.mode == JetDefs.PST_UPD_LOCATION:
+ self.je.ctrls[JetDefs.F_GRAPH].UpdateLocation(evt.data)
+ elif evt.mode == JetDefs.PST_PLAY:
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+ self.je.ctrls[JetDefs.F_PLAY].SetLabel(JetDefs.BUT_STOP)
+ self.je.ctrls[JetDefs.F_PAUSE].Enabled = True
+ self.je.ctrls[JetDefs.F_ETRIGGERBUT].Enabled = True
+ self.je.ctrls[JetDefs.F_EMUTEBUT].Enabled = True
+ elif evt.mode == JetDefs.PST_DONE:
+ self.je.ctrls[JetDefs.F_EMUTEBUT].SetLabel(JetDefs.BUT_UNMUTE)
+ self.je.ctrls[JetDefs.F_PLAY].SetLabel(JetDefs.BUT_PLAY)
+ self.je.ctrls[JetDefs.F_PAUSE].Enabled = False
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+ self.je.ctrls[JetDefs.F_ETRIGGERBUT].Enabled = False
+ self.je.ctrls[JetDefs.F_EMUTEBUT].Enabled = False
+ elif evt.mode == JetDefs.PST_PAUSE:
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_RESUME)
+ elif evt.mode == JetDefs.PST_RESUME:
+ self.je.ctrls[JetDefs.F_PAUSE].SetLabel(JetDefs.BUT_PAUSE)
+
+ def OnPause(self, evt):
+ """ Pause the player """
+ self.Player.Pause()
+
+ def UpdateGraph(self):
+ """ Called back from player thread to update the graph """
+ if len(self.segment.jetevents) == 0:
+ self.segment.jetevents.append(JetEvent(self.je.ctrls[JetDefs.F_ENAME].GetValue(),
+ self.je.ctrls[JetDefs.F_ETYPE].GetValue(),
+ 1,
+ self.je.ctrls[JetDefs.F_ETRACK].GetValue(),
+ self.je.ctrls[JetDefs.F_ECHANNEL].GetValue(),
+ self.je.ctrls[JetDefs.F_ESTART].GetValue(),
+ self.je.ctrls[JetDefs.F_EEND].GetValue()))
+
+ self.segment.jetevents[0].event_name = self.je.ctrls[JetDefs.F_ENAME].GetValue()
+ self.segment.jetevents[0].event_type = self.je.ctrls[JetDefs.F_ETYPE].GetValue()
+ self.segment.jetevents[0].event_start = self.je.ctrls[JetDefs.F_ESTART].GetValue()
+ self.segment.jetevents[0].event_end = self.je.ctrls[JetDefs.F_EEND].GetValue()
+ self.je.ctrls[JetDefs.F_GRAPH].LoadSegment(self.segment, showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def OnClose(self, event):
+ """ Called when dialog is closed """
+ self.ShutdownPlayer()
+ self.je.ctrls[JetDefs.F_ESTART].UnBindKillFocus()
+ self.je.ctrls[JetDefs.F_EEND].UnBindKillFocus()
+ self.EndModal(wx.ID_CANCEL)
+
+ def ShutdownPlayer(self):
+ """ Sets flag to kill play loop """
+ if self.Player is not None:
+ self.Player.SetKeepPlayingFlag(False)
+
+ def GetValue(self, fld):
+ """ Gets the value of a control """
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ """ Sets the value of a control """
+ self.je.ctrls[fld].SetValue(val)
+
+ def SetEventId(self):
+ """ Sets the eventid value """
+ if self.title == JetDefs.MAIN_ADDEVENTTITLE:
+ iNextEventId = -1
+ for evt in self.segment.jetevents:
+ if evt.event_type == JetDefs.E_CLIP:
+ if iNextEventId < evt.event_id:
+ iNextEventId = evt.event_id
+ self.je.ctrls[JetDefs.F_EEVENTID].SetValue(iNextEventId + 1)
+
+ def OnEventSelect(self, event=None):
+ """ Adjusts the dialog box controls for various types of events """
+ eType = self.je.ctrls[JetDefs.F_ETYPE].GetValue()
+ if eType == JetDefs.E_EOS:
+ self.je.ctrls[JetDefs.F_ENAME].SetValue(JetDefs.E_EOS)
+ self.je.ctrls[JetDefs.F_ENAME].Enable(False)
+ self.je.ctrls[JetDefs.F_ESTART].Enable(False)
+ self.je.ctrls[JetDefs.F_EEND].Enable(True)
+ self.je.ctrls[JetDefs.F_ETRIGGERBUT].Enable(False)
+ self.je.ctrls[JetDefs.F_EEVENTID].Enable(False)
+ elif eType == JetDefs.E_CLIP:
+ if len(self.je.ctrls[JetDefs.F_ENAME].GetValue()) == 0 or self.je.ctrls[JetDefs.F_ENAME].GetValue() == JetDefs.E_EOS or self.je.ctrls[JetDefs.F_ENAME].GetValue() == JetDefs.E_APP:
+ self.je.ctrls[JetDefs.F_ENAME].SetValue(JetDefs.E_CLIP)
+ self.je.ctrls[JetDefs.F_ENAME].Enable(True)
+ self.je.ctrls[JetDefs.F_ESTART].Enable(True)
+ self.je.ctrls[JetDefs.F_EEND].Enable(True)
+ self.je.ctrls[JetDefs.F_ETRIGGERBUT].Enable(True)
+ self.je.ctrls[JetDefs.F_EEVENTID].Enable(True)
+ self.je.ctrls[JetDefs.F_EEVENTID].SetRange(JetDefs.EVENTID_MIN, JetDefs.EVENTID_MAX)
+ if self.je.ctrls[JetDefs.F_EEVENTID].GetValue() < JetDefs.EVENTID_MIN:
+ self.je.ctrls[JetDefs.F_EEVENTID].SetValue(JetDefs.EVENTID_MIN)
+ if self.je.ctrls[JetDefs.F_EEVENTID].GetValue() > JetDefs.EVENTID_MAX:
+ self.je.ctrls[JetDefs.F_EEVENTID].SetValue(JetDefs.EVENTID_MAX)
+ self.SetEventId()
+ elif eType == JetDefs.E_APP:
+ if len(self.je.ctrls[JetDefs.F_ENAME].GetValue()) == 0 or self.je.ctrls[JetDefs.F_ENAME].GetValue() == JetDefs.E_EOS:
+ self.je.ctrls[JetDefs.F_ENAME].SetValue(JetDefs.E_APP)
+ self.je.ctrls[JetDefs.F_ENAME].Enable(True)
+ self.je.ctrls[JetDefs.F_ESTART].Enable(True)
+ self.je.ctrls[JetDefs.F_EEND].Enable(False)
+ self.je.ctrls[JetDefs.F_ETRIGGERBUT].Enable(False)
+ self.je.ctrls[JetDefs.F_EEVENTID].Enable(True)
+ self.je.ctrls[JetDefs.F_EEVENTID].SetRange(JetDefs.APPCONTROLLERID_MIN, JetDefs.APPCONTROLLERID_MAX)
+ if self.je.ctrls[JetDefs.F_EEVENTID].GetValue() < JetDefs.APPCONTROLLERID_MIN:
+ self.je.ctrls[JetDefs.F_EEVENTID].SetValue(JetDefs.APPCONTROLLERID_MIN)
+ if self.je.ctrls[JetDefs.F_EEVENTID].GetValue() > JetDefs.APPCONTROLLERID_MAX:
+ self.je.ctrls[JetDefs.F_EEVENTID].SetValue(JetDefs.APPCONTROLLERID_MAX)
+
+ def OnOk(self, event):
+ """ Exits the dialog box """
+ self.ShutdownPlayer()
+ if self.Validate():
+ self.je.ctrls[JetDefs.F_ESTART].UnBindKillFocus()
+ self.je.ctrls[JetDefs.F_EEND].UnBindKillFocus()
+ self.EndModal(wx.ID_OK)
+
+ def Validate(self):
+ """ Validates the control values prior to exiting """
+ if len(self.je.ctrls[JetDefs.F_ENAME].GetValue()) == 0:
+ InfoMsg("Event Name", "The event must have a name.")
+ self.je.ctrls[JetDefs.F_ENAME].SetFocus()
+ return False
+ if len(self.je.ctrls[JetDefs.F_ETYPE].GetValue()) == 0:
+ InfoMsg("Event Name", "The event type must be selected.")
+ self.je.ctrls[JetDefs.F_ETYPE].SetFocus()
+ return False
+ if self.je.ctrls[JetDefs.F_ETYPE].GetValue() == JetDefs.E_CLIP:
+ if not CompareMbt(self.je.ctrls[JetDefs.F_ESTART].GetValue(), self.je.ctrls[JetDefs.F_EEND].GetValue()):
+ InfoMsg("Start/End", "The event starting and ending times are illogical.")
+ self.je.ctrls[JetDefs.F_ESTART].SetFocus()
+ return False
+ if MbtVal(self.je.ctrls[JetDefs.F_ESTART].GetValue()) < MbtVal(self.je.ctrls[JetDefs.F_START].GetValue()):
+ InfoMsg("Event Starting Time", "The event starting time is illogical.")
+ self.je.ctrls[JetDefs.F_ESTART].SetFocus()
+ return False
+ if MbtVal(self.je.ctrls[JetDefs.F_EEND].GetValue()) > MbtVal(self.je.ctrls[JetDefs.F_END].GetValue()):
+ InfoMsg("Event Ending Time", "The event ending time is illogical.")
+ self.je.ctrls[JetDefs.F_ESTART].SetFocus()
+ return False
+ if self.je.ctrls[JetDefs.F_ETYPE].GetValue() == JetDefs.E_APP:
+ self.je.ctrls[JetDefs.F_EEND].SetValue(self.je.ctrls[JetDefs.F_ESTART].GetValue())
+ if self.je.ctrls[JetDefs.F_ETYPE].GetValue() == JetDefs.E_EOS:
+ self.je.ctrls[JetDefs.F_ESTART].SetValue(self.je.ctrls[JetDefs.F_EEND].GetValue())
+ return True
+
+ def SetSegment(self, segment):
+ """ Sets the segment values, then calls the graph update """
+ self.segment = segment
+ md = GetMidiInfo(segment.filename)
+ if md.err == 0:
+ self.SetValue(JetDefs.F_SEGNAME, segment.segname)
+ self.SetValue(JetDefs.F_MUTEFLAGS, segment.mute_flags)
+ self.SetValue(JetDefs.F_MIDIFILE, segment.filename)
+ self.SetValue(JetDefs.F_DLSFILE, segment.dlsfile)
+ self.SetValue(JetDefs.F_START, segment.start)
+ self.SetValue(JetDefs.F_END, segment.end)
+ self.SetValue(JetDefs.F_QUANTIZE, segment.quantize)
+ self.SetValue(JetDefs.F_TRANSPOSE, segment.transpose)
+ self.SetValue(JetDefs.F_REPEAT, segment.repeat)
+ maxMeasures = abs(int(self.je.ctrls[JetDefs.F_END].GetValue('int')[0]))
+ self.je.ctrls[JetDefs.F_EEND].SetMaxMbt(maxMeasures+1,md.maxBeats,md.maxTicks)
+ self.je.ctrls[JetDefs.F_ESTART].SetMaxMbt(maxMeasures+1,md.maxBeats,md.maxTicks)
+ minMeasures = abs(int(self.je.ctrls[JetDefs.F_START].GetValue('int')[0]))
+ self.je.ctrls[JetDefs.F_EEND].SetMinMbt(minMeasures+1,0,0)
+ self.je.ctrls[JetDefs.F_ESTART].SetMinMbt(minMeasures+1,0,0)
+ self.je.ctrls[JetDefs.F_END].GetValue('int')
+ self.je.ctrls[JetDefs.F_ETRACK].SetRange(1, md.maxTracks)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].DeleteAllItems()
+ for track in md.trackList:
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].AddTrackRow(track)
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].CheckTracks(self.je.ctrls[JetDefs.F_MUTEFLAGS].GetValue())
+ self.je.ctrls[JetDefs.F_MUTEFLAGS].SetTextColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
+ self.je.ctrls[JetDefs.F_GRAPH].LoadSegment(segment, showLabels=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, 'bool', 'True'), showClips=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, 'bool', 'True'), showAppEvts=IniGetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, 'bool', 'True'))
+
+ def OnPlay(self, event):
+ """ Plays the segment allowing interaction with events """
+ if self.je.ctrls[JetDefs.F_PLAY].GetLabel() == JetDefs.BUT_STOP:
+ self.Player.SetKeepPlayingFlag(False)
+ return
+
+ if not self.Validate():
+ return
+
+ jetevents = []
+ jetevents.append(JetEvent(self.GetValue(JetDefs.F_ENAME), self.GetValue(JetDefs.F_ETYPE),
+ self.event_id, int(self.GetValue(JetDefs.F_ETRACK)),
+ int(self.GetValue(JetDefs.F_ECHANNEL)),
+ self.GetValue(JetDefs.F_ESTART), self.GetValue(JetDefs.F_EEND)))
+
+ segment = JetSegment(self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_MIDIFILE),
+ self.GetValue(JetDefs.F_START),
+ self.GetValue(JetDefs.F_END),
+ JetDefs.MBT_ZEROSTR,
+ self.GetValue(JetDefs.F_SEGNAME),
+ self.GetValue(JetDefs.F_QUANTIZE),
+ jetevents,
+ self.GetValue(JetDefs.F_DLSFILE),
+ None,
+ self.GetValue(JetDefs.F_TRANSPOSE),
+ self.GetValue(JetDefs.F_REPEAT),
+ self.GetValue(JetDefs.F_MUTEFLAGS))
+
+ self.Player = PreviewPlayer(self.je.ctrls[JetDefs.F_PLAY], segment)
+ self.Player.SetGraphCtrl(self.je.ctrls[JetDefs.F_GRAPH], self)
+ self.je.ctrls[JetDefs.F_GRAPH].ClickCallbackFct = self.GraphTriggerClip
+ self.Player.trigger_button = self.je.ctrls[JetDefs.F_ETRIGGERBUT]
+ self.Player.mute_button = self.je.ctrls[JetDefs.F_EMUTEBUT]
+ thread.start_new_thread(self.Player .Start, ())
+
+ def GraphTriggerClip(self, sClipName, iEventId):
+ """ Triggers a clip via graph """
+ if self.Player is not None:
+ self.Player.GraphTriggerClip(sClipName, iEventId)
+
+ def OnMute(self, event):
+ """ Mutes a track """
+ if self.Player is not None:
+ self.Player.MuteTrackViaButton(self.je.ctrls[JetDefs.F_EMUTEBUT],
+ int(self.je.ctrls[JetDefs.F_ETRACK].GetValue()))
+
+ def OnTriggerClip(self, event):
+ """ Triggers a clip """
+ if self.Player is not None:
+ self.Player.TriggerClip(self.event_id)
+
+ def OnSetGraphOptions(self, evt):
+ """ Sets graph options """
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHLABELS, JetDefs.F_GRAPHLABELS, self.je.ctrls[JetDefs.F_GRAPHLABELS].GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHAPPEVTS, JetDefs.F_GRAPHAPPEVTS, self.je.ctrls[JetDefs.F_GRAPHAPPEVTS].GetValue())
+ IniSetValue(self.currentJetConfigFile, JetDefs.F_GRAPHCLIPS, JetDefs.F_GRAPHCLIPS, self.je.ctrls[JetDefs.F_GRAPHCLIPS].GetValue())
+ self.UpdateGraph()
+
+ def OnReplicate(self, evt):
+ dlg = JetReplicate("Replicate Event")
+ dlg.SetValue(JetDefs.F_RPREPLACE, True)
+ dlg.SetName(self.GetValue(JetDefs.F_ENAME))
+ dlg.event_type = self.GetValue(JetDefs.F_ETYPE)
+ dlg.event_max = self.segment.end
+ dlg.length = MbtDifference(ConvertStrTimeToTuple(self.GetValue(JetDefs.F_ESTART)), ConvertStrTimeToTuple(self.GetValue(JetDefs.F_EEND)))
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ self.replicatePrefix = dlg.GetValue(JetDefs.F_RPPREFIX)
+ self.lstReplicate = dlg.lstReplicate
+ self.chkReplaceMatching = dlg.GetValue(JetDefs.F_RPREPLACE)
+ self.EndModal(wx.ID_OK)
+ else:
+ dlg.Destroy()
+
+class JetReplicate(wx.Dialog):
+ """ Replicate dialog box """
+ def __init__(self, title):
+ wx.Dialog.__init__(self, None, -1, title)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.je = JetEdit(self, "REPLICATE_CTRLS", self)
+
+ self.je.ctrls[JetDefs.F_RPINCREMENT].SetMinMbt(0,0,0)
+ self.je.ctrls[JetDefs.F_RPINCREMENT].SetValue((-1,-1,-1))
+ self.je.ctrls[JetDefs.F_RPNUMBER].SetValue(1)
+ for title, width, fld in JetDefs.REPLICATE_GRID:
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].AddCol(title, width)
+ self.lstReplicate = []
+
+ self.SetSize(JetDefs.REPLICATE_SIZE)
+ self.CenterOnParent()
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self, fld):
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ self.je.ctrls[fld].SetValue(val)
+
+ def SetName(self, name):
+ for i in range(len(name), 1, -1):
+ if not name[i-1].isdigit() and name[i-1] <> ' ':
+ break
+ else:
+ name = name[0:i-1]
+ self.SetValue(JetDefs.F_RPPREFIX, name)
+
+ def Validate(self):
+ if self.GetValue(JetDefs.F_RPPREFIX) == '':
+ InfoMsg("Message", "Prefix is required.")
+ return False
+ return True
+
+ def OnPreview(self, event):
+ if not self.Validate():
+ return
+ start = MbtVal(self.GetValue(JetDefs.F_ESTART))
+ max = MbtVal(self.event_max)
+ increment = MbtVal((self.je.ctrls[JetDefs.F_RPINCREMENT].GetMeasure(), self.je.ctrls[JetDefs.F_RPINCREMENT].GetBeat(), self.je.ctrls[JetDefs.F_RPINCREMENT].GetTick()))
+
+ self.lstReplicate = []
+ iTo = int(self.GetValue(JetDefs.F_RPNUMBER))
+ for i in range(iTo):
+ evt_name = "%s %.2d" % (self.GetValue(JetDefs.F_RPPREFIX), i)
+ s_ticks = start + (i * increment)
+ s_mbt = TicksToMbt(s_ticks)
+ evt_start = "%d:%d:%d" % (s_mbt[0]+1, s_mbt[1]+1, s_mbt[2])
+ if self.event_type == JetDefs.E_CLIP or self.event_type == "SEGMENT":
+ e_ticks = s_ticks + self.length
+ e_mbt = TicksToMbt(e_ticks)
+ evt_end = "%d:%d:%d" % (e_mbt[0]+1, e_mbt[1]+1, e_mbt[2])
+ else:
+ e_ticks = s_ticks
+ evt_end = evt_start
+ if s_ticks <= max and e_ticks <= max:
+ self.lstReplicate.append((evt_name, evt_start, evt_end))
+
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].DeleteAllItems()
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].AddRows(self.lstReplicate)
+
+class JetMove(wx.Dialog):
+ """ Move events dialog box """
+ def __init__(self, title):
+ wx.Dialog.__init__(self, None, -1, title)
+ self.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
+ self.je = JetEdit(self, "MOVE_CTRLS", self)
+
+ self.je.ctrls[JetDefs.F_RPINCREMENT].SetMinMbt(-999,-4,-480)
+ self.je.ctrls[JetDefs.F_RPINCREMENT].SetValue((-1,-1,-1))
+ for title, width, fld in JetDefs.REPLICATE_GRID:
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].AddCol(title, width)
+ self.lstMove = []
+ self.lstMoveMbt = []
+ self.lstMoveItems = []
+
+ self.SetSize(JetDefs.REPLICATE_SIZE)
+ self.CenterOnParent()
+
+ def OnOk(self, event):
+ self.EndModal(wx.ID_OK)
+
+ def GetValue(self, fld):
+ return self.je.ctrls[fld].GetValue()
+
+ def SetValue(self, fld, val):
+ self.je.ctrls[fld].SetValue(val)
+
+ def OnPreview(self, event):
+ increment = MbtVal((abs(self.je.ctrls[JetDefs.F_RPINCREMENT].GetMeasure()), abs(self.je.ctrls[JetDefs.F_RPINCREMENT].GetBeat()), abs(self.je.ctrls[JetDefs.F_RPINCREMENT].GetTick())))
+ if self.je.ctrls[JetDefs.F_RPINCREMENT].GetMeasure() < 0 or self.je.ctrls[JetDefs.F_RPINCREMENT].GetBeat() < 0 or self.je.ctrls[JetDefs.F_RPINCREMENT].GetTick() < 0:
+ increment = 0 - increment
+ self.lstMove = []
+ self.lstMoveMbt = []
+ for itm in self.lstMoveItems:
+ max = MbtVal(itm[3])
+ evt_name = itm[0]
+ start = MbtVal(itm[1])
+ s_ticks = start + increment
+
+ s_mbt = TicksToMbt(s_ticks)
+ evt_start = "%d:%d:%d" % (s_mbt[0]+1, s_mbt[1]+1, s_mbt[2])
+ evt_start_save = "%d:%d:%d" % s_mbt
+
+ end = MbtVal(itm[2])
+ e_ticks = end + increment
+ e_mbt = TicksToMbt(e_ticks)
+ evt_end = "%d:%d:%d" % (e_mbt[0]+1, e_mbt[1]+1, e_mbt[2])
+ evt_end_save = "%d:%d:%d" % e_mbt
+
+ if s_ticks <= max and e_ticks <= max and s_ticks >= 0 and e_ticks >= 0:
+ self.lstMove.append((evt_name, evt_start, evt_end))
+ self.lstMoveMbt.append((evt_name, evt_start_save, evt_end_save))
+
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].DeleteAllItems()
+ self.je.ctrls[JetDefs.F_RPGRDPREVIEW].AddRows(self.lstMove)
+
+if __name__ == '__main1__':
+ """ Test dialogs """
+ app = wx.PySimpleApp()
+
+ #dlg = JetOpen()
+ #dlg = JetPropertiesDialog()
+ #dlg = ExportDialog("Export Jet File")
+ #dlg = JetAbout()
+
+ dlg = JetReplicate("Replicate Event")
+ dlg.SetValue(JetDefs.F_RPREPLACE, True)
+ dlg.event_max = "5:0:0"
+ dlg.event_type = JetDefs.E_APP
+ dlg.length = 480
+ dlg.SetName("abc 02")
+ result = dlg.ShowModal()
+ if result == wx.ID_OK:
+ print("OK")
+
+ dlg.Destroy()
+
+if __name__ == '__main1__':
+ """ Test Segment dialog """
+ app = wx.PySimpleApp()
+
+ helpProvider = wx.SimpleHelpProvider()
+ wx.HelpProvider_Set(helpProvider)
+
+ dlg = SegEdit("Segments", JetDefs.UNTITLED_FILE)
+ dlg.SetValue(JetDefs.F_SEGNAME, "Test Segment Name")
+ dlg.SetValue(JetDefs.F_MIDIFILE, '/Users/BHruska/JetContent/jenn_Burning Line.mid')
+ dlg.SetValue(JetDefs.F_MIDIFILE, 'C:/_Data/JetCreator/JetDemo1/jenn_Burning Line.mid')
+ dlg.SetValue(JetDefs.F_DLSFILE, '')
+ dlg.SetValue(JetDefs.F_START, '4:0:0')
+ dlg.SetValue(JetDefs.F_END, '8:0:0')
+ dlg.SetValue(JetDefs.F_QUANTIZE, 6)
+
+ result = dlg.ShowModal()
+ dlg.Destroy()
+
+if __name__ == '__main__':
+ """ Test Event dialog """
+ app = wx.PySimpleApp()
+
+ jetevents = []
+
+ segment = JetSegment("Test Segment Name", 'C:/_Data/JetCreator/JetDemo1/jenn_Burning Line.mid',
+ '0:0:0', '4:0:0', None,
+ "Test Segment Name", 6, jetevents,
+ '', None, 0,0,3)
+
+ dlg = EventEdit("Event Edit", JetDefs.UNTITLED_FILE)
+ dlg.SetValue(JetDefs.F_ENAME, "Test Event Name")
+ dlg.SetValue(JetDefs.F_ETYPE, "TriggerClip")
+ dlg.SetSegment(segment)
+
+ result = dlg.ShowModal()
+ dlg.Destroy()
+
+
+
diff --git a/jet_tools/JetCreator/JetFile.py b/jet_tools/JetCreator/JetFile.py
new file mode 100755
index 0000000..d29db7e
--- /dev/null
+++ b/jet_tools/JetCreator/JetFile.py
@@ -0,0 +1,775 @@
+"""
+ File:
+ JetFile.py
+
+ Contents and purpose:
+ Auditions a jet file to simulate interactive music functions
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from __future__ import with_statement
+
+import logging
+import ConfigParser
+import struct
+import os
+import sys
+import midifile
+
+from JetUtils import *
+from JetDefs import *
+
+VERSION = '0.1'
+
+# JET file defines
+JET_HEADER_STRUCT = '<4sl'
+JET_HEADER_TAG = 'JET '
+JET_VERSION = 0x01000000
+
+# JET chunk tags
+JET_INFO_CHUNK = 'JINF'
+JET_SMF_CHUNK = 'JSMF'
+JET_DLS_CHUNK = 'JDLS'
+
+# JINF defines
+JINF_STRUCT = '<4sl4sl4sl4sl'
+JINF_JET_VERSION = 'JVER'
+JINF_NUM_SMF_CHUNKS = 'SMF#'
+JINF_NUM_DLS_CHUNKS = 'DLS#'
+
+# JCOP defines
+JCOP_STRUCT = '<4sl'
+JCOP_CHUNK = 'JCOP'
+
+# JAPP defines
+JAPP_STRUCT = '<4sl'
+JAPP_CHUNK = 'JAPP'
+
+# config file defines
+OUTPUT_SECTION = 'output'
+OUTPUT_FILENAME = 'filename'
+OUTPUT_COPYRIGHT = 'copyright'
+OUTPUT_APP_DATA = 'app_data'
+OUTPUT_CHASE_CONTROLLERS = 'chase_controllers'
+OUTPUT_OMIT_EMPTY_TRACKS = 'omit_empty_tracks'
+SEGMENT_SECTION = 'segment'
+SEGMENT_FILENAME = 'filename'
+SEGMENT_DLSFILE = 'dlsfile'
+SEGMENT_NAME = 'segname'
+SEGMENT_START = 'start'
+SEGMENT_END = 'end'
+SEGMENT_END_MARKER = 'end_marker'
+SEGMENT_QUANTIZE = 'quantize'
+SEGMENT_OUTPUT = 'output'
+SEGMENT_LENGTH = 'length'
+SEGMENT_DUMP_FILE = 'dump'
+SEGMENT_TRANSPOSE = 'transpose'
+SEGMENT_REPEAT = 'repeat'
+SEGMENT_MUTE_FLAGS = 'mute_flags'
+LIBRARY_SECTION = 'libraries'
+LIBRARY_FILENAME = 'lib'
+CLIP_PREFIX = 'clip'
+APP_PREFIX = 'app'
+
+# JET events
+JET_EVENT_MARKER = 102
+JET_MARKER_LOOP_END = 0
+JET_EVENT_TRIGGER_CLIP = 103
+
+class JetSegment (object):
+ """ Class to hold segments """
+ def __init__ (self, segname, filename, start=None, end=None, length=None, output=None, quantize=None, jetevents=[], dlsfile=None, dump_file=None, transpose=0, repeat=0, mute_flags=0):
+ self.segname = segname
+ self.filename = filename
+ self.dlsfile = dlsfile
+ self.start = start
+ self.end = end
+ self.length = length
+ self.output = output
+ self.quantize = quantize
+ self.dump_file = dump_file
+ self.jetevents = jetevents
+ #API FIELDS FOR UI
+ self.transpose = transpose
+ self.repeat = repeat
+ self.mute_flags = mute_flags
+
+class JetEvent (object):
+ """ Class to hold events """
+ def __init__(self, event_name, event_type, event_id, track_num, channel_num, event_start, event_end):
+ self.event_name = event_name
+ self.event_type = event_type
+ self.event_id = event_id
+ self.track_num = track_num
+ self.channel_num = channel_num
+ self.event_start = event_start
+ self.event_end = event_end
+
+class JetFileException (Exception):
+ """ Exceptions class """
+ def __init__ (self, msg):
+ self.msg = msg
+ def __str__ (self):
+ return self.msg
+
+class JetSegmentFile (midifile.MIDIFile):
+ def ConvertMusicTimeToTicks (self, s):
+ measures, beats, ticks = s.split(':',3)
+ return self.ConvertToTicks(int(measures), int(beats), int(ticks))
+
+ def ExtractEvents (self, start, end, length, quantize, chase_controllers):
+ if (start is None) and (end is None) and (length is None):
+ logging.debug('ExtractEvents: No change')
+ return
+
+ if start is not None:
+ start = self.ConvertMusicTimeToTicks(start)
+ else:
+ start = 0
+ if end is not None:
+ end = self.ConvertMusicTimeToTicks(end)
+ elif length is not None:
+ length = self.ConvertMusicTimeToTicks(length)
+ end = start + length
+
+ if quantize is not None:
+ quantize = int(quantize)
+ else:
+ quantize = 0
+
+ self.Trim(start, end, quantize, chase_controllers=chase_controllers)
+ #self.DumpTracks()
+
+ def SyncClips (self):
+ """Add controller events to the start of a clip to keep it synced."""
+ values = None
+ last_seq = 0
+ for track in self.tracks:
+ for event in track.events:
+
+ # find start of clip and chase events from last save point
+ if (event.msg_type == midifile.CONTROL_CHANGE) and \
+ (event.controller == JET_EVENT_TRIGGER_CLIP) and \
+ ((event.value & 0x40) == 0x40):
+ logging.debug('Syncing clip at %d ticks' % event.ticks)
+ values = track.events.ChaseControllers(event.seq, last_seq, values)
+
+ #BTH; Seems to fix chase controller bug when multiple clips within segment
+ #last_seq = event.seq
+
+ # generate event list from default values
+ clip_events = values.GenerateEventList(event.ticks)
+
+ #for evt in clip_events:
+ # logging.info(evt)
+
+ track.events.InsertEvents(clip_events, event.seq + 1)
+
+ def AddJetEvents (self, jetevents):
+ for jet_event in jetevents:
+ if jet_event.event_type == JetDefs.E_CLIP:
+ #DumpEvent(jet_event)
+
+ # sanity check
+ if jet_event.track_num >= len(self.tracks):
+ raise JetFileException('Track number %d of out of range for clip' % jet_event.track_num)
+ if jet_event.channel_num > 15:
+ raise JetFileException('Channel number %d of out of range for clip' % jet_event.channel_num)
+ if jet_event.event_id > 63:
+ raise JetFileException('event_id %d of out of range for clip' % jet_event.event_id)
+
+ logging.debug('Adding trigger event for clip %d @ %s and %s' % (jet_event.event_id, jet_event.event_start, jet_event.event_end))
+
+ events = midifile.EventList()
+ events.append(midifile.ControlChangeEvent(
+ self.ConvertMusicTimeToTicks(jet_event.event_start),
+ 0,
+ jet_event.channel_num,
+ JET_EVENT_TRIGGER_CLIP,
+ jet_event.event_id | 0x40))
+
+ events.append(midifile.ControlChangeEvent(
+ self.ConvertMusicTimeToTicks(jet_event.event_end),
+ sys.maxint,
+ jet_event.channel_num,
+ JET_EVENT_TRIGGER_CLIP,
+ jet_event.event_id))
+
+ # merge trigger events
+ self.tracks[jet_event.track_num].events.MergeEvents(events)
+
+ elif jet_event.event_type == JetDefs.E_EOS:
+ if jet_event.track_num >= len(self.tracks):
+ raise JetFileException('Track number %d of out of range for end marker' % jet_event.track_num)
+ if jet_event.channel_num > 15:
+ raise JetFileException('Channel number %d of out of range for end marker' % jet_event.channel_num)
+
+ events = midifile.EventList()
+ logging.debug('Adding end marker at %s' % jet_event.event_start)
+ events.append(midifile.ControlChangeEvent(
+ self.ConvertMusicTimeToTicks(jet_event.event_start),
+ 0,
+ jet_event.channel_num,
+ JET_EVENT_MARKER,
+ JET_MARKER_LOOP_END))
+ self.tracks[jet_event.track_num].events.MergeEvents(events)
+
+ elif jet_event.event_type == JetDefs.E_APP:
+ if jet_event.track_num >= len(self.tracks):
+ raise JetFileException('Track number %d of out of range for app marker' % jet_event.track_num)
+ if jet_event.channel_num > 15:
+ raise JetFileException('Channel number %d of out of range for app marker' % jet_event.channel_num)
+ if jet_event.event_id > 83 or jet_event.event_id < 80:
+ raise JetFileException('EventID %d out of range for application controller' % jet_event.event_id)
+
+ events = midifile.EventList()
+ logging.debug('Adding application controller at %s' % jet_event.event_start)
+ events.append(midifile.ControlChangeEvent(
+ self.ConvertMusicTimeToTicks(jet_event.event_start),
+ 0,
+ jet_event.channel_num,
+ jet_event.event_id,
+ jet_event.event_id))
+ self.tracks[jet_event.track_num].events.MergeEvents(events)
+
+class JetFile (object):
+ """Write a JET file based on a configuration file."""
+ def __init__ (self, config_file, options):
+ self.config_file = config_file
+ self.config = config = ConfigParser.ConfigParser()
+ if self.config_file == "":
+ self.InitializeConfig(JetDefs.UNTITLED_FILE)
+ if not FileExists(self.config_file):
+ self.InitializeConfig(self.config_file)
+
+ config.read(self.config_file)
+ self.ParseConfig(options)
+
+ def DumpConfig (self):
+ """Drump configuration to log file."""
+ # dump configuration
+ config = self.config
+ for section in config.sections():
+ logging.debug('[%s]' % section)
+ for option, value in config.items(section):
+ logging.debug('%s: %s' % (option, value))
+
+ def ParseConfig (self, options):
+ """Validate the configuration."""
+ # check for output name
+ config = self.config
+ if config.has_option(OUTPUT_SECTION, OUTPUT_FILENAME):
+ config.filename = config.get(OUTPUT_SECTION, OUTPUT_FILENAME)
+ else:
+ raise JetFileException('No output filename in configuration file')
+ if config.filename == '' or config.filename == None:
+ config.filename = FileJustRoot(self.config_file) + ".JET"
+ config.chase_controllers = True
+ if config.has_option(OUTPUT_SECTION, OUTPUT_CHASE_CONTROLLERS):
+ try:
+ config.chase_controllers = config.getboolean(OUTPUT_SECTION, OUTPUT_CHASE_CONTROLLERS)
+ except:
+ pass
+
+ config.delete_empty_tracks = False
+ if config.has_option(OUTPUT_SECTION, OUTPUT_OMIT_EMPTY_TRACKS):
+ try:
+ config.delete_empty_tracks = config.getboolean(OUTPUT_SECTION, OUTPUT_OMIT_EMPTY_TRACKS)
+ except:
+ pass
+
+ config.copyright = None
+ if config.has_option(OUTPUT_SECTION, OUTPUT_COPYRIGHT):
+ config.copyright = config.get(OUTPUT_SECTION, OUTPUT_COPYRIGHT)
+
+ config.app_data = None
+ if config.has_option(OUTPUT_SECTION, OUTPUT_APP_DATA):
+ config.app_data = config.get(OUTPUT_SECTION, OUTPUT_APP_DATA)
+
+ # count segments
+ segments = []
+ seg_num = 0
+ while 1:
+
+ # check for segment section
+ segment_name = SEGMENT_SECTION + str(seg_num)
+ if not config.has_section(segment_name):
+ break
+
+ # initialize some parameters
+ start = end = length = output = end_marker = dlsfile = dump_file = None
+ transpose = repeat = mute_flags = 0
+ jetevents = []
+
+ # get the segment parameters
+ segname = config.get(segment_name, SEGMENT_NAME)
+ filename = config.get(segment_name, SEGMENT_FILENAME)
+ if config.has_option(segment_name, SEGMENT_DLSFILE):
+ dlsfile = config.get(segment_name, SEGMENT_DLSFILE)
+ if config.has_option(segment_name, SEGMENT_START):
+ start = config.get(segment_name, SEGMENT_START)
+ if config.has_option(segment_name, SEGMENT_END):
+ end = config.get(segment_name, SEGMENT_END)
+ if config.has_option(segment_name, SEGMENT_LENGTH):
+ length = config.get(segment_name, SEGMENT_LENGTH)
+ if config.has_option(segment_name, SEGMENT_OUTPUT):
+ output = config.get(segment_name, SEGMENT_OUTPUT)
+ if config.has_option(segment_name, SEGMENT_QUANTIZE):
+ quantize = config.get(segment_name, SEGMENT_QUANTIZE)
+ if config.has_option(segment_name, SEGMENT_DUMP_FILE):
+ dump_file = config.get(segment_name, SEGMENT_DUMP_FILE)
+ #API FIELDS
+ if config.has_option(segment_name, SEGMENT_TRANSPOSE):
+ transpose = config.get(segment_name, SEGMENT_TRANSPOSE)
+ if config.has_option(segment_name, SEGMENT_REPEAT):
+ repeat = config.get(segment_name, SEGMENT_REPEAT)
+ if config.has_option(segment_name, SEGMENT_MUTE_FLAGS):
+ mute_flags = config.get(segment_name, SEGMENT_MUTE_FLAGS)
+
+ if config.has_option(segment_name, SEGMENT_END_MARKER):
+ end_marker = config.get(segment_name, SEGMENT_END_MARKER)
+ track_num, channel_num, event_time = end_marker.split(',',2)
+ #jetevents.append((JetDefs.E_EOS, 0, int(track_num), int(channel_num), event_time, ''))
+ jetevents.append(JetEvent(JetDefs.E_EOS, JetDefs.E_EOS, 0, int(track_num), int(channel_num), event_time, event_time))
+
+ # check for jetevents
+ for jetevent, location in config.items(segment_name):
+ if jetevent.startswith(CLIP_PREFIX):
+ event_name, event_id, track_num, channel_num, event_start, event_end = location.split(',', 5)
+ jetevents.append(JetEvent(event_name, JetDefs.E_CLIP, int(event_id), int(track_num), int(channel_num), event_start, event_end))
+
+ # check for appevents
+ for jetevent, location in config.items(segment_name):
+ if jetevent.startswith(APP_PREFIX):
+ event_name, event_id, track_num, channel_num, event_start, event_end = location.split(',', 5)
+ jetevents.append(JetEvent(event_name, JetDefs.E_APP, int(event_id), int(track_num), int(channel_num), event_start, event_end))
+
+ segments.append(JetSegment(segname, filename, start, end, length, output, quantize, jetevents, dlsfile, dump_file, int(transpose), int(repeat), int(mute_flags)))
+ seg_num += 1
+
+ self.segments = segments
+ if not len(segments):
+ #TODO: Check for segments when writing
+ #raise JetFileException('No segments defined in configuration file')
+ pass
+
+ # count libraries
+ libraries = []
+ lib_num = 0
+ while 1:
+ library_name = LIBRARY_FILENAME + str(lib_num)
+ if not config.has_option(LIBRARY_SECTION, library_name):
+ break
+ libraries.append(config.get(LIBRARY_SECTION, library_name))
+ lib_num += 1
+ self.libraries = libraries
+
+ def WriteJetFileFromConfig (self, options):
+ """Write JET file from config file."""
+
+ # open the output file and write the header
+ output_file = open(self.config.filename, 'wb')
+ jet_header = struct.pack(JET_HEADER_STRUCT, JET_HEADER_TAG, 0)
+ output_file.write(jet_header)
+
+ # write the JINF chunk
+ jet_info = struct.pack(JINF_STRUCT,
+ JET_INFO_CHUNK, struct.calcsize(JINF_STRUCT) - 8,
+ JINF_JET_VERSION, JET_VERSION,
+ JINF_NUM_SMF_CHUNKS, len(self.segments),
+ JINF_NUM_DLS_CHUNKS, len(self.libraries))
+ output_file.write(jet_info)
+
+ # write the JCOP chunk (if any)
+ if self.config.copyright is not None:
+ size = len(self.config.copyright) + 1
+ if size & 1:
+ size += 1
+ extra_byte = True
+ else:
+ extra_byte = False
+ jet_copyright = struct.pack(JCOP_STRUCT, JCOP_CHUNK, size)
+ output_file.write(jet_copyright)
+ output_file.write(self.config.copyright)
+ output_file.write(chr(0))
+ if extra_byte:
+ output_file.write(chr(0))
+
+ # write the app data chunk (if any)
+ if self.config.app_data is not None:
+ size = os.path.getsize(self.config.app_data)
+ if size & 1:
+ size += 1
+ extra_byte = True
+ else:
+ extra_byte = False
+ jet_app_data = struct.pack(JAPP_STRUCT, JAPP_CHUNK, size)
+ output_file.write(jet_app_data)
+ with open(self.config.app_data, 'rb') as f:
+ output_file.write(f.read())
+ if extra_byte:
+ output_file.write(chr(0))
+
+ # copy the MIDI segments
+ seg_num = 0
+ for segment in self.segments:
+ logging.debug('Writing segment %d' % seg_num)
+
+ # open SMF file and read it
+ jet_segfile = JetSegmentFile(segment.filename, 'rb')
+ jet_segfile.ReadFromStream()
+
+ # insert events
+ jet_segfile.AddJetEvents(segment.jetevents)
+
+ # trim to length specified in config file
+ jet_segfile.ExtractEvents(segment.start, segment.end, segment.length, segment.quantize, self.config.chase_controllers)
+
+ # chase controller events and fix them
+ if self.config.chase_controllers:
+ jet_segfile.SyncClips()
+
+ # delete empty tracks
+ if self.config.delete_empty_tracks:
+ jet_segfile.DeleteEmptyTracks()
+
+ # write separate output file if requested
+ if segment.output is not None:
+ jet_segfile.SaveAs(segment.output)
+
+ # write dump file
+ if segment.dump_file is not None:
+ with open(segment.dump_file, 'w') as f:
+ jet_segfile.DumpTracks(f)
+
+ # write the segment header
+ header_pos = output_file.tell()
+ smf_header = struct.pack(JET_HEADER_STRUCT, JET_SMF_CHUNK, 0)
+ output_file.write(smf_header)
+ start_pos = output_file.tell()
+
+ # write SMF file to output file
+ jet_segfile.Write(output_file, offset=start_pos)
+ jet_segfile.close()
+
+ # return to segment header and write actual size
+ end_pos = output_file.tell()
+ file_size = end_pos - start_pos
+ if file_size & 1:
+ file_size += 1
+ end_pos += 1
+ output_file.seek(header_pos, 0)
+ smf_header = struct.pack(JET_HEADER_STRUCT, JET_SMF_CHUNK, file_size)
+ output_file.write(smf_header)
+ output_file.seek(end_pos, 0)
+
+ seg_num += 1
+
+ # copy the DLS segments
+ for library in self.libraries:
+ if FileExists(library):
+ # open SMF file and get size
+ lib_file = (open(library,'rb'))
+ lib_file.seek(0,2)
+ file_size = lib_file.tell()
+ lib_file.seek(0)
+
+ # write the library header
+ dls_header = struct.pack(JET_HEADER_STRUCT, JET_DLS_CHUNK, file_size)
+ output_file.write(dls_header)
+
+ # copy DLS file to output file
+ output_file.write(lib_file.read())
+ lib_file.close()
+
+ # write the header with the read data size
+ file_size = output_file.tell()
+ output_file.seek(0)
+ jet_header = struct.pack(JET_HEADER_STRUCT, JET_HEADER_TAG, file_size - struct.calcsize(JET_HEADER_STRUCT))
+ output_file.write(jet_header)
+ output_file.close()
+
+ def GetMidiFiles(self):
+ """ Gets a list of midifiles """
+ midiFiles = []
+ for segment in self.segments:
+ if segment.filename not in midiFiles:
+ midiFiles.append(segment.filename)
+ return midiFiles
+
+ def GetLibraries(self):
+ """ Gets the libraries """
+ return self.libraries
+
+ def GetEvents(self, segName):
+ """ Gets the events for a segment """
+ for segment in self.segments:
+ if segment.segname == segName:
+ return segment.jetevents
+ return None
+
+ def GetEvent(self, segName, eventName):
+ """ Gets a single event from a segment """
+ for segment in self.segments:
+ if segment.segname == segName:
+ for event in segment.jetevents:
+ if event.event_name == eventName:
+ return event
+ return None
+
+ def AddEvent(self, segname, event_name, event_type, event_id, track_num, channel_num, event_start, event_end):
+ """ Adds an event """
+ for segment in self.segments:
+ if segment.segname == segname:
+ segment.jetevents.append(JetEvent(event_name, event_type, int(event_id), int(track_num), int(channel_num), event_start, event_end))
+
+ def ReplaceEvents(self, segname, newEvents):
+ """ Replaces all events """
+ for segment in self.segments:
+ if segment.segname == segname:
+ segment.jetevents = newEvents
+ return segment
+
+ def UpdateEvent(self, segname, orgeventname, event_name, event_type, event_id, track_num, channel_num, event_start, event_end):
+ """ Updates an event """
+ for segment in self.segments:
+ if segment.segname == segname:
+ for jetevent in segment.jetevents:
+ if jetevent.event_name == orgeventname:
+ jetevent.event_name = event_name
+ jetevent.event_type = event_type
+ jetevent.event_id = event_id
+ jetevent.track_num = track_num
+ jetevent.channel_num = channel_num
+ jetevent.event_start = event_start
+ jetevent.event_end = event_end
+
+ def DeleteSegmentsMatchingPrefix(self, prefix):
+ """ Deletes all segments matching name """
+ iOnce = True
+ iAgain = False
+ while(iOnce or iAgain):
+ iOnce = False
+ iAgain = False
+ for segment in self.segments:
+ if segment.segname[0:len(prefix)].upper() == prefix.upper():
+ self.segments.remove(segment)
+ iAgain = True
+
+ def DeleteEvent(self, segname, event_name):
+ """ Deletes an event """
+ for segment in self.segments:
+ if segment.segname == segname:
+ for jetevent in segment.jetevents:
+ if jetevent.event_name == event_name:
+ segment.jetevents.remove(jetevent)
+
+ def DeleteEventsMatchingPrefix(self, segname, prefix):
+ """ Deletes all events matching name """
+ for segment in self.segments:
+ if segment.segname == segname:
+ iOnce = True
+ iAgain = False
+ while(iOnce or iAgain):
+ iOnce = False
+ iAgain = False
+ for jetevent in segment.jetevents:
+ if jetevent.event_name[0:len(prefix)].upper() == prefix.upper():
+ segment.jetevents.remove(jetevent)
+ iAgain = True
+
+ def MoveEvent(self, segname, movename, event_start, event_end):
+ """ Move an event """
+ for segment in self.segments:
+ if segment.segname == segname:
+ for jetevent in segment.jetevents:
+ if jetevent.event_name == movename:
+ jetevent.event_start = event_start
+ jetevent.event_end = event_end
+ return
+
+ def GetSegments(self):
+ """ Gets all segments """
+ return self.segments
+
+ def GetSegment(self, segName):
+ """ Gets one segment by name """
+ for segment in self.segments:
+ if segment.segname == segName:
+ return segment
+ return None
+
+ def AddSegment(self, segname, filename, start, end, length, output, quantize, jetevents, dlsfile, dump_file, transpose, repeat, mute_flags):
+ """ Adds a segment """
+ if length == JetDefs.MBT_ZEROSTR:
+ length = None
+ if end == JetDefs.MBT_ZEROSTR:
+ end = None
+ self.segments.append(JetSegment(segname, filename, start, end, length, output, quantize, jetevents, dlsfile, dump_file, transpose, repeat, mute_flags))
+
+ def UpdateSegment(self, orgsegname, segname, filename, start, end, length, output, quantize, jetevents, dlsfile, dump_file, transpose, repeat, mute_flags):
+ """ Updates a segment """
+ if length == JetDefs.MBT_ZEROSTR:
+ length = None
+ if end == JetDefs.MBT_ZEROSTR:
+ end = None
+ for segment in self.segments:
+ if segment.segname == orgsegname:
+ segment.segname = segname
+ segment.filename = filename
+ segment.start = start
+ segment.end = end
+ segment.length = length
+ segment.output = output
+ segment.quantize = quantize
+ segment.dlsfile = dlsfile
+ segment.transpose = transpose
+ segment.repeat = repeat
+ segment.mute_flags = mute_flags
+
+ def MoveSegment(self, segname, start, end):
+ """ Moves a segment """
+ for segment in self.segments:
+ if segment.segname == segname:
+ segment.start = start
+ segment.end = end
+ return
+
+ def DeleteSegment(self, segname):
+ """ Deletes a segment """
+ for segment in self.segments:
+ if segment.segname == segname:
+ self.segments.remove(segment)
+
+ def SaveJetConfig(self, configFile):
+ """ Saves the jet config file """
+ if self.config.filename == '' or self.config.filename == None:
+ self.config.filename = FileJustRoot(configFile) + ".JET"
+ config = ConfigParser.ConfigParser()
+ config.add_section(OUTPUT_SECTION)
+ config.set(OUTPUT_SECTION, OUTPUT_FILENAME, self.config.filename)
+ config.set(OUTPUT_SECTION, OUTPUT_CHASE_CONTROLLERS, self.config.chase_controllers)
+ config.set(OUTPUT_SECTION, OUTPUT_OMIT_EMPTY_TRACKS, self.config.delete_empty_tracks)
+ if self.config.copyright is not None:
+ config.set(OUTPUT_SECTION, OUTPUT_COPYRIGHT, self.config.copyright)
+ if self.config.app_data is not None:
+ config.set(OUTPUT_SECTION, OUTPUT_APP_DATA, self.config.app_data)
+
+ self.libraries = []
+ seg_num = 0
+ for segment in self.segments:
+ segment_name = SEGMENT_SECTION + str(seg_num)
+ config.add_section(segment_name)
+ config.set(segment_name, SEGMENT_NAME, segment.segname)
+ config.set(segment_name, SEGMENT_FILENAME, segment.filename)
+
+ config.set(segment_name, SEGMENT_DLSFILE, segment.dlsfile)
+ if FileExists(segment.dlsfile):
+ if not segment.dlsfile in self.libraries:
+ self.libraries.append(segment.dlsfile)
+ config.set(segment_name, SEGMENT_START, segment.start)
+ if segment.end > JetDefs.MBT_ZEROSTR and len(segment.end) > 0:
+ config.set(segment_name, SEGMENT_END, segment.end)
+ if segment.length > JetDefs.MBT_ZEROSTR and len(segment.length) > 0:
+ config.set(segment_name, SEGMENT_LENGTH, segment.length)
+ config.set(segment_name, SEGMENT_OUTPUT, segment.output)
+ config.set(segment_name, SEGMENT_QUANTIZE, segment.quantize)
+ if segment.dump_file is not None:
+ config.set(segment_name, SEGMENT_DUMP_FILE, segment.dump_file)
+ config.set(segment_name, SEGMENT_TRANSPOSE, segment.transpose)
+ config.set(segment_name, SEGMENT_REPEAT, segment.repeat)
+ config.set(segment_name, SEGMENT_MUTE_FLAGS, segment.mute_flags)
+
+ clip_num = 0
+ app_num = 0
+ for jet_event in segment.jetevents:
+ if jet_event.event_type == JetDefs.E_CLIP:
+ clip_name = CLIP_PREFIX + str(clip_num)
+ s = "%s,%s,%s,%s,%s,%s" % (jet_event.event_name, jet_event.event_id, jet_event.track_num, jet_event.channel_num, jet_event.event_start, jet_event.event_end)
+ config.set(segment_name, clip_name, s)
+ clip_num += 1
+ elif jet_event.event_type == JetDefs.E_APP:
+ app_name = APP_PREFIX + str(app_num)
+ s = "%s,%s,%s,%s,%s,%s" % (jet_event.event_name, jet_event.event_id, jet_event.track_num, jet_event.channel_num, jet_event.event_start, jet_event.event_end)
+ config.set(segment_name, app_name, s)
+ app_num += 1
+ elif jet_event.event_type == JetDefs.E_EOS:
+ s = "%s,%s,%s" % (jet_event.track_num, jet_event.channel_num, jet_event.event_start)
+ config.set(segment_name, SEGMENT_END_MARKER, s)
+
+ seg_num += 1
+
+ lib_num = 0
+ config.add_section(LIBRARY_SECTION)
+ for library in self.libraries:
+ library_name = LIBRARY_FILENAME + str(lib_num)
+ config.set(LIBRARY_SECTION, library_name, library)
+ lib_num += 1
+
+ FileKillClean(configFile)
+ cfgfile = open(configFile,'w')
+ config.write(cfgfile)
+ cfgfile.close()
+
+ def InitializeConfig(self, configFile):
+ """ Initializes the values for an empty flag """
+ self.config.filename = FileJustRoot(configFile) + ".JET"
+ self.config.chase_controllers = True
+ self.config.delete_empty_tracks = False
+ self.config.copyright = None
+ self.config.app_data = None
+ self.segments = []
+ self.libraries = []
+ self.config_file = configFile
+ self.SaveJetConfig(configFile)
+
+
+
+#---------------------------------------------------------------
+# main
+#---------------------------------------------------------------
+if __name__ == '__main__':
+ sys = __import__('sys')
+ optparse = __import__('optparse')
+
+ # parse command line options
+ parser = optparse.OptionParser(version=VERSION)
+ parser.set_defaults(log_level=logging.INFO, log_file=None)
+ parser.add_option('-d', '--debug', action="store_const", const=logging.DEBUG, dest='log_level', help='Enable debug output')
+ parser.add_option('-l', '--log_file', dest='log_file', help='Write debug output to log file')
+ (options, args) = parser.parse_args()
+
+ # get master logger
+ logger = logging.getLogger('')
+ logger.setLevel(options.log_level)
+
+ # create console logger
+ console_logger = logging.StreamHandler()
+ console_logger.setFormatter(logging.Formatter('%(message)s'))
+ logger.addHandler(console_logger)
+
+ # create rotating file logger
+ if options.log_file is not None:
+ file_logger = logging.FileHandler(options.log_file, 'w')
+ file_logger.setFormatter(logging.Formatter('%(message)s'))
+ logger.addHandler(file_logger)
+
+ # process files
+ for arg in args:
+ print arg
+ jet_file = JetFile(arg, options)
+ jet_file.WriteJetFileFromConfig(options)
+
diff --git a/jet_tools/JetCreator/JetHelp.py b/jet_tools/JetCreator/JetHelp.py
new file mode 100755
index 0000000..1606a2a
--- /dev/null
+++ b/jet_tools/JetCreator/JetHelp.py
@@ -0,0 +1,33 @@
+"""
+ File:
+ JetHelp.py
+
+ Contents and purpose:
+ Displays the help text
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import wx
+import wx.html
+from JetDefs import *
+
+app = wx.PySimpleApp()
+frame = wx.Frame(None, -1, JetDefs.MAIN_HELPTITLE, size=(800,600))
+html1 = wx.html.HtmlWindow(frame, -1)
+html1.LoadPage(JetDefs.MAIN_HELPFILE)
+frame.Center()
+frame.Show()
+app.MainLoop()
diff --git a/jet_tools/JetCreator/JetPreview.py b/jet_tools/JetCreator/JetPreview.py
new file mode 100755
index 0000000..34c16d3
--- /dev/null
+++ b/jet_tools/JetCreator/JetPreview.py
@@ -0,0 +1,200 @@
+"""
+ File:
+ JetPreview.py
+
+ Contents and purpose:
+ Plays the preview of a segment or event via the dialog box
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from __future__ import with_statement
+
+import wx
+import threading
+
+from JetDefs import *
+from JetCtrls import *
+from JetFile import *
+from JetUtils import *
+from eas import *
+from JetStatusEvent import *
+
+class PreviewPlayer(wx.Frame):
+ """ Segment player """
+ def __init__ (self, play_button, segment):
+ self.segment = segment
+ self.play_button = play_button
+ self.mute_button = None
+ self.trigger_button = None
+ self.playerLock = threading.RLock()
+ self.SetKeepPlayingFlag(False)
+ self.graph = None
+
+ def SetGraphCtrl(self, graph, parentWin):
+ """ Sets the graph control for the player """
+ self.graph = graph
+ self.parentWin = parentWin
+
+ def SetGraphCallbackFct(self, ClickCallbackFct):
+ """ Sets the callback function for the graph control to update """
+ self.ClickCallbackFct = ClickCallbackFct
+
+ def GraphTriggerClip(self, sClipName, iEventId):
+ """ Triggers a clip by clicking on it """
+ with self.playerLock:
+ try:
+ self.jet.TriggerClip(iEventId)
+ return True
+ except:
+ return False
+
+ def SetMuteFlag(self, trackNum, mute):
+ """ Sets a mute flag """
+ sync = JetDefs.DEFAULT_MUTE_SYNC
+ with self.playerLock:
+ try:
+ self.jet.SetMuteFlag(trackNum, mute, sync)
+ logging.info("SetMuteFlag() Track:%d Mute:%d Sync:%d" % (trackNum, mute, sync))
+ return True
+ except:
+ return False
+
+ def TriggerClip(self, eventID):
+ """ Triggers a clip via function """
+ with self.playerLock:
+ try:
+ self.jet.TriggerClip(eventID)
+ logging.info("TriggerClip() eventID: %d" % eventID)
+ return True
+ except:
+ return False
+
+ def MuteTrackViaButton(self, button, trackNum):
+ """ Mutes a track via a button """
+ with self.playerLock:
+ self.mute_button = button
+ if button.GetLabel() == JetDefs.BUT_MUTE:
+ if self.SetMuteFlag(trackNum, True):
+ button.SetLabel(JetDefs.BUT_UNMUTE)
+ else:
+ if self.SetMuteFlag(trackNum, False):
+ button.SetLabel(JetDefs.BUT_MUTE)
+
+ def Start(self):
+ """ Starts the playback. Called as a thread from dialog boxes """
+ self.paused = False
+
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_PLAY, None))
+
+ # create a temporary config file, and jet output file
+ FileKillClean(JetDefs.TEMP_JET_CONFIG_FILE)
+
+ self.jet_file = JetFile(JetDefs.TEMP_JET_CONFIG_FILE, "")
+
+ self.jet_file.AddSegment(self.segment.segname,
+ self.segment.filename,
+ self.segment.start,
+ self.segment.end,
+ self.segment.length,
+ SegmentOutputFile(self.segment.segname, JetDefs.TEMP_JET_CONFIG_FILE),
+ self.segment.quantize,
+ self.segment.jetevents,
+ self.segment.dlsfile,
+ None,
+ self.segment.transpose,
+ self.segment.repeat,
+ self.segment.mute_flags)
+ userID = 0
+ dls_num = -1
+ seg_num = 0
+
+ if len(self.segment.dlsfile) > 0:
+ self.jet_file.libraries.append(self.segment.dlsfile)
+ dls_num = 0
+
+ self.jet_file.SaveJetConfig(JetDefs.TEMP_JET_CONFIG_FILE)
+ self.jet_file.WriteJetFileFromConfig(JetDefs.TEMP_JET_CONFIG_FILE)
+
+ if not ValidateConfig(self.jet_file):
+ return
+
+ self.queueSegs = []
+ self.queueSegs.append(QueueSeg(self.segment.segname, userID, seg_num, dls_num, self.segment.repeat, self.segment.transpose, self.segment.mute_flags))
+
+ self.jet = JET()
+ self.jet.eas.StartWave()
+ self.jet.OpenFile(self.jet_file.config.filename)
+
+ # queue first segment and start playback
+ index = 0
+ Queue(self.jet, self.queueSegs[index])
+
+ index += 1
+ self.jet.Play()
+
+ self.SetKeepPlayingFlag(True)
+ while self.GetKeepPlayingFlag():
+ self.jet.Render()
+ status = self.jet.Status()
+
+ # if no more segments - we're done
+ if status.numQueuedSegments == 0:
+ break
+
+ self.jet.GetAppEvent()
+
+ # if less than 2 segs queued - queue another one
+ if (index < len(self.queueSegs)) and (status.numQueuedSegments < 2):
+ Queue(self.jet, self.queueSegs[index])
+ index += 1
+
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_UPD_LOCATION, status.location))
+
+ SafeJetShutdown(self.playerLock, self.jet)
+
+ FileKillClean(SegmentOutputFile(self.segment.segname, JetDefs.TEMP_JET_CONFIG_FILE))
+ FileKillClean(JetDefs.TEMP_JET_CONFIG_FILE)
+ FileKillClean(self.jet_file.config.filename)
+
+ self.SetKeepPlayingFlag(False)
+
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_DONE, None))
+
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_UPD_LOCATION, 0))
+
+ def SetKeepPlayingFlag(self, val):
+ """ Sets the flag to tell us wheter to keep playing """
+ with self.playerLock:
+ self.keepPlaying = val
+
+ def GetKeepPlayingFlag(self):
+ """ Gets the keep playing flag """
+ with self.playerLock:
+ return self.keepPlaying
+
+ def Pause(self):
+ """ Pauses playback """
+ if self.jet is None:
+ return
+ if not self.paused:
+ self.jet.Pause()
+ self.paused = True
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_PAUSE, None))
+ else:
+ self.jet.Play()
+ self.paused = False
+ wx.PostEvent(self.parentWin, JetStatusEvent(JetDefs.PST_RESUME, None))
+
diff --git a/jet_tools/JetCreator/JetSegGraph.py b/jet_tools/JetCreator/JetSegGraph.py
new file mode 100755
index 0000000..04d4ca3
--- /dev/null
+++ b/jet_tools/JetCreator/JetSegGraph.py
@@ -0,0 +1,361 @@
+"""
+ File:
+ JetSegGraph.py
+
+ Contents and purpose:
+ Draws the event graph and progress bar
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+
+import wx
+import logging
+
+from JetUtils import *
+from JetDefs import *
+
+GRAPH_COLORS = [
+ '#C0E272',
+ '#85CF89',
+ '#CF9683',
+ '#749EDE',
+ '#9FB5B1',
+ '#B095BF',
+ '#FE546D',
+ '#B3BB97',
+ '#FFFFB8',
+
+ ]
+
+PROGRESS_BAR = '#0000CC'
+EOS_BAR = '#095000'
+APP_BAR = '#B3BB97'
+
+
+class Marker():
+ """ Defines portions of the graph for events """
+ def __init__(self, sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn):
+ self.sEventType = sEventType
+ self.iEventId = iEventId
+ self.sName = sName
+ self.StartMbt = ConvertStrTimeToTuple(sStartMbt)
+ self.EndMbt = ConvertStrTimeToTuple(sEndMbt)
+ self.iStartMeasure = iStartMeasure
+ self.iStart = 0
+ self.iEnd = 0
+ self.iWidth = 0
+ self.iHeight = 0
+ self.iTop = 0
+ self.iUpdate = False
+ self.sColor = '#FFFFB8'
+ self.ppqn = ppqn
+ self.isDirty = False
+
+ def CalcCoord(self, step, height, ColorFct):
+ """ Calculates the coordinates in pixels for graphing the shaded regions """
+ #measures
+ iStartM = self.StartMbt[0] - self.iStartMeasure
+ iEndM = self.EndMbt[0] - self.iStartMeasure
+ self.iStart = step * iStartM
+ self.iEnd = step * iEndM
+ #beats
+ self.iStart = self.iStart + ((step / 4.0) * (self.StartMbt[1]-1))
+ self.iEnd = self.iEnd + ((step / 4.0) * (self.EndMbt[1]-1))
+ #ticks
+ pctTickOfBeat = (float(self.StartMbt[2]) / float(self.ppqn))
+ self.iStart = self.iStart + ((pctTickOfBeat * (step / 4.0)))
+ pctTickOfBeat = (float(self.EndMbt[2]) / float(self.ppqn))
+ self.iEnd = self.iEnd + ((pctTickOfBeat * (step / 4.0)))
+
+ self.iWidth = self.iEnd - self.iStart
+
+ self.iHeight = height
+ self.sColor = ColorFct()
+ self.iUpdate = False
+
+class SegmentGraph(wx.Panel):
+ """ Draws the player graph bar """
+ def __init__(self, parent, pos=wx.DefaultPosition, size=wx.DefaultSize, ClickCallbackFct=None, showLabels=True, showClips=True, showAppEvts=True):
+ wx.Panel.__init__(self, parent, -1, pos=pos, size=size, style=wx.BORDER_STATIC)
+ self.iLocationInMs = 0
+ self.iLengthInMs = 0
+ self.iLengthInMeasures = 0
+ self.iMarkerTop = 15
+ self.iScaleTop = 0
+ self.iEdges = 5
+ self.iStartMeasure = 0
+ self.iMidiMode = False
+ self.ClickCallbackFct = ClickCallbackFct
+ self.iColor = 0
+ self.showLabels = showLabels
+ self.showClips = showClips
+ self.showAppEvts = showAppEvts
+
+ self.font = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, 'Courier')
+
+ self.Markers = []
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+ self.Bind(wx.EVT_SIZE, self.OnSize)
+ self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+
+ #initialize buffer
+ self.OnSize(None)
+
+ def ClearGraph(self):
+ """ Clears the graph values """
+ self.iLocationInMs = 0
+ self.iLengthInMs = 0
+ self.iLengthInMeasures = 0
+ self.iMarkerTop = 15
+ self.iScaleTop = 0
+ self.iEdges = 5
+ self.iStartMeasure = 0
+ self.iMidiMode = False
+ self.iColor = 0
+ self.Markers = []
+ self.iLocationInMs = 0
+ self.DoDrawing()
+
+ def LoadSegment(self, segment, segMarker=None, iMidiMode=False, showLabels=True, showClips=True, showAppEvts=True):
+ """ Loads up the segment drawing the graph """
+ if segment is None:
+ self.ClearGraph()
+ return None
+ self.iMidiMode = iMidiMode
+ self.showLabels = showLabels
+ self.showClips = showClips
+ self.showAppEvts = showAppEvts
+ self.Markers = []
+ self.iLocationInMs = 0
+ info = MidiSegInfo(segment)
+ #disable graph for debugging
+ #return info
+ self.iLengthInMs = info.iLengthInMs
+ self.ppqn = info.ppqn
+ self.StartMbt = mbtFct(ConvertStrTimeToTuple(segment.start), 1)
+ self.EndMbt = mbtFct(ConvertStrTimeToTuple(segment.end), 1)
+ self.LengthMbt = None
+ self.iStartMeasure = self.StartMbt[0]
+ self.iLengthInMeasures = self.EndMbt[0] - self.StartMbt[0]
+
+ for jet_event in segment.jetevents:
+ if self.showClips and jet_event.event_type == JetDefs.E_CLIP:
+ self.AddMarker(JetDefs.E_CLIP, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_start,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
+ elif jet_event.event_type == JetDefs.E_EOS:
+ self.AddMarker(JetDefs.E_EOS, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_end,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
+ elif self.showAppEvts and jet_event.event_type == JetDefs.E_APP:
+ self.AddMarker(JetDefs.E_APP, jet_event.event_id, jet_event.event_name, mbtFct(jet_event.event_start,1), mbtFct(jet_event.event_end,1), self.iStartMeasure, self.ppqn)
+
+ if segMarker is not None:
+ self.AddMarker(JetDefs.E_CLIP, 0, segMarker[0], mbtFct(segMarker[1],1), mbtFct(segMarker[2],1), self.iStartMeasure, self.ppqn)
+
+ self.DoDrawing()
+ return info
+
+ def AddMarker(self, sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn):
+ """ Adds a marker to the list """
+ if not CompareMbt(sStartMbt, sEndMbt):
+ sEndMbt = sStartMbt
+ self.Markers.append(Marker(sEventType, iEventId, sName, sStartMbt, sEndMbt, iStartMeasure, ppqn))
+
+ def OnLeftDown(self, event):
+ """ Calls the function assicated with an event """
+ pt = event.GetPosition()
+ for Marker in self.Markers:
+ if pt[0] >= Marker.iStart and pt[0] <= Marker.iEnd and pt[1] >= Marker.iTop and pt[1] <= Marker.iTop + Marker.iHeight:
+ if self.ClickCallbackFct != None:
+ self.ClickCallbackFct(Marker.sName, Marker.iEventId)
+
+ def GetAColor(self):
+ """ Gets a color """
+ color = GRAPH_COLORS[self.iColor]
+ self.iColor = self.iColor + 1
+ if self.iColor >= len(GRAPH_COLORS):
+ self.iColor = 0
+ return color
+
+ def OnSize(self, event=None):
+ """ Repaints for resizing of screen """
+ if OsWindows():
+ # The Buffer init is done here, to make sure the buffer is always
+ # the same size as the Window
+ Size = self.GetClientSizeTuple()
+
+ # Make new offscreen bitmap: this bitmap will always have the
+ # current drawing in it, so it can be used to save the image to
+ # a file, or whatever.
+ self._Buffer = wx.EmptyBitmap(*Size)
+ self.DoDrawing(None)
+ if event is not None:
+ event.Skip()
+
+ def OnPaint(self, event=None):
+ """ Painting of windows """
+ if OsWindows():
+ dc = wx.BufferedPaintDC(self, self._Buffer)
+ else:
+ dc = wx.AutoBufferedPaintDC(self)
+ dc.Background = wx.Brush(wx.WHITE)
+ self.DoDrawing(dc)
+
+ def DoDrawing(self, dc=None):
+ """ Does the actual drawing of the control """
+ if dc is None:
+ if OsWindows():
+ dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+ else:
+ dc = wx.AutoBufferedPaintDC(self)
+ dc.Background = wx.Brush(wx.WHITE)
+
+ dc.Clear()
+
+ self.iColor = 0
+ gWidth, gHeight = self.GetSize()
+ gWidth = gWidth - (self.iEdges * 2)
+ step = int(gWidth / (self.iLengthInMeasures + .01))
+
+ for Marker in self.Markers:
+ Marker.CalcCoord(step, gHeight, self.GetAColor)
+
+ """ eliminate overlaps; establish colors """
+ iClips = 0
+ iMarkers = 0
+ for index, Marker in enumerate(self.Markers):
+ if Marker.sEventType == JetDefs.E_CLIP:
+ iClips = iClips + 1
+ iOverlaps = 1
+ for index1, Marker1 in enumerate(self.Markers):
+ if Marker.sEventType == JetDefs.E_CLIP:
+ if index != index1 and not Marker1.iUpdate:
+ if Marker.iStart <= Marker1.iStart and Marker.iEnd <= Marker1.iEnd and Marker.iEnd >= Marker1.iStart:
+ iOverlaps = iOverlaps + 1
+ Marker.iUpdate = True
+ Marker1.iUpdate = True
+ if not Marker.iUpdate and Marker.iStart >= Marker1.iStart and Marker.iEnd >= Marker1.iEnd and Marker.iStart <= Marker1.iEnd:
+ iOverlaps = iOverlaps + 1
+ Marker.iUpdate = True
+ Marker1.iUpdate = True
+ if iOverlaps > 1:
+ iTop = 0
+ for index1, Marker1 in enumerate(self.Markers):
+ if Marker.sEventType == JetDefs.E_CLIP:
+ if Marker1.iUpdate:
+ Marker1.iHeight = gHeight / iOverlaps
+ Marker1.iTop = iTop * Marker1.iHeight
+ iTop = iTop + 1
+ elif Marker.sEventType == JetDefs.E_APP:
+ iMarkers = iMarkers + 1
+
+ for Marker in self.Markers:
+ if Marker.sEventType == JetDefs.E_CLIP:
+ dc.SetPen(wx.Pen(Marker.sColor))
+ dc.SetBrush(wx.Brush(Marker.sColor))
+ dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, Marker.iWidth, Marker.iHeight)
+ width, height = dc.GetTextExtent(Marker.sName)
+ k = ((Marker.iStart + Marker.iEnd) / 2) - (width/2) + self.iEdges
+ if self.showLabels or self.iMidiMode:
+ dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
+ if self.iMidiMode:
+ self.iMidiModeStart = Marker.iStart
+ elif Marker.sEventType == JetDefs.E_EOS:
+ dc.SetPen(wx.Pen(EOS_BAR))
+ dc.SetBrush(wx.Brush(EOS_BAR))
+ dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, 1, Marker.iHeight)
+ width, height = dc.GetTextExtent(Marker.sName)
+ k = Marker.iStart - (width/2) + self.iEdges
+ dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
+ elif Marker.sEventType == JetDefs.E_APP:
+ dc.SetPen(wx.Pen(APP_BAR))
+ dc.SetBrush(wx.Brush(APP_BAR))
+ dc.DrawRectangle(Marker.iStart + self.iEdges, Marker.iTop, 1, Marker.iHeight)
+ width, height = dc.GetTextExtent(Marker.sName)
+ k = Marker.iStart - (width/2) + self.iEdges
+ if self.showLabels or self.iMidiMode:
+ dc.DrawText(Marker.sName, k, ((Marker.iTop+Marker.iHeight/2) - (height*.5)))
+
+
+ """ Draw scale """
+ if gWidth == 0:
+ iDiv = 50
+ else:
+ iDiv = (gWidth)/18
+ if iDiv == 0:
+ iDiv = 50
+ scale = ((self.iLengthInMeasures / iDiv) + 1)
+ if scale == 0:
+ scale = 1
+ beatStep = step / 4.0
+ dc.SetFont(self.font)
+ j = 0
+ lastEnd = 0
+ num = range(self.iStartMeasure, self.iStartMeasure + self.iLengthInMeasures + 1, 1)
+ dc.SetPen(wx.Pen('#5C5142'))
+ for i in range(0, (self.iLengthInMeasures+1)*step, step):
+ k = i + self.iEdges
+ dc.DrawLine(k, self.iScaleTop, k, self.iScaleTop+8)
+ if i != (self.iLengthInMeasures)*step:
+ for iBeat in range(1,4):
+ k = i+(iBeat * beatStep) + self.iEdges
+ dc.DrawLine(k, self.iScaleTop, k, self.iScaleTop+4)
+ width, height = dc.GetTextExtent(str(num[j]))
+ k = i-(width/2) + self.iEdges
+ if k > lastEnd:
+ if j == 0 or (j % scale) == 0:
+ dc.DrawText(str(num[j]), k, self.iScaleTop+8)
+ lastEnd = k + width
+ j = j + 1
+
+ """ Updates the location bar in case screen moved or resized """
+ if self.iLocationInMs > 0 and self.iLengthInMs > 0:
+ iOffset = 0
+ if self.iMidiMode:
+ iOffset = self.iMidiModeStart
+
+ till = gWidth * (self.iLocationInMs / self.iLengthInMs)
+ dc.SetPen(wx.Pen(PROGRESS_BAR))
+ dc.SetBrush(wx.Brush(PROGRESS_BAR))
+ dc.DrawRectangle(self.iEdges + iOffset, gHeight-6, till, 3)
+
+ def UpdateLocation(self, iLocationInMs):
+ """ Updates the location bar """
+ #disable graph for debugging
+ #return info
+
+ self.iLocationInMs = iLocationInMs
+ if self.iLocationInMs > 0 and self.iLengthInMs > 0:
+ if OsWindows():
+ dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+ else:
+ dc = wx.AutoBufferedPaintDC(self)
+ dc.Background = wx.Brush(wx.WHITE)
+
+ iOffset = 0
+ if self.iMidiMode:
+ iOffset = self.iMidiModeStart
+
+ gWidth, gHeight = self.GetSize()
+ gWidth = gWidth - (self.iEdges * 2)
+
+ till = gWidth * (self.iLocationInMs / self.iLengthInMs)
+ dc.SetPen(wx.Pen(PROGRESS_BAR))
+ dc.SetBrush(wx.Brush(PROGRESS_BAR))
+ dc.DrawRectangle(self.iEdges + iOffset, gHeight-6, till, 3)
+ self.isDirty = True
+ else:
+ if self.isDirty:
+ self.DoDrawing()
+ self.isDirty = False
diff --git a/jet_tools/JetCreator/JetStatusEvent.py b/jet_tools/JetCreator/JetStatusEvent.py
new file mode 100755
index 0000000..64b4b50
--- /dev/null
+++ b/jet_tools/JetCreator/JetStatusEvent.py
@@ -0,0 +1,38 @@
+"""
+ File:
+ JetStatusEvent.py
+
+ Contents and purpose:
+ Creates an event for postevent callbacks
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import wx
+
+EVT_JET_STATUS_ID = wx.NewId()
+
+def EVT_JET_STATUS(win, func):
+ win.Connect(-1, -1, EVT_JET_STATUS_ID, func)
+
+class JetStatusEvent(wx.PyEvent):
+ """Used for posting events out of play thread back to UI"""
+ def __init__(self, mode, data):
+ wx.PyEvent.__init__(self)
+ self.SetEventType(EVT_JET_STATUS_ID)
+ self.mode = mode
+ self.data = data
+
+
diff --git a/jet_tools/JetCreator/JetSystemInfo.py b/jet_tools/JetCreator/JetSystemInfo.py
new file mode 100755
index 0000000..a9ac932
--- /dev/null
+++ b/jet_tools/JetCreator/JetSystemInfo.py
@@ -0,0 +1,40 @@
+"""
+ File:
+ JetSystemInfo.py
+
+ Contents and purpose:
+ Displays the system info regarding os version, wxPython version, and Python version
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import sys
+import os
+import string
+import wx
+
+print("")
+print("wxPython Version:")
+print(wx.__version__)
+
+print("")
+print("Python Version:")
+print(sys.version)
+
+print("")
+print("Python Paths:")
+for dir in string.split(os.environ['PYTHONPATH'], os.pathsep):
+ print dir
+
diff --git a/jet_tools/JetCreator/JetUtils.py b/jet_tools/JetCreator/JetUtils.py
new file mode 100755
index 0000000..d81e34d
--- /dev/null
+++ b/jet_tools/JetCreator/JetUtils.py
@@ -0,0 +1,788 @@
+"""
+ File:
+ JetUtils.py
+
+ Contents and purpose:
+ Utilities used throughout JetCreator
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from __future__ import with_statement
+
+import wx
+import os
+import copy
+import ConfigParser
+import logging
+import time
+import tempfile
+
+from JetDefs import *
+from JetDebug import *
+from midifile import TimeBase, trackGrid
+
+class JetCutCopy(object):
+ """ Handles cut/copy/pasting of events and segments """
+ def __init__ (self, objType, objSave, currentSegmentName):
+ self.objType = objType
+ self.objSave = copy.deepcopy(objSave)
+ self.currentSegmentName = currentSegmentName
+
+ def GetObj(self, objList):
+ """ Gets an object """
+ objSave = copy.deepcopy(self.objSave)
+ if self.objType == JetDefs.MAIN_SEGLIST:
+ oldName = objSave.segname
+ i = len(oldName) - 1
+ while i > 0:
+ if not oldName[i].isdigit():
+ break
+ i = i - 1
+ oldName = oldName[0:i+1]
+ i = 1
+ while True:
+ newName = oldName + str(i)
+ if self.UniqueSegName(newName, objList):
+ break
+ i = i + 1
+ objSave.segname = newName
+ elif self.objType == JetDefs.MAIN_EVENTLIST:
+ oldName = objSave.event_name
+ i = len(oldName) - 1
+ while i > 0:
+ if not oldName[i].isdigit():
+ break
+ i = i - 1
+ oldName = oldName[0:i+1]
+ i = 1
+ while True:
+ newName = oldName + str(i)
+ if self.UniqueEventName(newName, objList):
+ break
+ i = i + 1
+ objSave.event_name = newName
+ return objSave
+
+ def UniqueSegName(self, nameVal, seglist):
+ for nm in seglist:
+ if nm.segname == nameVal:
+ return False
+ return True
+
+ def UniqueEventName(self, nameVal, eventlist):
+ for nm in eventlist:
+ if nm.event_name == nameVal:
+ return False
+ return True
+
+
+class JetState(object):
+ """ Saves the state for cut/copy/paste """
+ def __init__ (self, jet_file, currentSegmentIndex, currentEventIndex):
+ self.jet_file = copy.deepcopy(jet_file)
+ self.currentSegmentIndex = currentSegmentIndex
+ self.currentEventIndex = currentEventIndex
+
+def Queue (jet, queueSeg):
+ """ Queues a segment """
+ jet.QueueSegment(queueSeg.userID, queueSeg.seg_num, queueSeg.dls_num, queueSeg.repeat, queueSeg.transpose, queueSeg.mute_flags)
+
+class QueueSeg(object):
+ """ Object representing a segment """
+ def __init__ (self, name, userID, seg_num, dls_num=-1, repeat=0, transpose=0, mute_flags=0, status=''):
+ self.name = name
+ self.userID = userID
+ self.seg_num = seg_num
+ self.dls_num = dls_num
+ self.repeat = repeat
+ self.transpose = transpose
+ self.mute_flags = mute_flags
+ self.status = status
+ #DumpQueueSeg(self)
+
+def FindDlsNum(libraries, dlsfile):
+ """ Looks for a dls file in the library list """
+ for index, library in enumerate(libraries):
+ if library == dlsfile:
+ return index
+ return -1
+
+def SetRowSelection(list, row, state):
+ """ Sets the selection status of a list row """
+ if state:
+ list.SetItemState(row, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
+ else:
+ list.SetItemState(row, ~wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
+
+def ClearRowSelections(list):
+ """ Clears the list rows selection status """
+ index = list.GetFirstSelected()
+ while index != -1:
+ SetRowSelection(list, index, False)
+ index = list.GetNextSelected(index)
+
+def getColumnText(list, index, col):
+ """ Sets the text of a column """
+ item = list.GetItem(index, col)
+ return item.GetText()
+
+def getColumnValue(list, index, col):
+ """ Gets the text of a column """
+ item = list.GetItem(index, col)
+ v = str(item.GetText())
+ if len(v) > 0:
+ return int(item.GetText())
+ else:
+ return 0
+
+def StrNoneChk(fld):
+ """ Returns a blank string if none """
+ if fld is None:
+ return ""
+ return str(fld)
+
+def ConvertStrTimeToTuple(s):
+ """ Converts a string time to a tuple """
+ try:
+ measures, beats, ticks = s.split(':',3)
+ return (int(measures), int(beats), int(ticks))
+ except:
+ return JetDefs.MBT_DEFAULT
+
+def FileRelativePath(target, base=os.curdir):
+ """ Returns relative file path """
+ if not os.path.exists(target):
+ return target
+
+ if not os.path.isdir(base):
+ return target
+
+ base_list = (os.path.abspath(base)).split(os.sep)
+ target_list = (os.path.abspath(target)).split(os.sep)
+ if os.name in ['nt','dos','os2'] and base_list[0] <> target_list[0]:
+ return target
+ for i in range(min(len(base_list), len(target_list))):
+ if base_list[i] <> target_list[i]: break
+ else:
+ i+=1
+ rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
+ return os.path.join(*rel_list)
+
+def FileFixPath(fileSpec):
+ """ Tweaks slashes """
+ return fileSpec.replace("\\", "/")
+
+def FileKillClean(fileName):
+ """ Deletes a file skipping errors """
+ try:
+ os.remove(fileName)
+ except:
+ pass
+
+def FileJustRoot(fileName):
+ """ Gets just the root of the file name """
+ try:
+ return os.path.splitext(fileName)[0]
+ except:
+ return ""
+
+def FileJustName(fileName):
+ """ Gets just the filename, without the path """
+ try:
+ return os.path.split(fileName)[1]
+ except:
+ return ""
+
+def FileJustPath(fileName):
+ """ Gets just the path, without the file name """
+ try:
+ return os.path.split(fileName)[0]
+ except:
+ return ""
+
+def FileJustExt(fileName):
+ """ Gets just the extension of the file """
+ try:
+ ext = os.path.splitext(fileName)[1]
+ return ext.upper()
+ except:
+ return ""
+
+def FileDateTime(fileName):
+ """ Gets the date/time of a file """
+ try:
+ filetime = time.ctime(os.path.getmtime(fileName))
+ return filetime
+ except:
+ return ""
+
+def FileExists(fileName):
+ """ Checks if a file exists """
+ try:
+ return os.path.exists(fileName)
+ except:
+ return False
+
+def IniSetValue(configFile, section, option, value):
+ """ Sets the value of a config file field """
+ config = ConfigParser.ConfigParser()
+ config.read(configFile)
+ if not config.has_section(section):
+ config.add_section(section)
+ config.set(section, option, value)
+ cfgfile = open(configFile,'w')
+ config.write(cfgfile)
+ cfgfile.close()
+
+def IniGetValue(configFile, section, option, retType='str', default=''):
+ """ Gets the value of a config file field """
+ ret = default
+ config = ConfigParser.ConfigParser()
+ config.read(configFile)
+ if config.has_section(section):
+ if config.has_option(section, option):
+ ret = config.get(section, option)
+ if retType =='int':
+ try:
+ ret = int(ret)
+ except:
+ ret = 0
+ elif retType == 'float':
+ try:
+ ret = float(ret)
+ except:
+ ret = 0
+ elif retType == 'bool':
+ try:
+ if ret[0].upper()=='T':
+ ret = True
+ else:
+ ret = False
+ except:
+ ret = False
+ elif retType == 'list':
+ try:
+ ret = eval(ret)
+ except:
+ ret = []
+ return ret
+
+def GetRecentJetFiles():
+ """ Builds a list of recent jet files """
+ fileList = []
+ config = ConfigParser.ConfigParser()
+ config.read(JetDefs.JETCREATOR_INI)
+ if config.has_section(JetDefs.RECENT_SECTION):
+ for count in range(0, 10):
+ sFile = "File" + str(count)
+ if config.has_option(JetDefs.RECENT_SECTION, sFile):
+ sFileName = config.get(JetDefs.RECENT_SECTION, sFile)
+ if FileExists(sFileName):
+ if sFileName != JetDefs.UNTITLED_FILE:
+ #fileList.append(FileRelativePath(config.get(JetDefs.RECENT_SECTION, sFile)))
+ fileList.append(config.get(JetDefs.RECENT_SECTION, sFile))
+ return fileList
+
+def AppendRecentJetFile(jetFile):
+ """ Appends to a list of recent jet files """
+ addedFiles = []
+ fileList = GetRecentJetFiles()
+ config = ConfigParser.ConfigParser()
+ config.read(JetDefs.JETCREATOR_INI)
+ if config.has_section(JetDefs.RECENT_SECTION):
+ config.remove_section(JetDefs.RECENT_SECTION)
+ config.add_section(JetDefs.RECENT_SECTION)
+ config.set(JetDefs.RECENT_SECTION, "File0", jetFile)
+ addedFiles.append(jetFile)
+ count = 1
+ for file in fileList:
+ if file not in addedFiles:
+ sFile = "File" + str(count)
+ config.set(JetDefs.RECENT_SECTION, sFile, file)
+ addedFiles.append(file)
+ count += 1
+ FileKillClean(JetDefs.JETCREATOR_INI)
+ cfgfile = open(JetDefs.JETCREATOR_INI,'w')
+ config.write(cfgfile)
+ cfgfile.close()
+
+def CompareMbt(mbt1, mbt2):
+ """ Compates to measure/beat/tick values """
+ try:
+ m1, b1, t1 = mbt1.split(':',3)
+ m2, b2, t2 = mbt2.split(':',3)
+ if int(m1) > int(m2):
+ return False
+ elif int(m1) == int(m2) and int(b1) > int(b2):
+ return False
+ elif int(b1) == int(b2) and int(t1) > int(t2):
+ return False
+ elif int(m1) == int(m2) and int(b1) == int(b2) and int(t1) == int(t2):
+ return False
+ else:
+ return True
+ except:
+ return False
+
+def MbtVal(mbt):
+ """ Converts mbts to ticks """
+ if type(mbt).__name__=='str' or type(mbt).__name__=='unicode':
+ mbt1 = mbt
+ else:
+ mbt1 = "%d:%d:%d" % mbt
+ try:
+ return TimeBase().ConvertStrTimeToTicks(mbt1)
+ except:
+ return 0
+
+def TicksToMbt(ticks):
+ """ Converts ticks to mbts """
+ return TimeBase().ConvertTicksToMBT(ticks)
+
+def TicksToStrMbt(ticks):
+ """ Converts ticks to mbts """
+ return TimeBase().ConvertTicksToStr(ticks, '%02d:%02d:%02d')
+
+def MbtDifference(mbt1, mbt2):
+ """ Returns difference between mbt values """
+ return TimeBase().MbtDifference(mbt1, mbt2)
+
+def PlayMidiFile(midiFile, dlsFile=''):
+ """ Plays a midi file """
+ try:
+ e = __import__('eas')
+
+ if midiFile == '':
+ return
+ eas = e.EAS()
+ if dlsFile > '':
+ eas.LoadDLSCollection(dlsFile)
+ eas.StartWave()
+ audio_file = eas.OpenFile(midiFile)
+ audio_file.Prepare()
+ audio_file.Play()
+ audio_file.Close()
+ eas.StopWave()
+ eas.Shutdown()
+ except:
+ return
+
+def SegmentOutputFile(segName, configFile):
+ """ Computes a segment output file """
+ configPath = FileJustPath(configFile) + "/"
+ segOutput = configPath + "Seg_" + segName + ".mid"
+ return segOutput
+
+def ComputeMuteFlags(jet_file, segName):
+ """ Computes mute flags """
+ muteFlag = 0
+ for jet_event in jet_file.GetEvents(segName):
+ muteFlag = SetMute(jet_event.track_num, muteFlag)
+ return muteFlag
+
+def ComputeMuteFlagsFromList1(list):
+ """ Computes mute flags from a list """
+ muteFlag = 0
+ num = list.GetItemCount()
+ for iRow in range(num):
+ track_num = list.GetTrackNumber(iRow)
+ if list.IsChecked(iRow):
+ muteFlag = SetMute(track_num, muteFlag)
+ else:
+ muteFlag = ClearMute(track_num, muteFlag)
+ return muteFlag
+
+def ComputeMuteFlagsFromList(list):
+ """ Computes mute flags from a list """
+ muteFlags = 0
+ num = list.GetItemCount()
+ for iRow in range(num):
+ track_num = list.GetTrackNumber(iRow)
+ if list.IsChecked(iRow):
+ muteFlags = SetMute(track_num, muteFlags)
+ return muteFlags
+
+
+def SetMuteFlag(track, muteFlag, mute):
+ """ Sets a mute flag """
+ if mute:
+ SetMute(track, muteFlag)
+ else:
+ ClearMute(track, muteFlag)
+
+def SetMute(track, muteFlag):
+ """ Sets a mute flag """
+ try:
+ muteFlag |= 1 << (track)
+ return muteFlag
+ except:
+ #bad argument
+ return muteFlag
+
+def ClearMute(track, muteFlag):
+ """ Clears a mute flag """
+ try:
+ muteFlag &= ~(1 << (track))
+ return muteFlag;
+ except:
+ #bad argument
+ return muteFlag
+
+def GetMute(track, muteFlag):
+ """ Get a mute flag """
+ try:
+ if (muteFlag & ( 1 << (track))) == 0:
+ return False
+ else:
+ return True
+ except:
+ #bad argument
+ return False
+
+def InfoMsg(msgTitle, msgText):
+ """ Display a simple informational message """
+ dlg = wx.MessageDialog(None,
+ message=msgText,
+ caption=msgTitle,
+ style=wx.OK|wx.ICON_INFORMATION
+ )
+ dlg.ShowModal()
+ dlg.Destroy()
+
+def SendEvent (mycontrol, evt):
+ """ Sends an event """
+ cmd = wx.CommandEvent(evt)
+ cmd.SetEventObject(mycontrol)
+ cmd.SetId(mycontrol.GetId())
+ mycontrol.GetEventHandler().ProcessEvent(cmd)
+
+def GetJetHelpText(dlgName, fld):
+ """ Gets the jet help text file """
+ return IniGetValue(JetDefs.JETCREATOR_HLP, dlgName, fld)
+
+def ExportJetArchive(fileName, jetConfigFile, jetFile):
+ """ Exports all files into a zip archive file """
+ z = __import__('zipfile')
+ zip = z.ZipFile(fileName, 'w')
+
+ #zip the original .JET file
+ if FileExists(jetFile.config.filename):
+ zip.write(jetFile.config.filename, FileJustName(jetFile.config.filename))
+
+ #make copy of object so we can modify it
+ jet_file = copy.deepcopy(jetFile)
+
+ #zip the files, without paths
+ for segment in jet_file.GetSegments():
+ if FileExists(segment.filename):
+ if not FileJustName(segment.filename) in zip.namelist():
+ zip.write(segment.filename, FileJustName(segment.filename))
+ if FileExists(segment.output):
+ if not FileJustName(segment.output) in zip.namelist():
+ zip.write(segment.output, FileJustName(segment.output))
+
+ #zip the library files
+ for library in jet_file.GetLibraries():
+ if FileExists(library):
+ if not FileJustName(library) in zip.namelist():
+ zip.write(library, FileJustName(library))
+
+ #remove the paths on filenames
+ for segment in jet_file.GetSegments():
+ segment.filename = FileJustName(segment.filename)
+ segment.dlsfile = FileJustName(segment.dlsfile)
+ segment.output = FileJustName(segment.output)
+
+ #remove paths
+ for index, library in enumerate(jet_file.libraries):
+ jet_file.libraries[index] = FileJustName(library)
+
+ #create temporary .JTC file so we can modify paths to files
+ tmpConfigFile = JetDefs.TEMP_JET_CONFIG_FILE
+ FileKillClean(tmpConfigFile)
+
+ #save the file
+ jet_file.SaveJetConfig(tmpConfigFile)
+
+ #zip it and rename it back to original name without path
+ zip.write(tmpConfigFile, FileJustName(jetConfigFile))
+
+ #create a flag file so we know this is a jet archive
+ zip.write(tmpConfigFile, "JetArchive")
+
+ zip.close()
+
+ FileKillClean(tmpConfigFile)
+
+def ValidateConfig(test_jet_file):
+ """ Validates the contents of a config file """
+ dImp = __import__('JetDialogs')
+ errors = []
+ fatalError = False
+ for segment in test_jet_file.segments:
+ logging.debug(segment.filename)
+ if segment.filename is not None and len(segment.filename) > 0 and not FileExists(segment.filename):
+ errors.append(("Segment MIDI file not found", segment.filename))
+ fatalError = True
+ if segment.dlsfile is not None and len(segment.dlsfile) > 0 and not FileExists(segment.dlsfile):
+ errors.append(("Segment DLS file not found; removing from config", segment.dlsfile))
+ segment.dlsfile = ""
+
+ logging.debug(test_jet_file.config.filename)
+
+ if len(errors) == 0:
+ return True
+ else:
+ dlg = dImp.JetErrors("Jet Definition File Errors")
+ dlg.SetErrors(errors)
+ result = dlg.ShowModal()
+ dlg.Destroy()
+ if fatalError:
+ return False
+ else:
+ return True
+
+def release_getLogger(name):
+ """ passing original handler with debug() method replaced to empty function """
+
+ def dummy(*k, **kw):
+ pass
+
+ global __orig_getLogger
+ log = __orig_getLogger(name)
+ setattr(log, 'debug', dummy)
+ setattr(log, 'info', dummy)
+ setattr(log, 'error', dummy)
+ setattr(log, 'critical', dummy)
+ return log
+
+def install_release_loggers():
+ """ Save original handler, installs newer one """
+ global __orig_getLogger
+ __orig_getLogger = logging.getLogger
+ setattr(logging, 'getLogger', release_getLogger)
+
+def restore_getLogger():
+ """ Restores original handler """
+ global __orig_getLogger
+ if __orig_getLogger:
+ setattr(logging, 'getLogger', __orig_getLogger)
+
+def GetMidiFileLength(midiFile):
+ """ Gets the length of a midi file via eas """
+ e = __import__('eas')
+
+ if not FileExists(midiFile):
+ return 0
+
+ eas = e.EAS()
+ audio_file = eas.OpenFile(midiFile)
+ audio_file.Prepare()
+ midiLength = eas.audio_streams[0].ParseMetaData()
+ audio_file.Close()
+ eas.Shutdown()
+ return midiLength
+
+def GetMidiInfo(midiFile):
+ """ Gets midi file info """
+ m = __import__('midifile')
+ md = m.GetMidiInfo(midiFile)
+ return md
+
+def PrintMidiInfo(midiFile):
+ """ Prints info about a midi file """
+ mi = GetMidiInfo(midiFile)
+ if mi.err == 0:
+ print("ppqn: " + str(mi.ppqn))
+ print("beats_per_measure: " + str(mi.beats_per_measure))
+ print("ending mbt: " + str(mi.endMbt))
+ print("ending mbt str: " + mi.endMbtStr)
+ print("maxMeasures: " + str(mi.maxMeasures))
+ print("maxBeats: " + str(mi.maxBeats))
+ print("maxTicks: " + str(mi.maxTicks))
+ print("maxTracks: " + str(mi.maxTracks))
+ print("totalTicks: " + str(mi.totalTicks))
+ for track in mi.trackList:
+ print(track)
+ else:
+ print("Error opening")
+
+def MidiSegInfo(segment):
+ """ Midi file info saved in config file for speed """
+ class segInfo:
+ iMsPerTick = 0
+ bpm = 4
+ ppqn = 480
+ total_ticks = 0
+ iLengthInMs = 0
+ iTracks = 0
+ trackList = []
+
+ ver = "1.5"
+ ret = segInfo()
+ savedVer = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "Ver")
+ savedDateTime = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "DateTime")
+ dateTime = FileDateTime(segment.filename)
+ if ver != savedVer or dateTime != savedDateTime:
+ mi = GetMidiInfo(segment.filename)
+ if mi.err == 0:
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "Ver", ver)
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "DateTime", str(dateTime))
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "PPQN", str(mi.ppqn))
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "BPM", str(mi.beats_per_measure))
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "totalTicks", str(mi.totalTicks))
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "maxTracks", str(mi.maxTracks))
+ iLengthInMs = GetMidiFileLength(segment.filename) * 1000
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "LengthInMs", str(iLengthInMs))
+ if iLengthInMs > 0:
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "MsPerTick", str(iLengthInMs / mi.totalTicks))
+ #have to write out the tracklist in format that can be saved in INI file
+ tl = []
+ for track in mi.trackList:
+ tl.append((track.track, track.channel, track.name))
+ IniSetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "Tracks", tl)
+
+ trackList = []
+ tl = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "Tracks", 'list', [])
+ for t in tl:
+ trackList.append(trackGrid(t[0], t[1], t[2],False))
+ iTracks = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "maxTracks", 'int', 0)
+ iMsPerTick = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "MsPerTick", 'float', 0)
+ bpm = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "BPM", 'int', 0)
+ ppqn = IniGetValue(JetDefs.JETMIDIFILES_INI, segment.filename, "PPQN", 'int', 480)
+ if iMsPerTick == 0 or bpm == 0 or ppqn == 0:
+ return ret
+ tb = TimeBase(ppqn, bpm)
+ total_ticks = tb.ConvertStrTimeToTicks(segment.length)
+ if total_ticks == 0:
+ total_ticks = tb.MbtDifference(tb.ConvertStrTimeToTuple(segment.start), tb.ConvertStrTimeToTuple(segment.end))
+ if total_ticks == 0:
+ return ret
+
+ ret.iTracks = iTracks
+ ret.iMsPerTick = iMsPerTick
+ ret.bpm = bpm
+ ret.ppqn = ppqn
+ ret.total_ticks = total_ticks
+ ret.iLengthInMs = total_ticks * iMsPerTick
+ ret.trackList = trackList
+ return ret
+
+def TimeStr(ms):
+ """ Returns a time string """
+ s=ms/1000
+ m,s=divmod(s,60)
+ h,m=divmod(m,60)
+ d,h=divmod(h,24)
+ if m > 0:
+ return "%d Min %d Sec" % (m,s)
+ else:
+ return "%d Seconds" % (s)
+
+def mbtFct(mbt, mod):
+ """ Converts times """
+ if type(mbt).__name__=='str' or type(mbt).__name__=='unicode':
+ mbt = ConvertStrTimeToTuple(mbt)
+ retType = 'str'
+ else:
+ retType = 'int'
+
+ m = mbt[0]+mod
+ b = mbt[1]+mod
+ t = mbt[2]
+ if m < 0:
+ m = 0
+ if b < 0:
+ b = 0
+ if b > 4:
+ b = 4
+ if t < 0:
+ t = 0
+
+ if retType == 'str':
+ return "%d:%d:%d" % (m, b, t)
+ else:
+ return (m, b, t)
+
+def OsWindows():
+ """ Tells us whether windows or os x """
+ if os.name == 'nt':
+ return True ;
+ else:
+ return False ;
+
+def MacOffset():
+ """ Mac screen coordinates funky on some controls so we finagle a few pixels """
+ if not OsWindows():
+ return 3
+ else:
+ return 0
+
+def SafeJetShutdown(lock, jet):
+ """ Makes sure we do the jet shutdown properly """
+ with lock:
+ #MAKE SURE WE CLEANUP
+ #try: jet.Clear_Queue()
+ #except: pass
+
+ try: jet.eas.StopWave()
+ except: pass
+
+ try: jet.Shutdown()
+ except: pass
+
+ jet = None
+
+
+def CreateTempJetFile(org_jet_file):
+ """ Creates temporary jet file for playback testing """
+ dirname = JetDefs.TEMP_JET_DIR
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+
+ tmpConfigFile = dirname + FileJustName(org_jet_file.config_file)
+ FileKillClean(tmpConfigFile)
+
+ jet_file = copy.deepcopy(org_jet_file)
+
+ for tmp in jet_file.segments:
+ tmp.output = dirname + FileJustName(tmp.output)
+
+ jet_file.config_file = tmpConfigFile
+ jet_file.config.filename = dirname + FileJustName(jet_file.config.filename)
+ FileKillClean(jet_file.config.filename)
+
+ jet_file.SaveJetConfig(tmpConfigFile)
+ jet_file.WriteJetFileFromConfig(tmpConfigFile)
+
+ return jet_file
+
+def CleanupTempJetFile(jet_file):
+ """ Cleans up temporary files """
+ FileKillClean(jet_file.config.filename)
+ FileKillClean(jet_file.config_file)
+ for tmp in jet_file.segments:
+ FileKillClean(tmp.output)
+
+def GetNow():
+ return time.asctime()
+
+
+if __name__ == '__main__':
+ """ Tests functions """
+ pass
+
+ \ No newline at end of file
diff --git a/jet_tools/JetCreator/ReadMe1st.txt b/jet_tools/JetCreator/ReadMe1st.txt
new file mode 100755
index 0000000..121ae32
--- /dev/null
+++ b/jet_tools/JetCreator/ReadMe1st.txt
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+PREREQUISITES
+
+JetCreator was created and tested with:
+
+Python Version 2.5.4
+wxPython Version 2.8.7.1
+
+These can be downloaded here:
+
+PC
+
+http://www.python.org/download/releases/2.5.4/
+http://www.wxpython.org/download.php
+
+MAC
+
+http://wiki.python.org/moin/MacPython/Leopard
+http://www.wxpython.org/download.php
+
+
+
+INSTALLING
+
+1. Install Python
+2. Install wxPython
+3. Unzip or copy the JetCreator directory
+4. Execute JetCreator.py
+5. Use Import to import simple example project off Demos directory
diff --git a/jet_tools/JetCreator/eas.py b/jet_tools/JetCreator/eas.py
new file mode 100755
index 0000000..127314b
--- /dev/null
+++ b/jet_tools/JetCreator/eas.py
@@ -0,0 +1,1229 @@
+
+from __future__ import with_statement
+
+import threading
+import logging
+import time
+from ctypes import *
+from JetUtils import OsWindows
+
+
+# stream state
+EAS_STATE_READY = 0
+EAS_STATE_PLAY = 1
+EAS_STATE_STOPPING = 2
+EAS_STATE_PAUSING = 3
+EAS_STATE_STOPPED = 4
+EAS_STATE_PAUSED = 5
+EAS_STATE_OPEN = 6
+EAS_STATE_ERROR = 7
+EAS_STATE_EMPTY = 8
+
+# EAS error codes
+EAS_SUCCESS = 0
+EAS_FAILURE = -1
+EAS_ERROR_INVALID_MODULE = -2
+EAS_ERROR_MALLOC_FAILED = -3
+EAS_ERROR_FILE_POS = -4
+EAS_ERROR_INVALID_FILE_MODE = -5
+EAS_ERROR_FILE_SEEK = -6
+EAS_ERROR_FILE_LENGTH = -7
+EAS_ERROR_NOT_IMPLEMENTED = -8
+EAS_ERROR_CLOSE_FAILED = -9
+EAS_ERROR_FILE_OPEN_FAILED = -10
+EAS_ERROR_INVALID_HANDLE = -11
+EAS_ERROR_NO_MIX_BUFFER = -12
+EAS_ERROR_PARAMETER_RANGE = -13
+EAS_ERROR_MAX_FILES_OPEN = -14
+EAS_ERROR_UNRECOGNIZED_FORMAT = -15
+EAS_BUFFER_SIZE_MISMATCH = -16
+EAS_ERROR_FILE_FORMAT = -17
+EAS_ERROR_SMF_NOT_INITIALIZED = -18
+EAS_ERROR_LOCATE_BEYOND_END = -19
+EAS_ERROR_INVALID_PCM_TYPE = -20
+EAS_ERROR_MAX_PCM_STREAMS = -21
+EAS_ERROR_NO_VOICE_ALLOCATED = -22
+EAS_ERROR_INVALID_CHANNEL = -23
+EAS_ERROR_ALREADY_STOPPED = -24
+EAS_ERROR_FILE_READ_FAILED = -25
+EAS_ERROR_HANDLE_INTEGRITY = -26
+EAS_ERROR_MAX_STREAMS_OPEN = -27
+EAS_ERROR_INVALID_PARAMETER = -28
+EAS_ERROR_FEATURE_NOT_AVAILABLE = -29
+EAS_ERROR_SOUND_LIBRARY = -30
+EAS_ERROR_NOT_VALID_IN_THIS_STATE = -31
+EAS_ERROR_NO_VIRTUAL_SYNTHESIZER = -32
+EAS_ERROR_FILE_ALREADY_OPEN = -33
+EAS_ERROR_FILE_ALREADY_CLOSED = -34
+EAS_ERROR_INCOMPATIBLE_VERSION = -35
+EAS_ERROR_QUEUE_IS_FULL = -36
+EAS_ERROR_QUEUE_IS_EMPTY = -37
+EAS_ERROR_FEATURE_ALREADY_ACTIVE = -38
+
+# special result codes
+EAS_EOF = 3
+EAS_STREAM_BUFFERING = 4
+
+# buffer full error returned from Render
+EAS_BUFFER_FULL = 5
+
+# file types
+file_types = (
+ 'Unknown',
+ 'SMF Type 0 (.mid)',
+ 'SMF Type 1 (.mid)',
+ 'SMAF - Unknown type (.mmf)',
+ 'SMAF MA-2 (.mmf)',
+ 'SMAF MA-3 (.mmf)',
+ 'SMAF MA-5 (.mmf)',
+ 'CMX/QualComm (.pmd)',
+ 'MFi (NTT/DoCoMo i-mode)',
+ 'OTA/Nokia (.ott)',
+ 'iMelody (.imy)',
+ 'RTX/RTTTL (.rtx)',
+ 'XMF Type 0 (.xmf)',
+ 'XMF Type 1 (.xmf)',
+ 'WAVE/PCM (.wav)',
+ 'WAVE/IMA-ADPCM (.wav)',
+ 'MMAPI Tone Control (.js)'
+)
+
+stream_states = (
+ 'Ready',
+ 'Play',
+ 'Stopping',
+ 'Stopped',
+ 'Pausing',
+ 'Paused',
+ 'Open',
+ 'Error',
+ 'Empty'
+)
+
+# iMode play modes
+IMODE_PLAY_ALL = 0
+IMODE_PLAY_PARTIAL = 1
+
+# callback type for metadata
+EAS_METADATA_CBFUNC = CFUNCTYPE(c_int, c_int, c_char_p, c_ulong)
+
+# callbacks for external audio
+EAS_EXT_PRG_CHG_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p)
+EAS_EXT_EVENT_FUNC = CFUNCTYPE(c_int, c_void_p, c_void_p)
+
+# callback for aux mixer decoder
+EAS_DECODER_FUNC = CFUNCTYPE(c_void_p, c_void_p, c_int, c_int)
+
+# DLL path
+if OsWindows():
+ EAS_DLL_PATH = "EASDLL.dll"
+else:
+ EAS_DLL_PATH = "libEASLIb.dylib"
+
+eas_dll = None
+
+# logger
+eas_logger = None
+
+#---------------------------------------------------------------
+# InitEASModule
+#---------------------------------------------------------------
+def InitEASModule (dll_path=None):
+ global eas_dll
+ global eas_logger
+
+
+ # initialize logger
+ if eas_logger is None:
+ eas_logger = logging.getLogger('EAS')
+
+ # initialize path to DLL
+ if dll_path is None:
+ dll_path=EAS_DLL_PATH
+
+ # intialize DLL
+ if eas_dll is None:
+ eas_dll = cdll.LoadLibrary(dll_path)
+
+#---------------------------------------------------------------
+# S_JET_CONFIG
+#---------------------------------------------------------------
+class S_JET_CONFIG (Structure):
+ _fields_ = [('appLowNote', c_ubyte)]
+
+#---------------------------------------------------------------
+# S_EXT_AUDIO_PRG_CHG
+#---------------------------------------------------------------
+class S_EXT_AUDIO_PRG_CHG (Structure):
+ _fields_ = [('bank', c_ushort),
+ ('program', c_ubyte),
+ ('channel', c_ubyte)]
+
+#---------------------------------------------------------------
+# S_EXT_AUDIO_EVENT
+#---------------------------------------------------------------
+class S_EXT_AUDIO_EVENT (Structure):
+ _fields_ = [('channel', c_ubyte),
+ ('note', c_ubyte),
+ ('velocity', c_ubyte),
+ ('noteOn', c_ubyte)]
+
+#---------------------------------------------------------------
+# S_MIDI_CONTROLLERS
+#---------------------------------------------------------------
+class S_MIDI_CONTROLLERS (Structure):
+ _fields_ = [('modWheel', c_ubyte),
+ ('volume', c_ubyte),
+ ('pan', c_ubyte),
+ ('expression', c_ubyte),
+ ('channelPressure', c_ubyte)]
+
+#---------------------------------------------------------------
+# WAVEFORMAT
+#---------------------------------------------------------------
+class WAVEFORMAT (Structure):
+ _fields_ = [('wFormatTag', c_ushort),
+ ('nChannels', c_ushort),
+ ('nSamplesPerSec', c_ulong),
+ ('nAvgBytesPerSec', c_ulong),
+ ('nBlockAlign', c_ushort),
+ ('wBitsPerSample', c_ushort)]
+
+#---------------------------------------------------------------
+# EAS_Exception
+#---------------------------------------------------------------
+class EAS_Exception (Exception):
+ def __init__ (self, result_code, msg, function=None):
+ self.msg = msg
+ self.result_code = result_code
+ self.function = function
+ def __str__ (self):
+ return self.msg
+
+#---------------------------------------------------------------
+# Log callback function
+#---------------------------------------------------------------
+# map EAS severity levels to the Python logging module
+severity_mapping = (logging.CRITICAL, logging.CRITICAL, logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG)
+LOG_FUNC_TYPE = CFUNCTYPE(c_int, c_int, c_char_p)
+def Log (level, msg):
+ eas_logger.log(severity_mapping[level], msg)
+ return level
+LogCallback = LOG_FUNC_TYPE(Log)
+
+#---------------------------------------------------------------
+# EAS_Stream
+#---------------------------------------------------------------
+class EAS_Stream (object):
+ def __init__ (self, handle, eas):
+ eas_logger.debug('EAS_Stream.__init__')
+ self.handle = handle
+ self.eas = eas
+
+ def SetVolume (self, volume):
+ """Set the stream volume"""
+ eas_logger.debug('Call EAS_SetVolume: volume=%d' % volume)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetVolume(self.eas.handle, self.handle, volume)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetVolume error %d on file %s' % (result, self.path), 'EAS_SetVolume')
+
+ def GetVolume (self):
+ """Get the stream volume."""
+ eas_logger.debug('Call EAS_GetVolume')
+ with self.eas.lock:
+ volume = eas_dll.EAS_GetVolume(self.eas.handle, self.handle)
+ if volume < 0:
+ raise EAS_Exception(volume, 'EAS_GetVolume error %d on file %s' % (volume, self.path), 'EAS_GetVolume')
+ eas_logger.debug('EAS_GetVolume: volume=%d' % volume)
+ return volume
+
+ def SetPriority (self, priority):
+ """Set the stream priority"""
+ eas_logger.debug('Call EAS_SetPriority: priority=%d' % priority)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetPriority(self.eas.handle, self.handle, priority)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetPriority error %d on file %s' % (result, self.path), 'EAS_SetPriority')
+
+ def GetPriority (self):
+ """Get the stream priority."""
+ eas_logger.debug('Call EAS_GetPriority')
+ priority = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetPriority(self.eas.handle, self.handle, byref(priority))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetPriority error %d on file %s' % (result, self.path), 'EAS_GetPriority')
+ eas_logger.debug('EAS_GetPriority: priority=%d' % priority.value)
+ return priority.value
+
+ def SetTransposition (self, transposition):
+ """Set the transposition of a stream."""
+ eas_logger.debug('Call EAS_SetTransposition: transposition=%d' % transposition)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetTransposition(self.eas.handle, self.handle, transposition)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetTransposition error %d on file %s' % (result, self.path), 'EAS_SetTransposition')
+
+ def SetPolyphony (self, polyphony):
+ """Set the polyphony of a stream."""
+ eas_logger.debug('Call EAS_SetPolyphony: polyphony=%d' % polyphony)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetPolyphony(self.eas.handle, self.handle, polyphony)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetPolyphony error %d on file %s' % (result, self.path), 'EAS_SetPolyphony')
+
+ def GetPolyphony (self):
+ """Get the polyphony of a stream."""
+ eas_logger.debug('Call EAS_GetPolyphony')
+ polyphony = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetPolyphony(self.eas.handle, self.handle, byref(polyphony))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetPolyphony error %d on file %s' % (result, self.path), 'EAS_GetPolyphony')
+ eas_logger.debug('EAS_SetPolyphony: polyphony=%d' % polyphony.value)
+ return polyphony.value
+
+ def SelectLib (self, test_lib=False):
+ eas_logger.debug('Call EAS_SelectLib: test_lib=%s' % test_lib)
+ with self.eas.lock:
+ result = eas_dll.EAS_SelectLib(self.eas.handle, self.handle, test_lib)
+ if result:
+ raise EAS_Exception(result, 'EAS_SelectLib error %d on file %s' % (result, self.path), 'EAS_SelectLib')
+
+ def LoadDLSCollection (self, path):
+ eas_logger.debug('Call EAS_LoadDLSCollection: lib_path=%d' % path)
+ with self.eas.lock:
+ result = eas_dll.EAS_LoadDLSCollection(self.eas.handle, self.handle, path)
+ if result:
+ raise EAS_Exception(result, 'EAS_LoadDLSCollection error %d on file %s lib %s' % (result, self.path, path), 'EAS_LoadDLSCollection')
+
+ def RegExtAudioCallback (self, user_data, prog_chg_func, event_func):
+ """Register an external audio callback."""
+ eas_logger.debug('Call EAS_RegExtAudioCallback')
+ if prog_chg_func is not None:
+ prog_chg_func = EAS_EXT_PRG_CHG_FUNC(prog_chg_func)
+ else:
+ prog_chg_func = 0
+ if event_func is not None:
+ event_func = EAS_EXT_EVENT_FUNC(event_func)
+ else:
+ event_func = 0
+ with self.eas.lock:
+ result = eas_dll.EAS_RegExtAudioCallback(self.eas.handle, self.handle, user_data, prog_chg_func, event_func)
+ if result:
+ raise EAS_Exception(result, 'EAS_RegExtAudioCallback error %d on file %s' % (result, self.path), 'EAS_RegExtAudioCallback')
+
+ def SetPlayMode (self, play_mode):
+ """Set play mode on a stream."""
+ eas_logger.debug('Call EAS_SetPlayMode: play_mode=%d' % play_mode)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetPlayMode(self.eas.handle, self.handle, play_mode)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetPlayMode error %d on file %s' % (result, self.path), 'EAS_SetPlayMode')
+
+"""
+EAS_PUBLIC EAS_RESULT EAS_GetMIDIControllers (EAS_DATA_HANDLE pEASData, EAS_HANDLE streamHandle, EAS_U8 channel, S_MIDI_CONTROLLERS *pControl);
+"""
+
+#---------------------------------------------------------------
+# EAS_File
+#---------------------------------------------------------------
+class EAS_File (EAS_Stream):
+ def __init__ (self, path, handle, eas):
+ EAS_Stream.__init__(self, handle, eas)
+ eas_logger.debug('EAS_File.__init__')
+ self.path = path
+ self.prepared = False
+
+ def Prepare (self):
+ """Prepare an audio file for playback"""
+ if self.prepared:
+ eas_logger.warning('Prepare already called on file %s' % self.path)
+ else:
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_Prepare for file: %s' % self.path)
+ result = eas_dll.EAS_Prepare(self.eas.handle, self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_Prepare error %d on file %s' % (result, self.path), 'EAS_Prepare')
+ self.prepared = True
+
+ def State (self):
+ """Get stream state."""
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_State for file: %s' % self.path)
+ state = c_long(-1)
+ result = eas_dll.EAS_State(self.eas.handle, self.handle, byref(state))
+ if result:
+ raise EAS_Exception(result, 'EAS_State error %d on file %s' % (result, self.path), 'EAS_State')
+ eas_logger.debug('EAS_State: file=%s, state=%s' % (self.path, stream_states[state.value]))
+ return state.value
+
+ def Close (self):
+ """Close audio file."""
+ if hasattr(self, 'handle'):
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_CloseFile for file: %s' % self.path)
+ result = eas_dll.EAS_CloseFile(self.eas.handle, self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_CloseFile error %d on file %s' % (result, self.path), 'EAS_CloseFile')
+
+ # remove file from the EAS object
+ self.eas.audio_streams.remove(self)
+
+ # clean up references
+ del self.handle
+ del self.eas
+ del self.path
+
+ def Pause (self):
+ """Pause a stream."""
+ eas_logger.debug('Call EAS_Pause')
+ with self.eas.lock:
+ result = eas_dll.EAS_Pause(self.eas.handle, self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_Pause error %d on file %s' % (result, self.path), 'EAS_Pause')
+
+ def Resume (self):
+ """Resume a stream."""
+ eas_logger.debug('Call EAS_Resume')
+ with self.eas.lock:
+ result = eas_dll.EAS_Resume(self.eas.handle, self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_Resume error %d on file %s' % (result, self.path), 'EAS_Resume')
+
+ def Locate (self, secs, offset=False):
+ """Set the playback position of a stream in seconds."""
+ eas_logger.debug('Call EAS_Locate: location=%.3f, relative=%s' % (secs, offset))
+ with self.eas.lock:
+ result = eas_dll.EAS_Locate(self.eas.handle, self.handle, int(secs * 1000 + 0.5), offset)
+ if result:
+ raise EAS_Exception(result, 'EAS_Locate error %d on file %s' % (result, self.path), 'EAS_Locate')
+
+ def GetLocation (self):
+ """Get the stream location in seconds."""
+ eas_logger.debug('Call EAS_GetLocation')
+ msecs = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetLocation(self.eas.handle, self.handle, byref(msecs))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetLocation error %d on file %s' % (result, self.path), 'EAS_GetLocation')
+ msecs = float(msecs.value) / 1000
+ eas_logger.debug('EAS_GetLocation: location=%.3f' % msecs)
+ return msecs
+
+ def GetFileType (self):
+ """Get the file type."""
+ eas_logger.debug('Call EAS_GetFileType')
+ file_type = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetFileType(self.eas.handle, self.handle, byref(file_type))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetFileType error %d on file %s' % (result, self.path), 'EAS_GetFileType')
+ file_type = file_type.value
+ if file_type < len(file_types):
+ file_desc = file_types[file_type]
+ else:
+ file_desc = 'Unrecognized type %d' % file_type
+ eas_logger.debug('EAS_GetFileType: type=%d, desc=%s' % (file_type, file_desc))
+ return (file_type, file_desc)
+
+ def SetRepeat (self, count):
+ """Set the repeat count of a stream."""
+ eas_logger.debug('Call EAS_SetRepeat: count=%d' % count)
+ with self.eas.lock:
+ result = eas_dll.EAS_SetRepeat(self.eas.handle, self.handle, count)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetRepeat error %d on file %s' % (result, self.path), 'EAS_SetRepeat')
+
+ def GetRepeat (self):
+ """Get the repeat count of a stream."""
+ eas_logger.debug('Call EAS_GetRepeat')
+ count = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetRepeat(self.eas.handle, self.handle, byref(count))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetRepeat error %d on file %s' % (result, self.path), 'EAS_GetRepeat')
+ eas_logger.debug('EAS_GetRepeat: count=%d' % count.value)
+ return count.value
+
+ def SetPlaybackRate (self, rate):
+ """Set the playback rate of a stream."""
+ eas_logger.debug('Call EAS_SetPlaybackRate')
+ with self.eas.lock:
+ result = eas_dll.EAS_SetPlaybackRate(self.eas.handle, self.handle, rate)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetPlaybackRate error %d on file %s' % (result, self.path), 'EAS_SetPlaybackRate')
+
+ def ParseMetaData (self):
+ """Parse the metadata in a file."""
+ eas_logger.debug('Call EAS_ParseMetaData')
+ length = c_int(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_ParseMetaData(self.eas.handle, self.handle, byref(length))
+ if result:
+ raise EAS_Exception(result, 'EAS_ParseMetaData error %d on file %s' % (result, self.path), 'EAS_ParseMetaData')
+ return float(length.value) / 1000.0
+
+ def RegisterMetaDataCallback (self, func, buf, buf_size, user_data):
+ """Register a metadata callback."""
+ eas_logger.debug('Call EAS_RegisterMetaDataCallback')
+ with self.eas.lock:
+ if func is not None:
+ callback = EAS_METADATA_CBFUNC(func)
+ else:
+ callback = 0
+ result = eas_dll.EAS_RegisterMetaDataCallback(self.eas.handle, self.handle, callback, buf, buf_size, user_data)
+ if result:
+ raise EAS_Exception(result, 'EAS_RegisterMetaDataCallback error %d on file %s' % (result, self.path), 'EAS_RegisterMetaDataCallback')
+
+ def GetWaveFmtChunk (self):
+ """Get the file type."""
+ eas_logger.debug('Call EAS_GetWaveFmtChunk')
+ wave_fmt_chunk = c_void_p(0)
+ with self.eas.lock:
+ result = eas_dll.EAS_GetWaveFmtChunk(self.eas.handle, self.handle, byref(wave_fmt_chunk))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetWaveFmtChunk error %d on file %s' % (result, self.path), 'EAS_GetWaveFmtChunk')
+ return cast(wave_fmt_chunk, POINTER(WAVEFORMAT)).contents
+
+ def Play (self, max_time=None):
+ """Plays the file to the end or max_time."""
+ eas_logger.debug('EAS_File.Play')
+ if not self.prepared:
+ self.Prepare()
+ if max_time is not None:
+ max_time += self.eas.GetRenderTime()
+ while self.State() not in (EAS_STATE_STOPPED, EAS_STATE_ERROR, EAS_STATE_EMPTY):
+ self.eas.Render()
+ if max_time is not None:
+ if self.eas.GetRenderTime() >= max_time:
+ eas_logger.info('Max render time exceeded - stopping playback')
+ self.Pause()
+ self.eas.Render()
+ break
+
+#---------------------------------------------------------------
+# EAS_MIDIStream
+#---------------------------------------------------------------
+class EAS_MIDIStream (EAS_Stream):
+ def Write(self, data):
+ """Write data to MIDI stream."""
+ with self.eas.lock:
+ result = eas_dll.EAS_WriteMIDIStream(self.eas.handle, self.handle, data, len(data))
+ if result:
+ raise EAS_Exception(result, 'EAS_WriteMIDIStream error %d' % result, 'EAS_WriteMIDIStream')
+
+ def Close (self):
+ """Close MIDI stream."""
+ if hasattr(self, 'handle'):
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_CloseMIDIStream')
+ result = eas_dll.EAS_CloseMIDIStream(self.eas.handle, self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_CloseFile error %d' % result, 'EAS_CloseMIDIStream')
+
+ # remove file from the EAS object
+ self.eas.audio_streams.remove(self)
+
+ # clean up references
+ del self.handle
+ del self.eas
+
+#---------------------------------------------------------------
+# EAS_Config
+#---------------------------------------------------------------
+class EAS_Config (Structure):
+ _fields_ = [('libVersion', c_ulong),
+ ('checkedVersion', c_int),
+ ('maxVoices', c_long),
+ ('numChannels', c_long),
+ ('sampleRate', c_long),
+ ('mixBufferSize', c_long),
+ ('filterEnabled', c_int),
+ ('buildTimeStamp', c_ulong),
+ ('buildGUID', c_char_p)]
+
+#---------------------------------------------------------------
+# EAS
+#---------------------------------------------------------------
+class EAS (object):
+ def __init__ (self, handle=None, dll_path=None, log_file=None):
+ if eas_dll is None:
+ InitEASModule(dll_path)
+ if log_file is not None:
+ eas_logger.addHandler(log_file)
+ eas_logger.debug('EAS.__init__')
+ self.Init(handle)
+
+ def __del__ (self):
+ eas_logger.debug('EAS.__del__')
+ self.Shutdown()
+
+ def Init (self, handle=None):
+ """Initializes the EAS Library."""
+ eas_logger.debug('EAS.Init')
+
+ # if we are already initialized, shutdown first
+ if hasattr(self, 'handle'):
+ eas_logger.debug('EAS.Init called with library already initalized')
+ self.ShutDown()
+
+ # setup the logging function
+ eas_dll.SetLogCallback(LogCallback)
+
+ # create some members
+ self.handle = c_void_p(0)
+ self.audio_streams = []
+ self.output_streams = []
+ self.aux_mixer = None
+
+ # create a sync lock
+ self.lock = threading.RLock()
+ with self.lock:
+ # set log callback
+
+ # get library configuration
+ self.Config()
+
+ # initialize library
+ if handle is None:
+ self.do_shutdown = True
+ eas_logger.debug('Call EAS_Init')
+ result = eas_dll.EAS_Init(byref(self.handle))
+ if result:
+ raise EAS_Exception(result, 'EAS_Init error %d' % result, 'EAS_Init')
+ else:
+ self.do_shutdown = False
+ self.handle = handle
+
+ # allocate audio buffer for rendering
+ AudioBufferType = c_ubyte * (2 * self.config.mixBufferSize * self.config.numChannels)
+ self.audio_buffer = AudioBufferType()
+ self.buf_size = self.config.mixBufferSize
+
+ def Config (self):
+ """Retrieves the EAS library configuration"""
+ if not hasattr(self, 'config'):
+ eas_logger.debug('Call EAS_Config')
+ eas_dll.EAS_Config.restype = POINTER(EAS_Config)
+ self.config = eas_dll.EAS_Config()[0]
+ eas_logger.debug("libVersion=%08x, maxVoices=%d, numChannels=%d, sampleRate = %d, mixBufferSize=%d" %
+ (self.config.libVersion, self.config.maxVoices, self.config.numChannels, self.config.sampleRate, self.config.mixBufferSize))
+
+ def Shutdown (self):
+ """Shuts down the EAS library"""
+ eas_logger.debug('EAS.Shutdown')
+ if hasattr(self, 'handle'):
+ with self.lock:
+ # close audio streams
+ audio_streams = self.audio_streams
+ for f in audio_streams:
+ eas_logger.warning('Stream was not closed before EAS_Shutdown')
+ f.Close()
+
+ # close output streams
+ output_streams = self.output_streams
+ for s in output_streams:
+ s.close()
+
+ # shutdown library
+ if self.do_shutdown:
+ eas_logger.debug('Call EAS_Shutdown')
+ result = eas_dll.EAS_Shutdown(self.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_Shutdown error %d' % result, 'EAS_Shutdown')
+ del self.handle
+
+ def OpenFile (self, path):
+ """Opens an audio file to be played by the EAS library and
+ returns an EAS_File object
+
+ Arguments:
+ path - path to audio file
+
+ Returns:
+ EAS_File
+
+ """
+ with self.lock:
+ eas_logger.debug('Call EAS_OpenFile for file: %s' % path)
+ stream_handle = c_void_p(0)
+ result = eas_dll.EAS_OpenFile(self.handle, path, byref(stream_handle))
+ if result:
+ raise EAS_Exception(result, 'EAS_OpenFile error %d on file %s' % (result, path), 'EAS_OpenFile')
+
+ # create file object and save in list
+ stream = EAS_File(path, stream_handle, self)
+ self.audio_streams.append(stream)
+ return stream
+
+ def OpenMIDIStream (self, stream=None):
+ """Opens a MIDI stream.
+
+ Arguments:
+ stream - open stream object. If None, a new synth
+ is created.
+
+ Returns:
+ EAS_MIDIStream
+
+ """
+ with self.lock:
+ eas_logger.debug('Call EAS_OpenMIDIStream')
+ stream_handle = c_void_p(0)
+ if stream.handle is not None:
+ result = eas_dll.EAS_OpenMIDIStream(self.handle, byref(stream_handle), stream.handle)
+ else:
+ result = eas_dll.EAS_OpenMIDIStream(self.handle, byref(stream_handle), 0)
+ if result:
+ raise EAS_Exception(result, 'EAS_OpenMIDIStream error %d' % result, 'EAS_OpenMIDIStream')
+
+ # create stream object and save in list
+ stream = EAS_MIDIStream(stream_handle, self)
+ self.audio_streams.append(stream)
+ return stream
+
+ def OpenToneControlStream (self, path):
+ """Opens an MMAPI tone control file to be played by the EAS
+ library and returns an EAS_File object
+
+ Arguments:
+ path - path to audio file
+
+ Returns:
+ EAS_File
+
+ """
+ with self.lock:
+ eas_logger.debug('Call EAS_MMAPIToneControl for file: %s' % path)
+ stream_handle = c_void_p(0)
+ result = eas_dll.EAS_MMAPIToneControl(self.handle, path, byref(stream_handle))
+ if result:
+ raise EAS_Exception(result, 'EAS_MMAPIToneControl error %d on file %s' % (result, path), 'EAS_OpenToneControlStream')
+
+ # create file object and save in list
+ stream = EAS_File(path, stream_handle, self)
+ self.audio_streams.append(stream)
+ return stream
+
+ def Attach (self, stream):
+ """Attach a file or output device to the EAS output.
+
+ The stream object must support the following methods as
+ defined in the Python wave module:
+ close()
+ setparams()
+ writeframesraw()
+
+ Arguments:
+ stream - open wave object
+
+ """
+ self.output_streams.append(stream)
+ stream.setparams((self.config.numChannels, 2, self.config.sampleRate, 0, 'NONE', None))
+
+ def Detach (self, stream):
+ """Detach a file or output device from the EAS output. See
+ EAS.Attach for more details. It is the responsibility of
+ the caller to close the wave file or stream.
+
+ Arguments:
+ stream - open and attached wave object
+ """
+ self.output_streams.remove(stream)
+
+ def StartWave (self, dev_num=0, sampleRate=None, maxBufSize=None):
+ """Route the audio output to the indicated wave device. Note
+ that this can cause EASDLL.EAS_RenderWaveOut to return an
+ error code if all the output buffers are full. In this case,
+ the render thread should sleep a bit and try again.
+ Unfortunately, due to the nature of the MMSYSTEM interface,
+ there is no simple way to suspend the render thread.
+
+ """
+ if sampleRate == None:
+ sampleRate = self.config.sampleRate
+ if maxBufSize == None:
+ maxBufSize = self.config.mixBufferSize
+ with self.lock:
+ result = eas_dll.OpenWaveOutDevice(dev_num, sampleRate, maxBufSize)
+ if result:
+ raise EAS_Exception(result, 'OpenWaveOutDevice error %d' % result, 'OpenWaveOutDevice')
+
+ def StopWave (self):
+ """Stop routing audio output to the audio device."""
+ with self.lock:
+ result = eas_dll.CloseWaveOutDevice()
+ if result:
+ raise EAS_Exception(result, 'CloseWaveOutDevice error %d' % result, 'CloseWaveOutDevice')
+
+ def Render (self, count=None, secs=None):
+ """Calls EAS_Render to render audio.
+
+ Arguments
+ count - number of buffers to render
+ secs - number of seconds to render
+
+ If both count and secs are None, render a single buffer.
+
+ """
+
+ # determine number of buffers to render
+ if count is None:
+ if secs is not None:
+ count = int(secs * float(self.config.sampleRate) / float(self.buf_size) + 0.5)
+ else:
+ count = 1
+
+ # render buffers
+ eas_logger.debug('rendering %d buffers' % count)
+ samplesRendered = c_long(0)
+ with self.lock:
+ for c in range(count):
+ # render a buffer of audio
+ eas_logger.debug('rendering buffer')
+ while 1:
+ if self.aux_mixer is None:
+ result = eas_dll.EAS_RenderWaveOut(self.handle, byref(self.audio_buffer), self.buf_size, byref(samplesRendered))
+ else:
+ result = eas_dll.EAS_RenderAuxMixer(self.handle, byref(self.audio_buffer), byref(samplesRendered))
+
+ if result == 0:
+ break;
+ if result == EAS_BUFFER_FULL:
+ time.sleep(0.01)
+ else:
+ raise EAS_Exception(result, 'EAS_Render error %d' % result, 'EAS_Render')
+
+ # output to attached streams
+ for s in self.output_streams:
+ s.writeframesraw(self.audio_buffer)
+
+ def GetRenderTime (self):
+ """Get the render time in seconds."""
+ eas_logger.debug('Call EAS_GetRenderTime')
+ msecs = c_int(0)
+ with self.lock:
+ result = eas_dll.EAS_GetRenderTime(self.handle, byref(msecs))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetRenderTime error %d' % result, 'EAS_GetRenderTime')
+ msecs = float(msecs.value) / 1000
+ eas_logger.debug('EAS_GetRenderTime: time=%.3f' % msecs)
+ return msecs
+
+ def SetVolume (self, volume):
+ """Set the master volume"""
+ eas_logger.debug('Call EAS_SetVolume: volume=%d' % volume)
+ with self.lock:
+ result = eas_dll.EAS_SetVolume(self.handle, 0, volume)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetVolume error %d' % result, 'EAS_SetVolume')
+
+ def GetVolume (self):
+ """Get the stream volume."""
+ eas_logger.debug('Call EAS_GetVolume')
+ volume = c_int(0)
+ with self.lock:
+ result = eas_dll.EAS_GetVolume(self.handle, 0, byref(volume))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetVolume error %d' % result, 'EAS_GetVolume')
+ eas_logger.debug('EAS_GetVolume: volume=%d' % volume.value)
+ return volume.value
+
+ def SetPolyphony (self, polyphony, synth_num=0):
+ """Set the polyphony of a synth."""
+ eas_logger.debug('Call EAS_SetSynthPolyphony: synth_num=%d, polyphony=%d' % (synth_num, polyphony))
+ with self.lock:
+ result = eas_dll.EAS_SetSynthPolyphony(self.handle, synth_num, polyphony)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetSynthPolyphony error %d on synth %d' % (result, synth_num), 'EAS_SetPolyphony')
+
+ def GetPolyphony (self, synth_num=0):
+ """Get the polyphony of a synth."""
+ eas_logger.debug('Call EAS_GetSynthPolyphony: synth_num=%d' % synth_num)
+ polyphony = c_int(0)
+ with self.lock:
+ result = eas_dll.EAS_GetSynthPolyphony(self.handle, synth_num, byref(polyphony))
+ if result:
+ raise EAS_Exception(result, 'EAS_GetSynthPolyphony error %d on synth %d' % (result, synth_num), 'EAS_GetPolyphony')
+ eas_logger.debug('Call EAS_GetSynthPolyphony: synth_num=%d, polyphony=%d' % (synth_num, polyphony.value))
+ return polyphony.value
+
+ def SetMaxLoad (self, max_load):
+ """Set the maximum parser load."""
+ eas_logger.debug('Call EAS_SetMaxLoad: max_load=%d' % max_load)
+ with self.lock:
+ result = eas_dll.EAS_SetMaxLoad(self.handle, max_load)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetMaxLoad error %d' % result, 'EAS_SetMaxLoad')
+
+ def SetParameter (self, module, param, value):
+ """Set a module parameter."""
+ eas_logger.debug('Call EAS_SetParameter: module=%d, param=%d, value=%d' % (module, param, value))
+ with self.lock:
+ result = eas_dll.EAS_SetParameter(self.handle, module, param, value)
+ if result:
+ raise EAS_Exception(result, 'EAS_SetParameter error %d (param=%d, value=%d)' % (result, param, value), 'EAS_SetParameter')
+
+ def GetParameter (self, module, param):
+ """Get the polyphony of a synth."""
+ eas_logger.debug('Call EAS_GetParameter: module=%d, param=%d' % (module, param))
+ value = c_int(0)
+ with self.lock:
+ result = eas_dll.EAS_GetParameter(self.handle, module, param, byref(value))
+ if result:
+ raise EAS_Exception(result, 'EAS_SetParameter error %d (param=%d)' % (result, param), 'EAS_GetParameter')
+ eas_logger.debug('Call EAS_SetParameter: module=%d, param=%d, value=%d' % (module, param, value.value))
+ return value.value
+
+ def SelectLib (self, test_lib=False):
+ eas_logger.debug('Call EAS_SelectLib: test_lib=%s' % test_lib)
+ easdll = cdll.LoadLibrary('EASDLL')
+ with self.lock:
+ result = eas_dll.EAS_SelectLib(self.handle, 0, test_lib)
+ if result:
+ raise EAS_Exception(result, 'EAS_SelectLib error %d' % result, 'EAS_SelectLib')
+
+ def LoadDLSCollection (self, path):
+ eas_logger.debug('Call EAS_LoadDLSCollection: lib_path=%s' % path)
+ with self.lock:
+ result = eas_dll.EAS_LoadDLSCollection(self.handle, 0, path)
+ if result:
+ raise EAS_Exception(result, 'EAS_LoadDLSCollection error %d lib %s' % (result, path), 'EAS_LoadDLSCollection')
+
+ def SetAuxMixerHook (self, aux_mixer):
+
+ # if aux mixer has bigger buffer, re-allocate buffer
+ if (aux_mixer is not None) and (aux_mixer.buf_size > self.config.mixBufferSize):
+ buf_size = aux_mixer.buf_size
+ else:
+ buf_size = self.config.mixBufferSize
+
+ # allocate audio buffer for rendering
+ AudioBufferType = c_ubyte * (2 * buf_size * self.config.numChannels)
+ self.audio_buffer = AudioBufferType()
+ self.buf_size = buf_size
+ self.aux_mixer = aux_mixer
+
+ def SetDebugLevel (self, level=3):
+ """Sets the EAS debug level."""
+ with self.lock:
+ eas_logger.debug('Call EAS_SetDebugLevel')
+ eas_dll.EAS_DLLSetDebugLevel(self.handle, level)
+
+#---------------------------------------------------------------
+# EASAuxMixer
+#---------------------------------------------------------------
+class EASAuxMixer (object):
+ def __init__ (self, eas=None, num_streams=3, sample_rate=44100, max_sample_rate=44100):
+ eas_logger.debug('EASAuxMixer.__init__')
+ self.Init(eas, num_streams, sample_rate, max_sample_rate)
+
+ def __del__ (self):
+ eas_logger.debug('EASAuxMixer.__del__')
+ self.Shutdown()
+
+ def Init (self, eas=None, num_streams=3, sample_rate=44100, max_sample_rate=44100):
+ """Initializes the EAS Auxilliary Mixer."""
+ eas_logger.debug('EASAuxMixer.Init')
+
+ if hasattr(self, 'eas'):
+ raise EAS_Exception(-1, 'EASAuxMixer already initialized', 'EASAuxMixer.Init')
+
+ # initialize EAS, if necessary
+ if eas is None:
+ eas_logger.debug('No EAS handle --- initializing EAS')
+ eas = EAS()
+ self.alloc_eas = True
+ else:
+ self.alloc_eas = False
+ self.eas = eas
+
+ # initialize library
+ eas_logger.debug('Call EAS_InitAuxMixer')
+ buf_size = c_int(0)
+ result = eas_dll.EAS_InitAuxMixer(eas.handle, num_streams, sample_rate, max_sample_rate, byref(buf_size))
+ if result:
+ raise EAS_Exception(result, 'EAS_InitAuxMixer error %d' % result, 'EAS_InitAuxMixer')
+ self.buf_size = buf_size.value
+ self.streams = []
+ eas.SetAuxMixerHook(self)
+
+ def Shutdown (self):
+ """Shuts down the EAS Auxilliary Mixer"""
+ eas_logger.debug('EASAuxMixer.Shutdown')
+ if not hasattr(self, 'eas'):
+ return
+
+ with self.eas.lock:
+ if len(self.streams):
+ eas_logger.warning('Stream was not closed before EAS_ShutdownAuxMixer')
+ for stream in self.streams:
+ self.CloseStream(stream)
+
+ self.eas.SetAuxMixerHook(None)
+
+ # shutdown library
+ eas_logger.debug('Call EAS_ShutdownAuxMixer')
+ result = eas_dll.EAS_ShutdownAuxMixer(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_ShutdownAuxMixer error %d' % result, 'EAS_ShutdownAuxMixer')
+
+ # if we created the EAS reference here, shut it down
+ if self.alloc_eas:
+ self.eas.Shutdown()
+ self.alloc_eas = False
+ del self.eas
+
+ def OpenStream (self, decoder_func, inst_data, sample_rate, num_channels):
+ """Opens an audio file to be played by the JET library and
+ returns a JET_File object
+
+ Arguments:
+ callback - callback function to decode more audio
+
+ """
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_OpenAudioStream')
+ decoder_func = EAS_DECODER_FUNC(decoder_func)
+ stream_handle = c_void_p(0)
+ result = eas_dll.EAS_OpenAudioStream(self.eas.handle, decoder_func, inst_data, sample_rate, num_channels, stream_handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_OpenAudioStream error %d on file %s' % (result, path), 'EAS_OpenAudioStream')
+ self.streams.add(stream_handle)
+ return stream_handle
+
+ def CloseStream (self, stream_handle):
+ """Closes an open audio stream."""
+ with self.eas.lock:
+ eas_logger.debug('Call EAS_CloseAudioStream')
+ result = eas_dll.JET_CloseFile(self.eas.handle, stream_handle)
+ if result:
+ raise EAS_Exception(result, 'EAS_CloseAudioStream error %d' % result, 'EAS_CloseAudioStream')
+
+#---------------------------------------------------------------
+# JET_Status
+#---------------------------------------------------------------
+class JET_Status (Structure):
+ _fields_ = [('currentUserID', c_int),
+ ('segmentRepeatCount', c_int),
+ ('numQueuedSegments', c_int),
+ ('paused', c_int),
+ ('location', c_long),
+ ('currentPlayingSegment', c_int),
+ ('currentQueuedSegment', c_int),
+ ]
+
+#---------------------------------------------------------------
+# JET_File
+#---------------------------------------------------------------
+class JET_File (object):
+ def __init__ (self, handle, jet):
+ eas_logger.debug('JET_File.__init__')
+ self.handle = handle
+ self.jet = jet
+
+#---------------------------------------------------------------
+# JET
+#---------------------------------------------------------------
+class JET (object):
+ def __init__ (self, eas=None):
+ # eas_logger.debug('JET.__init__')
+ self.Init(eas)
+
+ def __del__ (self):
+ eas_logger.debug('JET.__del__')
+ self.Shutdown()
+
+ def Init (self, eas=None, config=None):
+ """Initializes the JET Library."""
+ # eas_logger.debug('JET.Init')
+
+ if hasattr(self, 'eas'):
+ raise EAS_Exception(-1, 'JET library already initialized', 'Jet.Init')
+
+ # create some members
+ if eas is None:
+ # eas_logger.debug('No EAS handle --- initializing EAS')
+ eas = EAS()
+ self.alloc_eas = True
+ else:
+ self.alloc_eas = False
+ self.eas = eas
+ self.fileOpen = False
+
+ # handle configuration
+ if config is None:
+ config_handle = c_void_p(0)
+ config_size = 0
+ else:
+ jet_config = S_JET_CONFIG()
+ jet_config.appLowNote = config.appLowNote
+ config_handle = c_void_p(jet_config)
+ config_size = jet_config.sizeof()
+
+ # initialize library
+ # eas_logger.debug('Call JET_Init')
+ result = eas_dll.JET_Init(eas.handle, config_handle, config_size)
+ if result:
+ raise EAS_Exception(result, 'JET_Init error %d' % result, 'JET_Init')
+
+ def Shutdown (self):
+ """Shuts down the JET library"""
+ eas_logger.debug('JET.Shutdown')
+ if not hasattr(self, 'eas'):
+ return
+
+ with self.eas.lock:
+ if self.fileOpen:
+ eas_logger.warning('Stream was not closed before JET_Shutdown')
+ self.CloseFile()
+
+ # shutdown library
+ eas_logger.debug('Call JET_Shutdown')
+ result = eas_dll.JET_Shutdown(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'JET_Shutdown error %d' % result, 'JET_Shutdown')
+
+ # if we created the EAS reference here, shut it down
+ if self.alloc_eas:
+ self.eas.Shutdown()
+ self.alloc_eas = False
+ del self.eas
+
+ def OpenFile (self, path):
+ """Opens an audio file to be played by the JET library and
+ returns a JET_File object
+
+ Arguments:
+ path - path to audio file
+
+ """
+ with self.eas.lock:
+ eas_logger.debug('Call JET_OpenFile for file: %s' % path)
+ result = eas_dll.JET_OpenFile(self.eas.handle, path)
+ if result:
+ raise EAS_Exception(result, 'JET_OpenFile error %d on file %s' % (result, path), 'JET_OpenFile')
+
+ def CloseFile (self):
+ """Closes an open audio file."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_CloseFile')
+ result = eas_dll.JET_CloseFile(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'JET_CloseFile error %d' % result, 'JET_CloseFile')
+
+ def QueueSegment (self, userID, seg_num, dls_num=-1, repeat=0, tranpose=0, mute_flags=0):
+ """Queue a segment for playback.
+
+ Arguments:
+ seg_num - segment number to queue
+ repeat - repeat count (-1=repeat forever, 0=no repeat, 1+ = play n+1 times)
+ tranpose - transpose amount (+/-12)
+
+ """
+ with self.eas.lock:
+ eas_logger.debug('Call JET_QueueSegment')
+ result = eas_dll.JET_QueueSegment(self.eas.handle, seg_num, dls_num, repeat, tranpose, mute_flags, userID)
+ if result:
+ raise EAS_Exception(result, 'JET_QueueSegment error %d' % result, 'JET_QueueSegment')
+
+ def Clear_Queue(self):
+ """Kills the queue."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_Clear_Queue')
+ result = eas_dll.JET_Clear_Queue(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'JET_Clear_Queue error %d' % result, 'JET_Clear_Queue')
+
+ def GetAppEvent(self):
+ """Gets an App event."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_GetEvent')
+ result = eas_dll.JET_GetEvent(self.eas.handle, 0, 0)
+ return result
+
+ def Play(self):
+ """Starts JET playback."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_Play')
+ result = eas_dll.JET_Play(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'JET_Play error %d' % result, 'JET_Play')
+
+ def Pause(self):
+ """Pauses JET playback."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_Pause')
+ result = eas_dll.JET_Pause(self.eas.handle)
+ if result:
+ raise EAS_Exception(result, 'JET_Pause error %d' % result, 'JET_Pause')
+
+ def Render (self, count=None, secs=None):
+ """Calls EAS_Render to render audio.
+
+ Arguments
+ count - number of buffers to render
+ secs - number of seconds to render
+
+ If both count and secs are None, render a single buffer.
+
+ """
+ # calls JET.Render
+ with self.eas.lock:
+ self.eas.Render(count, secs)
+
+ def Status (self):
+ """Get JET status."""
+ with self.eas.lock:
+ eas_logger.debug('Call JET_Status')
+ status = JET_Status()
+ result = eas_dll.JET_Status(self.eas.handle, byref(status))
+ if result:
+ raise EAS_Exception(result, 'JET_Status error %d' % result, 'JET_Status')
+ eas_logger.debug("currentUserID=%d, repeatCount=%d, numQueuedSegments=%d, paused=%d" %
+ (status.currentUserID, status.segmentRepeatCount, status.numQueuedSegments, status.paused))
+ return status
+
+ def SetVolume (self, volume):
+ """Set the JET volume"""
+ eas_logger.debug('Call JET_SetVolume')
+ with self.eas.lock:
+ result = eas_dll.JET_SetVolume(self.eas.handle, volume)
+ if result:
+ raise EAS_Exception(result, 'JET_SetVolume error %d' % result, 'JET_SetVolume')
+
+ def SetTransposition (self, transposition):
+ """Set the transposition of a stream."""
+ eas_logger.debug('Call JET_SetTransposition')
+ with self.eas.lock:
+ result = eas_dll.JET_SetTransposition(self.eas.handle, transposition)
+ if result:
+ raise EAS_Exception(result, 'JET_SetTransposition error %d' % result, 'JET_SetTransposition')
+
+ def TriggerClip (self, clipID):
+ """Trigger a clip in the current segment."""
+ eas_logger.debug('Call JET_TriggerClip')
+ with self.eas.lock:
+ result = eas_dll.JET_TriggerClip(self.eas.handle, clipID)
+ if result:
+ raise EAS_Exception(result, 'JET_SetTransposition error %d' % result, 'JET_TriggerClip')
+
+ def SetMuteFlag (self, track_num, mute, sync=True):
+ """Trigger a clip in the current segment."""
+ eas_logger.debug('Call JET_SetMuteFlag')
+ with self.eas.lock:
+ result = eas_dll.JET_SetMuteFlag(self.eas.handle, track_num, mute, sync)
+ if result:
+ raise EAS_Exception(result, 'JET_SetMuteFlag error %d' % result, 'JET_SetMuteFlag')
+
+ def SetMuteFlags (self, mute_flags, sync=True):
+ """Trigger a clip in the current segment."""
+ eas_logger.debug('Call JET_SetMuteFlags')
+ with self.eas.lock:
+ result = eas_dll.JET_SetMuteFlags(self.eas.handle, mute_flags, sync)
+ if result:
+ raise EAS_Exception(result, 'JET_SetMuteFlag error %d' % result, 'JET_SetMuteFlags')
+
+
diff --git a/jet_tools/JetCreator/img_Copy.py b/jet_tools/JetCreator/img_Copy.py
new file mode 100755
index 0000000..6df3f2e
--- /dev/null
+++ b/jet_tools/JetCreator/img_Copy.py
@@ -0,0 +1,51 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xd3\x02,\xfd\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x02\x8aIDATX\x85\xcd\x97Mk\x13A\x18\xc7\xff\xcf\xce\xee\
+\x9a\xe0!9X\xfa\t\x92\x8dh\xfd\x06\x82\x07\x11z\n\xf8\x1dz\xd0[%\x8d"\x05O\
+\x966*\x05\xf1\x05\xf1\xe0I\xc5\xb3w\xa9\x96\xe2QQ\x0f\x01\xb5*B$x\xc8\x8b\
+\xdb$\xe6e\xc6C\xdc\xcd\xce\xb2\xb3/\xbaA\x1f\x08\x99\xd9\x99\x9d\xffo\xfe\
+\xcf3\x9b\r\xf0\x8f\x83\x00`{\xbbvn0\x1c-\xa6\xb9\xb0a\x18\xdf+\x17\xaa\xb7\
+\xa3\xe6\xe9\x000\x18\x8e\x16/U/\xa7\xa9\x8f\xcd\xda\xc6B\x9cy\xba\xb7\xd3n\
+\xb7S\x11\xcf\xe7\xf3\xb1\xe7j\xa9(\xfeE\xe8\xd1S\xe2\x07\xe7|>\x00\x8f\x9f<\
+<\xdf\xedvb\xe5\xd4\t"`\xb3v\xf5\x8aR\xd80\xdeVV\xab\'\x02\x01\xea\xf5\xba\
+\xd4\xefv;\x0b\x17\xd7\xd2-\xd2\xadk\x1bK\x80\xc2\x81R\xa9$\xf5\x9f\xef>\x03\
+\x90N\x91\x12\x11r\xb9\x9c\xdb\xff?\x8b\xd0\x9f\x82\xb4B\x08\x11\x0f@\x95\
+\x824\x84\xfd\x10\xa1\xa7@u\xac\x928\xe4\x08\n!P(\x14\xa0\xeb\xb2\xa42\x05ag\
+\xda\xefP\x98\xb0\x1f"\xd2\x01\xce9\x8a\xc5\xa2tmwo\'R0J8h<\x10\xc0\x0f\x13\
+\x14\xaa\x14\x84\tZ\x96\x15x\x8f\x04\xe0\x08&MA\xd8\xae\xfdcD\xa4\x06p \xfc)\
+\xd8{\xf9"\x10$\xae\xddsIA\\A`j\xbf\n\xc2\x05\xe0\x9c\xbb\x82a\xf6\x0b!\xdc|\
+F\xd9\xed|{\xdbD$\xad/\x01\xc4\x11\x8f#\x185?\x14\xc0\x1b\xfek\xce\x02\xaa\
+\x14D\xc1X\x96\x05"J\'\x05\x7fR\x80\x8c1\xf5\x930\xed\x14x\xc74M\x03c\xcc\
+\x05\xd0\xb4\xd9\x8f\xb0\x04\x90\xc9d$\x08\xefD\x7f1\xc5\xd9=\x11I\xc2N\xbb\
+\xf1\xad\x01\x011\x030L\xa3y\xeb\xceM\xe5\xff\x02\xa2\x19T\x12\xbb\x89\x08\
+\xf7\x1f\xdc\x83i\x9a\x00\x00\xd301\x1c\r\x91\xcdd\xa7\x8b:\x00\x95\xd5\xea]\
+\x9580}\xb7\xe3\x9c+m\x0e\xb3\x1b\x00N\x9f:\x03\xe7\xfe\xc1`\x80\xd7o^\x81@\
+\x8f\\\x808\x11\x04\xe0m\xab\xecf\x8c\x81s\x8eV\xab\x05!\x04>\xec\xbf\x87\
+\xdd\xb7;}\xfb\xe7Jb\x00\xd5\xaeU\xc2\xba\xae\x83s\x8e^\xaf\x07\xdb\xb6\xf1\
+\xe5\xebg\xf4\xfa?ZG\x8f/\x9d,/\x97{\x89\x00&\x93\t\xb2\xd9\xacT\x8cD\x04"\
+\x82\xa6i\xd2\x871\x06!\x04\x0e\x0el\x00\xc0\xfe\xa7\x8fh4\x1b\x02\x02;\x87\
+\x8e\x1c>[^.\xbbo\xb7\x14,\'\xc7\xf5\x1b[\xef\xc6\x93\xf1\xb1\xb8\xb0N\x88\
+\xdf\xb5\xaei\xac9\x16\x93\x95\xf5\xb5\xf5\xa7I\xd7\x98{\xfc\x02\\\xe5\x0c\
+\xee\x86\xfe\xecP\x00\x00\x00\x00IEND\xaeB`\x82h\xfd>s' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Cut.py b/jet_tools/JetCreator/img_Cut.py
new file mode 100755
index 0000000..2ce5ca5
--- /dev/null
+++ b/jet_tools/JetCreator/img_Cut.py
@@ -0,0 +1,104 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01-\x08\xd2\xf7\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x07\xe4IDATX\x85\xad\x97{PU\xc7\x1d\xc7\xbf\xbb{\xce\xb9\
+\x8fs/\xf7\x12^\x82T\xb8\x02Q\xab\xc18\nV\x1b\x9f\xa0M\x1f\x99\xb6\x7f\x946&\
+\x13;M\x94\xe9\xa4\x99vb\x0c\x125\xe6\x86\xd2\x82\x8f\x90L\x9264\xd3vR\x9b4\
+\xb1\xc9\xc4\xcc\xb4\xd3\xd6\x17\xad\xd1&\x05\xb5>\x88\x164 p\x91\xc7\xe5\
+\xa1\xf7r\x9f\xe7\xecn\xff\xc0\xeb\xe0\x03\x84iw\xe67\xe7\xcc\xd9\xdf~\x7f\
+\x9f\xf3\xdb\xfd\xed\xd9C0A{\xe9\xe5\xddU\xf1xl[$\x14wz\xbd\xde\x91\x89|om\
+\xbb_\xaa\xad3M\xf3\'*\xb3\xda7m\xda\x14\x19\xcf\x8f\x8e\xd7Q[[\x9b\xc59\x7f\
+:#=#\x96\xe4\xb2{\xa7\x12\xbc\xa6\xe6\xc5\xfb@HyZZzL\xc0\xdc4\x91\xef\xb8\
+\x00\xaaF\xdfY\\\xbcX[S\xba\xd6"\t~X\xbd\xbbz\xf6d\x82K)\x89f\xb1\xee]\xfa\
+\xa5/[\xd6\x96~\xc5\xc2(\xad\x98h\xec\x1d\x01jj\xaa\x96*\x9a\xb2\xb0 \xff^EJ\
+\x82%\xc5K,6E{k2\x00;\xf7\xd4lp:\x93\xf2rsr\xa9\x10\x12\xc5\xc5\xc5v\x9b\xa2\
+\xd5N\t\x00\x94\xce\xcf\xce\xfa\x02\xe3\x9cCJ\t\x8f\'\x8fZ\xad\xb6\xb9\xb5\
+\xbb\x7f\xf6\xc8D\xc1\xab\xab\xab\xd3\x18%\xbbW\xaf*u\xc4\xe3\x06\xa4\x94\
+\xc8\xcc\x9cN\xa5\x94ES\x02\xe0\xc0\xd9\xcf\xdb.1\t\t\x000\x0c\x13kJJuF\xd8\
+\xab;^\xd9\x914\x9e\x98M\xd7^\x9d\xfb\xc5y\x9a\xa2\xa8\x90R\x82R\x8a\x93\xa7\
+\x1a#\x9c\xf3O\xa6\x04\xb0\xf5\xd9\xad\xc7\xa5D\xdd\xf1\xe3\xc7\xc2\x94RH)a\
+\xb5\xda\x91\x9f\x9foM\xe2\x8e\x9a;\x8d\xf9\xf9\xae\xaa\xe5\x84\x90\x87\xee\
+\x9b7\xdf\xc29\x07\x00\x84BAttv\x85B\xc1\xe8\xfa)\x01\x00@(\x18y\xd1\xd7\xdd\
+5\x12\x0c^\x03!\x04\x9c\x0b\x14-*\xb6\x11\x8a\xf5;wV\x17\x8e\xf5\xad\xaf\xaf\
+W-\x8a\xe5w%\xabJ\xed\x86a\x02\x004M\xc5\xe1\x86\xc3!a\x98\x1b&*\xe1q\x01\
+\xbc^o\xd80\x8d\x8d\x87\x8f\x1c\ni\xdahJ\xe3q\x03\x0f,]fe\x9a\xf6\x96\x94\
+\x92$|\x83\xc1\xe1-\xe9\x19\xe9\xa9n\xf7=\x10B\x80\x10\x82\x8b\x17[\xcch,\
+\xdaXQ\xb1u\xffx1&\x04\x00\x80\xcag\xb7}\x14\x8b\xc5\x9aZ/\xfe\xc7$\x84@J ++\
+\x9b&9\x9c\xf9\xbbv\xd5\xac\x07\x80]\xbb\xaa<\xa0d\xcb\xf2\x07V\xeb\xf1x\x1c\
+RJ0F\xd0t\xf2D\xdc\x88\xf2\x1fL\xa4\x7fW\x00\x00\x88\xc7\xf8\xe3M\'N\xc4\x15\
+E\x81\x94\x12\xb1X\x0c%\xab\xd78\x08#\xaf\xd4\xd5\xedp3\xd5\xf6\x9b\xc5\xc5K\
+4\xc3\x18]\xf5\x16\x8b\x05G\x8f\xfd=D\xa4\xac\xa9\xac\xac\xbc\xfc?\x03TVV\
+\xb6I)^>v\xfch81\x15\x84P\xcc\x993W\xe3\xc2\xfe\x81\xc5b)\xca\xcd\xf1(\x89\
+\xd4_\xbd6\x84\xbe>\xff\xa0\xd3\x99|\xc7\xc5:e\x00\x00\x18\tD\xaa|\xdd]\xc1\
+\x91\x91 \x00\xc04M\xdc_x\xbf\x952\xba\xaahQ\xb1\x1e\x89D \xa5\x84\xddn\xc7\
+\x91\x86\xc3!3\x1e\x7f\xac\xbc\xbc\xdc\xf8\xbf\x01x\xbd\xde\xa8\xc9\x8d\xc7\
+\x0f7\x1c\n\xd9\xedvH)\x11\x89D\xb1jE\t9s\xf64\x84\x10P\x14\x05\xe7\x9a\xcf\
+\xc4Mn\xfe\xa5\xa2b\xeb?&\xa3;i\x00\x00\xd8\xb2y\xdb\x9f\xe3F\xfc\xd3K\x9f\
+\xb7\x9a\x8a\xa2\xe0\xe2\xa5\x16$\'\'C\xd34\\\xeeh\x07S\x18\xce5\x9f\x8b\t#\
+\xf4\xe4d5\xa7\x04\x00\x00<\x1e\xdd\xd0\xd8\xd4\x18\xb7X,\xf0u\xfb\xd0\xdf\
+\xdf\x8f\x15\xcbV\xa2\xa5\xf5\x02\x0e\x1e\xfak\x88ss\xcb\xe6\xcd\xde\xfe\xa9\
+h\xb2\xa98\x1f<\xd8p\xf5\xeb32\x9f\x18\x94"yu\xc9\x83H\xec\x92$\x1cF\xef\xc7\
+G\r\xc7\x81\x86\xb2?\xf5\xf4\x88\xa9h*Sq~\xc3\x86E\xeaG\xef{\x06=y8u\xb2\x11\
+\xe6o\xeb\x119\xff\x19b\x83\x03\xb0\xc5c\xbat\xba\x9e\x07\xb0}*\x9a\xe4\xee.\
+\xa3\xad\x1eP\x15\x079\xbf sf\x9e:#\x97\xb4gg\xa1\xf7\x83\xbd\xc8w\xa5!\xdb\
+\x99\x02P\xe0\xe3\xce\x960\xe7r\xd6\xc6\x08|\x93\xd5\x1d7\x03o\x03\xc9\x92\
+\xd2\'UB\xd6q)\xb3A\xa0Q\x83)\x16\xc2\x88>0\x00\xe3\\S\x04 \x1fv\x04\x87\xbe\
+\x95\xa2\xeav\xcdb\x81\xc7\x9d\xaeu\x0e\x0f\x9c~\x97A\x95\x00e\x94v\x19\x9c\
+\xbf\xab\n\xf1Z\x1904\xe9\x0c\xbc\r|\x87P\xfa\xebT\x97K\xc9\xce\xc8\xb0\xb9t\
+\x1d\x12@\xdf\xd0\x10\xda|>X\xdc\xb6\xa8?6r\xc4\x19\x10\xdf\x0e:\xc9{\x19\
+\xba\xeb\xab\xd3T\xa7\xc5a\xb7\xa3y\xa8\x1bk\xe7\x15\x81\x00\x18\x0e\x04\xd0\
+\xe6\xf3E\xae\x0c\x0c\x98\xe0\xfc\x89u\xc0\xbe\xbb\x02\xfc\x81\xd2g(c/\x14\
+\xe6\xe5\xe9\x86i\xa2\xa3\xb7\xf7Z0\x1c\xd6\x18c\xea\xc29s\x94p$\x82\xcf:\
+\xda\xfbX\xdc\x9c^\x06\xf0\xd7\xd2\xe0\xb0G\xe9\xd1\x99\xee\xf4\xf9\x0e\xa8\
+\x94ZU\xb4uv\x07\xb8\x10$\xc5\xe9$3\xb3\xb3\x1d\x94R4]\xb8\x1029\xafz\xd84o:\
+\x1d\xddT\x86\xef\x00e\x84R\xef\xc2\xd9\xb3\xf5\xce\xde\xdeps[[\xd7\xf0\xc8\
+\xc8\xf7\x15!R\xa2\xdcx\xee|[\x1b2RR L\x91T\x06p\x00\xf8\x91\x1f#AU\xac\xbc<\
+\xd4\x0fS%h\xef\xba\x12\x88\xc5b\x0fs\xc3\x98\xdd;4\xb4\xe5_\xcd\xcd\xc3\x1d\
+==\xd1\xa5\x85\x85\xbaB\xe9\xf6\xdfS\xfa\xdd;\x02\xec\x03\\\x84\xd27\xe7\x17\
+\x14\xd8\xdb\xbb\xbb\xc3\x83\x81@\xa3[\x88\x82\xc7\x80\xfde@D\x13\xf8c8\x16\
+\x8b(\x8a\x02Hy\xd3\xda\xf9\xf1\x10\x02jDr]\xd7\x117\x0c\x85\x00\'\xd7\x01W\
+\x1e\x05^gBx\xae\xf8\xfd\xad\x97:;cEs\xe7\xea\n!\xbf\xda\x07\xdcs\x1b\x80Iiy\
+\xaa\xdb\xad\x00\xc0\xc0\xd5\xab#n!\x1e\xfc\x1a\x10K\xf4s`Y\xb2\xae\x1b\xc1P\
+\x08\nc\xed\xb7N\x9dBY\xbb\xce,p\xdal\xa6\x00V&\x9e\x97\x01\xd7\x98\x10\xcb\
+\xbb\xfd\xfe(\x01\x90\x95\x96\xa6\x1a\x94n\xbc\r\x80\x12\xb2>;=\xdd\xee\xeb\
+\xef\x0f\x9bB\xfctl\xf0z\xc0\xce\x18\xdbyonnRgOO\x94\x0b\xb1\xf7V\x00.\xc4\
+\xde\xae\x9e\x9e\xa8\'++\x89R\xba\xa7\x1e\xb0\x8f\x85\x90\xc0\xce\x8e\x9e\
+\x9e\xa8\';\xdb\xa62v\xe3\x88F\xc7\x08x\x1c\xba\x8eP8\x1c\'\xc0\xa9\xb1\xc1\
+\x93\x18;\x90\x99\x9a\xea\xd24\r>\xbf\xdf0\x84\xf8\xe5m\x19\x10\xe2\x17\xdd~\
+\xbf\xa1i\x1aR\\\xae\x14\'\xa5\x07\xc7Bp!\x9a\x82\xa1P\xd4\xe5t\xc2\xe4<\xf7\
+6\x00B\x88\t\x00\xba\xcd\xc6\x08P\xf8\x14\x90\xf4&\xb0\xce\xc1X[fj\xea\x82y\
+\x05\x05\xb6O\xce\x9c\t\rJ\xf9\xf4z@\x02H\x01\x90\x9a\xb02\x80\x0c\x00\x9bN\
+\xb7\xb6\x86f\xe5\xe4XS\xdd\xee\x05\x0eJ\xdb\xeb\x81u\x8f\x00I\x84\xd2\x85\
+\x0e]W)\xa5\x00pc\xbb\xbeQ\x86\xef)\xca\x87\x0599\xdfLq\xb9Hcss\xc8\xe4\x9c%\
+\xe9\xba1\xdb\xe3qZ5\r\xff<{6\xdck\x9a\xf5Oq\xfe\xc6up\x8a\x9b\xcbX\x02\x10u\
+\x8cm\x9c\xceX\xf9\xc2Y\xb3\xecQ\xc3@\x9b\xcf\x17\x0c\x84\xc3\x9a\xaa(\xa2d\
+\xf1b\xdbp O\x9c?\xff\xb7\xef\x19\xc67\x00\xf0\x84\x80R\x03\x14\xe40\xf6\
+\xe9\xac\xdc\\\xc7\x8ci\xd3\xa8\xaa(\x08\x8c\x8c\xa0\xbd\xbb;\xea\xf3\xfb\
+\xf9\x05)_\xa8\x12\xe2\x00\x00\x15\xa3;h\xc2\x18FK\xd2L\xd8s\x94\x96\xce\x03\
+\xb6g\xa5\xa51\xcf\xf4\xe9\xd6\x14\xb7\x1b\x00\xe0\xeb\xeb\x13\xffni\t^4\xcd\
+\x15\xcf\x03-\x00\x8c\xb1o@\xeb\x80\xc2tE\xd9CF\xffd(\x08\xe9\x1d\xe6|\xff\
+\xfbR\xeem\x00\x82\xd7\x83%\x8c\x8c19\xc68\x00\xbe\x0cp\x96\x11\xf2h\x8a\xa2\
+<D\x84\xc8\x04 $\xa5\xa7\xba\x0c\xe3\x99\n\xe0\xccuX9\xde\xc7(!Lo\tJ\xef`\
+\x899\x95c\xae\xfc\xfa}"3bL\xdfM\xed\xbfj\xaa\x81\xd8\x9f\'F\x8c\x00\x00\x00\
+\x00IEND\xaeB`\x82\x989\xee\xcb' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Find.py b/jet_tools/JetCreator/img_Find.py
new file mode 100755
index 0000000..aafb0e6
--- /dev/null
+++ b/jet_tools/JetCreator/img_Find.py
@@ -0,0 +1,85 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01d\x06\x9b\xf9\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x06\x1bIDATX\x85\xe5\x97kl\x93\xd7\x19\xc7\x7f\xe7\xbd\xf8\
+\x96\xc4vnNB.M(\xb1\r\x94\xc2(J\xc8F\xa9&Z\xb1\xa6\xd0\x0f\x05\x8a`Z\x99\x8a\
+\xa6\xa9\xd2\xbeL\xfb\xb0I\x93\x96i\x9a4i\x9a4M\x1a\x03\xa1J\x8b\x98\x986\
+\xc1\xc8\x9ai[\xfb%\x9d\xda\x02+YQK\xb2e\x8b\x93%C$\xe0\\\xb0\x9d\xf8\x12\
+\xbf\xd7} \xb6\x1cc\x93\x80\xf6m\x8f\xf4\xe8\xbc\xe7\xf8\xd5y~\xef\xff<\xcf9\
+\xc7\xf0\xffn\xa2x\xe0\xcc\x993\xaf)\x8a\xd2/IR\x95$IH\x92\x84eYX\x96\x85a\
+\x18\x98\xa6Y\xb2]oL\x08\xb1\xaci\xdaW\xfb\xfb\xfb\xaf<\x92\xe8\xec\xd9\xb31\
+\xd34m\xcb\xb2l\xcb\xb2lM\xd3\xf2\xcf\xa6i\xaeq\xc30\xf2\xae\xeb\xfa\x1a\xd7\
+4-\xef\xd9l\xd6\xcef\xb3\xf6\xe9\xd3\xa7c\xc5\xf1\x94\xe2\x01\xd34\xfdB\x08\
+\xa6\xa6\xa6\x10B`\x9a&\x15\x15\x15h\x9a\x86\x10\x0f\x04+n\xf3r\x8a\x87\x04\
+\x05\xc0\xb6m\x02\x81\x00\xba\xae\xfb\xd7\x05\xb0m;?Y\xce\x0b\xfbO\x02Pn\xbc\
+$\x80eY%\x03\n!\x98\x98\x98\xc04\xcd\x92\x13\x96\x1a\x93e\x99`0X6\xf8\xba\
+\x00\x85\xc1\x85\x10tvv\x96T\xa2\x1c@\xb1\xe5\xd4]\x17\xa0\xd42\x08!\x88D"\
+\x98\xa6Y2`9\x05B\xa1\xd0\xe3\x01\x14\x06(\xce\x83`0X6\x0f\xfeg\n\x94\x02\
+\xc8\xf5\x9fD\x81p8\xfc\xe4\x007o\xded\xd7\xae]\xf9~(\x14z\xe4\x97\xaf\xa7\
+\xc2c\x01\x00tuu\xe5\xeb_\x08\xc1\xf8\xf8x\xfe\xf7dr\x99\xe8\xfc=\x96\x97\
+\x970\x0c\x03I\x92\xa9\xac\xa8\xa4\xba\xba\x86\xba\xdaz$IBQ\x14\xb6n\xdd\xfa\
+\xf8\x00\xb6m#\x84x\xa8$C\xa1\x10\xe9t\x8ak\x7f\xbd\x8a\xd3\xe9\xe4K\x07_\
+\xa6\xbe>\x80Cu`\x98\x06\xf1D\x8c\xd1\xbf\x8f0:2\xc2\xfe}/\xd0\xda\xda\xb6f\
+\xee\xdc|\x1bV\xa0\xd8n\x8d\xdcbt\xf43\x0e\x1d~\x95\xb6\xd6v\x06?\x1c\xe3\
+\xca\xdb\x9f29\xbbDc\x8d\x87\x83{\x9a9\xf9R7\xcf>\xb3\x93\xdf\xfc\xf6\xd7\
+\xb44\xb7\xb1\xff\xf9\x17\x1e\xa9\x80T\x0e\xa0\xb8\n\x84\x10\xdc\x99\xb9Mo\
+\xef+x}\xf5\x1c\xe9\xfb\x13\xff\xba\xa7\xf3\x8d\xd7\xbb\xf8\xe5w{\xf9\xf6\
+\x1b_ k;8\xf6\xfdw\x99\x8e\xa69y\xe2\xcbLMO\x92J\xa7\xf2\xb9QJ\x81\r\x01\x00\
+\xcc\xce\xce\xe0v\xbbhmm\xe7\xcd\x9f\x0cq\xe2@\x88\xaf\xbc\x18dsC%^\xb7B\xc0\
+\xabr`w3\xa7z\xb7\xf1\x9d\xb7?f)c\xb1o\xdf\xf3|\xf2\xc90B\x08l\xdb\xde8@)\
+\xa9"\x93\xe3<\xb7{\x0f\xbf\xff`\x8c\x9d\x9d\x9b\xe8\xde\xde\x82\xac(\xc8\
+\xb2\x8c\xaa(8T\x15\x97\xd3I]\xb5\x8f\x03{\x83\xfc\xf4\xf2\x08\xdb\xb6ngrj\
+\x02\xc306\xbe\x04\xb9\x97s\x96Sa.\x1a\xc5S\xe1a\xf0\xfa,;\x83\x01\x0c\xd3B7\
+,4\xc3\xc4\xb0,\xc0F\x126+\xbaAK}\x15\xb7\xa6\xee\x93\xcad\xa9\xf6\xfb\x99_\
+\x98/\xbb\x04\xebnD9\xcbjY4-\xcb\x9d\x85eL-\xcbr2\x8d\xa6J(\x92\x00\x1e\xc8\
+\xfb\x9f{1\x16\x17\x93\xe8\x96\xc0\xad\nn\xdf]\xc4\xe9r\xa3kZY\x80\xb29\x00p\
+\xe3\xc6\r$\xe9\xc1+\xaa\xea@7t\x02~\x0fK\x1a\xc8\xaa\x03Eu\xe0p9q\xb9\xdc\
+\x98B\xc1_]M\xc7S\xcd\xb4lj$k\x80m\xe9,%\x128\x1c\x8e\x87\x02\x97U\xc00\x8c\
+\xfcZ\xf5\xf4\xf4\xa0\xad\xd2764\x92I\xaf\xf0\xc5\x1d~>\xbeu\x1b\xd9\xc8P\
+\xe9\x92Q0\x11\xb6\x8e,l\x0c\x0b\xb2\xa6\xe0\xce\xa2Nm\xa5J\xad\xcf\xc9\xfc\
+\xfc<\r\r\x8de\x01\x1eY\x05\x85\x1bQ8\xbc\x8d\x91\xd1\x11N\xbc\xf4\x0c\x8b\
+\xf1\x15\x12Y\x99*_\r\x81\xc6&\xda\xdb;\x08\x05\x83l\xd9\xd2IM\xa0\x89kc\xf7\
+\xf9\xe6kA\xa6\xa7\xa7I$\x12\xcc\xcdE7\xae@\xb9\x8dhS\xd3&\xae_\xbf\xca\xc2|\
+\x94\xf3\xdf\xda\xcf\x9b?~\x9f\xb1\xc9Y\x9em\xf7R[%\xa3\xeb\x16\xe3w\xd3\xdc\
+\xf8\xe7}\xfa\xde\xd8M\xb8\xa3\x8eH$A\xd7\x9en\xce\x9d?\xc7\xe1C\xafnL\x81\
+\xc2%(\xb6\xde\x97\x0f\xf1\xfe_\x86H\xc4\xe6\x18\xf8\xe1a\x0e\xedm\xe3o\x13\
+\xcb\xfclp\x8aw\x86\x17\xe9h\xaa\xe5\xf2\x0f^a\xdbS~&\'#$b\xcbd2Y\x9e\xfb\
+\xdc\x1e\x06\x07\x07P\x1d\n}}}kb>t|\x1d?~\xdc\xbep\xe1\x02\xb1X\x0c!\x04\x86\
+a\xe0\xf1x0M\x93\xb1\xb11VV2L\xfc;BMM\r={{\x08\xd47\xa0:\x1c\x98\x86I,~\x9fO\
+?\xbb\xc9\xd0\xd0\x10\xa1`\x98\x96\xe66\x0c\xc3 \x1a\x8d\xa2\xaa*\x13\x93\
+\x11F\xff1\xfa\x07aI\'/]\xba\x94,\tp\xf4\xe8Q\xfb\xe2\xc5\x8b%\x01r\xa5)I\
+\x12ssQ\xc6\'\xc6\x99\x9d\xb9C2\x95DUT|>?\xa1P\x98`g\x88s\xe7\x7fAUU\x15\x9d\
+[B\xe8\x9a\xce\xc2\xc2\x02\x0e\x87\x83\xb9\x85(W\xaf~8b(\xf6\xc1\x81\x8b\x03\
+w\x1f\xeb0\xca\xd9\x83kv\x03\x8d\x8dM\xf9\xbd"w\x82\xe6\xec\xeb_{\x8b\x0b\
+\xbf\xea\xe7\xda\xf5\x8f\xd8\xdb\xf5y\xea\xea\xea\x88\xc7\xe3\x84\x83a\x02\r\
+\xf5;\x06~w\xe5\xe7\xc0\x91\xe2\x1c\xf0\n!\x96l\xdb\xc6\xeb\xf5\xe2\xf5z\xf1\
+\xf9|\xb8\xddn***\xf0x<8\x9d\xce\xfc\xbf%M\xd3H\xa7\xd3\xa4R)\x92\xc9$\x89D\
+\x82D"A<\x1e\'\x9dNs\xf4\xc8\xebl\xeex\x9a?\xbf\xfbG,\xcb\xa4\xa5\xb5\x99\
+\xb6\xcd\xcdtwwc\xc3\x8b\xb0\xb6\n\xaa\x80\xfa\x99\x99\x99\x1f\x9d:u\xea{B\
+\x08w\xf1\x97\xe7.\xac\xb9\x83\xa5\xd4s\xa1\x1a9\x97\x15\x89\xcbW.s\xec\xd81\
+\xda]m\x0c\x0f\x0fc\xdb\xf6G\x80\\\x98\x03N \x00\xd4\x02\xfe\xd5\xbe\\\xc4 \
+\x17\xb8\xb4\xearA+\n\xdc\x06\xacU\xcf\xb4?\xdd\xde\xbec\xfb\xf6\x13N\x973l\
+\x99\xe6\x07)c\xe5\xad\xf7\xdeyo\xba\xd4%\xce\t\xb8V\xdbr\x97<Q\x04P\x08\xb4\
+F\xb4U\xd7\x80,\x90\x062\xabc\x00\xfc\x17\x99SG\x06\x87\xbc\x16\xfb\x00\x00\
+\x00\x00IEND\xaeB`\x82\x8e\xd9\x1c{' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_New.py b/jet_tools/JetCreator/img_New.py
new file mode 100755
index 0000000..79a31ee
--- /dev/null
+++ b/jet_tools/JetCreator/img_New.py
@@ -0,0 +1,61 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xf1\x03\x0e\xfc\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \
+\x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08\
+|\x08d\x88\x00\x00\x03\xa8IDATX\x85\xc5\x97\xcdN\x1bW\x14\xc7\x7f\xe7\xce\
+\xf5\x076\xb8@0\t\r\xd9T\x95\xca&\x8bnP\xd9t\xd5M\xb7}\x016\xac\xfa\x10\xddv\
+\xd9\x15\xddU\xea\x0b\xf4\r*e\xd1E%\xd4\xd0(HMhD]\xd2\x00\x01SC\x80\xf8s\xee\
+=]\x8cM\x8d=\xe31\x94*G\x1a\xcd\\\x8d\xe7\x9e\xdf\xf9\x9fs\xee\xf5\x85wl\xf2\
+\x7fL\xaaJ\x8e\xd3\xdc\x02\x19?\x8b\x0f&\x000\xaeA\xc7\xd4\x98n\xed\x8b\xd0N\
+\x04X__\xff\xc2Z\xfb\xbd1f\xca\x18\x831\x06\xef=\xde{\xc20\xc49\x17{\xef=O\
+\xe4\xdf0\xf3\xde\t\x93\xc53\n\x13\r\xac\xedt\xbf5\xad{\xf3\xc7\xdf~\xfa\xc9\
+\xce\x0f\xd0\xfc]JT\x01\xec \x801\xe6\xbb\xb5\xb5\xb5)\x91\x88-\x0cC\xac\xb5\
+\xdd\xc8t R\xbd\xfa\xac\xaf\x11\xfd\r\xd1\x1d\xd0W\xa0U\x84F\xf4N\x8a\xb9G\
+\x8f\xea_"\xd9\nhA\xb5\xf5X\x84\xda\x10\x80snZD\xa8T*\x88\x08\xce9\x8a\xc5"\
+\xedv\x9b\x1e\xd4\xe0=zn\x93\r\xb6\xc8\xdam\xac\xd9!c\xf7\t\xa4\x8aJ7\x03\
+\xd2f\xf1~=\x07\xb9\x87\x00\x9cK]\xb5\xf9\xf3\x10@/*\x11\xb9\xbc\xfa\xc7I\
+\x00\x81\x1ca\x83Clp@&\xd8\'0\x17H\xf0\x11\xc8\x9d\xee/j\xcc\x97\xb7@\xb2K\
+\xe0/\xc0U\xb9(\xbe\x18\x02\xf0\xde\xc7:\x1c\x05\x02`\xed\x19\x81y\x83\x91\
+\x1a\x81T\x91`\td\t1\x8b\xd1o\xd9#\xc8\x9c\x83\x1e?@\xec+\x08\xca\xd0\xb8;\
+\x12`\\%z\x12\x8b41\xd2\x04\x99\x00\x99E\xcc\x03\x08>\xeeN,\x88\xf9\x03\xa4\
+\x94A\xff.!R\xc0\x05S\xb1\x00qi\x88\x93\xbf\x1f )5Q\xa3]-\xde\x7fe\x93N\\\
+\x11\x8et\x9eX\x07\x92G5\x8f\x92G}\x1d1\xb5\xa8\x13\\o\xe6=\xd4\x1f\x83\x9e\
+\x85\x88;G\xb5N\xe0c\xbb`\x08 i|5\xdaY\x94\x19\x949<5\x02\xff2\x8a[\x0e\xbb\
+\xefOp\x9d?\xa1\xe0^\x82;\x00W\xe5\xb4\xf5\xda\x8c\x02\xd8\xdc\xdc\x8c\x05\
+\x88\xbb<wQ\xde\xc7\xeb}\x9c_\xc4\xf9"\xb8mpO\xba\xd76\x87GM\xd0\xf66\xda\
+\xa9 \xfa\\\x16x\x9b\xa8\x00\xc0\xf2\xf2\xf2e\xff\xa7\xa7"\x8f\xe7\x03\x9c6\
+\xa2\x8f\xb5\x882\x07>\x1a\xdbL\x89\xca\xeec\x96>l=E\xdbO\x99lnA\xccJ\xe8\
+\x9cCU\xa3\xa8\xae\xd9\x92J\x19\xa7\x0f\x81\x02\xeag0r\nD\x00\x81L\xf3b\xe7\
+\x19\x9f\x7f\x16\xfe\xc4dsK$\xaa\x8e\x91\n\x0c\xda85\x01s8fPN\xf0\x1c!z\x01\
+\x92\xc5\x9b\x05~\xf9\xf5Gd\xb2\xfe\xa4\x7f\xce\xb1\xbb \xcd\xf9\xd5\xd6\xb3\
+(eT\xcb\xd1PA)Po\x0c\x95\xdcx\x00\xfd\x11\xa6\xb5g\x92\xa9\xeaeJS\x01\x06w\
+\xbd\x9e]wm\x18\xb5{&\x02\x84a\x18\xeb4\r(\x0e`\xd0\xc6V \xceq\xda:pS\x80\
+\xc4\x85\x08`cc\x03c\x86\x0bg\\@cLjm\x0c\xcd\x1e\x86\xe1e\xaeVVVFnLI\x8e\x93\
+\x14\x8a\xb3\x91)\x18\xdc\x9aG)\x11\xbf&\xa4\xdb\xc8\x14\x8c\xe34\xed]\x1aLl\
+\x17$\xb5\xe1M\xc0z\xcbz\x92\xddH\x81\xdb\xb4k\xed\x05iv\x13\xe5n\x04\xd0\
+\xdf\x19i \xaa:\x12l\x10\xa0$"g\xaaZ*\x95J\x88D\xe7\x02k\xed\x7f\xae\x0b\x80\
+l6{\x0e\xdc\x03N\x80\xd6 \xc0\x14P\xde\xdb\xdb\xfbzuu\xf5+\x91\xee\x89\xe2\
+\x96LU\x9b\xbb\xbb\xbb\xdf\x00\x0b@\x00\x1c\x00\xbe_\xc3\x1c0\x0f\xdc\x01\
+\xa6\xbb\xe3\xe0\x96\xfc7\x89\x82}\x0b\xd4\x80\xbf\x80\x0b\x88?\x1d\xe7\x80|\
+\xf7~[\xa7g\x07t\x88do\xd1\xf7?\xfd\x1fi[\xcc\xd5\x90zP\xe4\x00\x00\x00\x00I\
+END\xaeB`\x82\xd34\xc5\xd2' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Open.py b/jet_tools/JetCreator/img_Open.py
new file mode 100755
index 0000000..7a1fe25
--- /dev/null
+++ b/jet_tools/JetCreator/img_Open.py
@@ -0,0 +1,79 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\x96\x05i\xfa\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x05MIDATX\x85\xed\x96Ol\x13\xd9\x1d\xc7?ofl\'v\xb6\x0eN\
+\x1c\xa5E\x02\xad@P$\x96C\xb47\n\xe5\x92Ks\xe1\x88\xb6R9\xf5\xc4\xa9\xf7\x82\
+\x84\xf6\x80\xe8\xb5\x87\xaa\x08\xf5\x86\xca\xa1Zi\x0fU\xa5\xa5\xda\xf6\xd0U\
+\xcb\x8av\xd5\x06*6 \xc7I\x086\x8e\'x<\xf6\x8cg\xe6\xfd\xe9!\xb1\xeb\xb1M \
+\xab\xaa\xbd\xf4;\x1a\xbd\xf7~\xbf7\xef\xfb\xfd\xfd\xde{\xf3\x1e\xfc\x8f!\
+\x0er\xae\xac\xac\xfc\x00\xf8m&\x93\xe9\x8e\xf6\xb5,\x0b\xdb\xb6\xb1m[\xd8\
+\xb6\x8d\xe38\xfd6\x96eaY\x96\rd\x87m\xc3\xe5\xad[\xb7\x04\x80s\xa0:!\xbe\
+\x9c\x9e\x9e\xe6\xfe\xfd\xfb\x05)%\xc6\x18\x00\x8c1\x83\xb7\x8f\xd1\xb6eYd2\
+\x99\xd4x}\xff\xf5\xeb\xd7\x076\x07\xe0\xea\xd5\xab\x7fL\x92\xe4\xfb\x93D\
+\x84a8,(U\xbe\r\xc3\x82\xfaPJ\xa1\xb5V)\x01I\x92\x9c\xbfq\xe3\x06\xadV+\x15\
+\x811\x86\xdb\xb7o\x03\xe0\xba.\x0f\x1e<x\'\xe2I8}\xfa4\xe7\xce\x9d\xc3\xf7}\
+\x84\x10\x9d\x94\x00\xdb\xb6_W\xab\xd5\xf2\xf3\xe7\xcfS\x1f\x05A\xc0\xc9\x93\
+\'\x91R\x02\xb0\xbc\xbc\x0cL\xce\xc4\xa4\xfa\xa8-\x08\x02\xea\xf5:\x96e\xd5G\
+\x05\xb8\xc5b\xb1<77\x97\x12\xd0n\xb7YZZ"\x0cC\xda\xed6\x0f\x1f><t\xe4\x97.]\
+J\t\xa9V\xab\xc4q\xfc\x97\x94\x00\xa0\xd6j\xb5\xbe\xeb\xban\xea\xe3\x8d\x8d\
+\r\xae\\\xb9B\xab\xd5"\x8ec\x96\x96\x96\xde\x1a\xe5h}t\r\xad\xaf\xafw\xc30\
+\xfcSJ\x80Rj;\x9b\xcdR.\x97\xd1Z\x03\x90$\t\x9e\xe7\xb1\xb8\xb8\xc8\xea\xea*\
+/^\xbc\xa0\xd3\x19L\xdd\xa1q\xe6\xcc\x192\x99\x0c\x95JEi\xad\xd3\x02\xb4\xd6\
+\xd5\x9d\x9d\x1d\xe3\xfb\xbe\x88\xa2\x08c\x0c[[[\xac\xac\xac\xd0h4\xe8v\xbb\
+\x94J%J\xa5\xd2\xa1\xe7\xbd_&IB\xadV#\x8a\xa2\xe6\x9d;w\x9e\xa6\x04\x18c\xea\
+\xbd^/Y\\\\\xcc\xfa\xbe\x0f\xc0\xa3G\x8f\xb8v\xed\x1akkkx\x9eG\xb7\xdb}\xe7h\
+3\x99\x0c\xf3\xf3\xf3cBVWW\xc30\x0c\x7f1\xdcw \xc0u\xdd\xc4\xb2\xac\xac\xe7y\
+\xb4\xdbmfgg\x91R\x0e\xb6f>\x9f?\xd4\xea\x0f\x82 eK\x92\x84g\xcf\x9e\xc5B\
+\x88\xbbc\x02\x80W\xbe\xef\xeb\xb3g\xcfb\x8c\xa1R\xa9p\xf9\xf2e\xfe\xf9t\x8d\
+\x9f}\x161\xfe;\xf9\x86\xc8.\x17\x81\xd7\x1f\xfexy\\@\xbb\xdd\x16\xae\xeb\
+\xd2h4\xd8\xdc\xdc\xe4\xe8\xd1\xa3|\xfe\xe7U\x8e-\xce\xf0\x93\x8f\xce\x1f\
+\x9e\xcc\xf4\x8bq\xf9\x06\x88"\xc9O\x7f\xf9\x87-\x07\xc0\xb2\xacW\x9e\xe7\
+\xe5\x16\x16\x16\xd8\xde\xde\xe6\xd4\xa9St:\x1dZ!|\xa7<\x8b\xebG\xacnx\x07\
+\xf1\xa4\x08\xc7m\xe9\xf6\xd1\xb9i^\xbf\xee\x80\xb1>\xb7\x00\xee\xdd\xbb\xd7\
+\x8e\xa2\x08\xd7u\xa9T*\\\xb8p\x81f\xb3\xc9n (\x15\xf3\xb4\x03\x99"4\x801\
+\xa0\xf7\x0f m\x0c\x1a\xb3\xdf\xde\xf3\x19\xdd\xef\xb3\xdf6{\xd2\x946d\x1d\
+\x8b\xaf7\x9b]%\xe5\xef\xad\xfe\xc0B\x88V\xa1P I\x12fff\x10B\xd0\xe8\x18f\
+\xbf\x95\xc7\x0f\xe5D\xc2\x14\xc1(a\xff\x19\x9c\x9c \x95!L$SY\x9b\xb5\x8d\
+\xa6\xb0\x84\xf9bp\x1c[\x96\xb5\xdbl6\xcb\x9dN\x87L&\x83\xd6\x9a\xa6\xaf\x98\
+\xc9gi\xf5\x12t\xff(\x9e\x98\xde\xfe1\xfd\xe6\xe9I\x94&\x91\x9a\xc2\x94\x83\
+\xeb\x85D\xb1\x8c\xbf\xbc\xfb\xa3\xf5\xe1\x0c\xd4\x1c\xc7!\x08\x02\n\x85\x02\
+\xb6\xed\xe0v$\xf9\xa9\x0ca\xac\'\xa6S\x0fE\xb7\x175\x83z\xdf\xa7\x94\xa1\
+\x97(b\xa91@>g\xb3Q\xf3\x10\xb6\xf5\x05\x0c]H\xb4\xd6\xdb\xbb\xbb\xbb(\xa5\
+\x10B\xb0\xd3\xd1\x94\x8f\x14\xe8%\x06\xa5\xf4X\x84\x93\x16\xdfp\x1f\xa5\xcd\
+\xe0\x1dF\xd6\xb1Y\x7f\xe1vu\xac~\x93\x12\xa0\x94Z\x97R\x1a\xc7qD\x14El4c\
+\x16\xe6\xde\xc3\x0f\x93At}B\xdd_\x07z\x7f\xae\x87\xa7c\x90\xa1q\x08\xb1w\
+\xaf\xdb\xac\xb7l\xa9\xad\xcfF3\xf0\xaa\xd5jER\xca\xa9\x9b7o\xd2\x9e\xfb\x1e\
+\x1f\x9e\xbfD\x10)\xa4\xdeK\xa52\x06\xa3\'\x0f\xfe.\xc896\xf5\xdd\x0e\xc6\
+\x98\xdaW\xbf\xfa\xe8eJ\x00P\xeb\xf5z\xc9\xc5\x8b\x17\xa7vvvx\x18|\x9b\xfcT\
+\x16/HH\xf6\xa7`\x14\x93\xae\\\x03\xdf\x04\x9b\xb0`\xf3\xe5\xae4\xda|\xd2\
+\xb7\r\xef\x82\x86\xef\xfb*\x97\xcbQ\xf7\x14\xf6\xb4C\x8cE\x10\xff\xfb\x1f\
+\x80\x99<\xf0\x81\xc4\xfb\x06\x01\xc4\x89a\xe3e\xb3\'5\xbf\x1e\x13 \xa5|\xe5\
+\xfb\xbeS.\x97\xa9\x89\xf7\x99/\xcf\xe3\xf7$Z\x8dl\xbf7P\x9aqS\nB\x08Z\xcd6I\
+\xac[\x7f\xbb\xfb\xc3\xbf\x8e\tx\xfc\xf8qx\xe2\xc4\x89\xdcW\xf5)B{\x8ecG\x8a\
+\xf4b5>\xd2\x9bx&\x90\xa6\xfd\x86j\xb5\x16\x06\xbb[?\x07\xde\x03\xfc\x94\x80\
+\'O\x9eD\xc7\x8f\x1f\x7f\xda0\xc7>@\x1a\x9e\xfc\xfd\xeb\x03\xc9\x0f\x03\x03\
+\xca\x08\x81\x8a:\xffX\xfb\xf4\xe3\xdf\x01\xd9\xbeo\xf8\x82_\xcc\xe5r\xe5bi\
+\xe1}\x91?R\x92I\x92\x1d\x1f\xea\xf0\xd0\xda\xc8^";Bi\x154+u\xa0\tl\x03jT\
+\x00@\x0e(\x02\xd3@\x86\xff\x1c$\x90\x00\x1e\xd0\xe5\xedk\xf9\xff\xf8\xef\
+\xe1_\x86IG\x92\xf3G\x04=\x00\x00\x00\x00IEND\xaeB`\x82\x8f\x9d\xc6\xf3' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Paste.py b/jet_tools/JetCreator/img_Paste.py
new file mode 100755
index 0000000..842c4a7
--- /dev/null
+++ b/jet_tools/JetCreator/img_Paste.py
@@ -0,0 +1,41 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xdd\x01"\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x01\x94IDATX\x85\xed\x97\xbdN\xc30\x14\x85OJ\x06gK6\x18;\
+\xa6\x1bk^\xa1\x8fS\xb5\x08\x18\x10\x03c\x07\x84X\x902\xb6o\x90\x951\xdd\xca\
+\xd6\x8c\x1e\x93\xcd\x06\x06\xdf\xad\x0c\xb4\x15HIs\x9dDD\x958[,\xfb\x9e/\
+\xbe\xd7\x7f\x8e38C\x9fr\x01\xe0\xe9\xe5qKZw\x1cZ`:\xbbrX\x00\xa45&\x93\xdbN\
+\xed\xe7\xf3\x07V?\xf7\xe7\x87\xeeh\x16|\xdfg\xf7\x1dt\xe2\xd8B\xff\x00nYc\
+\x96e\x8d\x82\r\x87C\x08!\xda\x03\x84a\xd8\x08\xa0\x89N#\x05i\x9aB)\xb2\x0e~\
+s}\xbf\x05\x80 \x00\xa6\xb3\xbb\xd2M\x89\x95\x82$y\x85\x94\xd6\xfe,\x9dF\n\
+\xfe\x1c\xa0,\x05{\x8dF\xe7\x10\xc2\xab\rLd\xb0\xd9\x14\xcd\x00\x8e\xc9\x18\
+\x821\x9c\x9e\xbc\xa2\xb5N\x81\xd6\xc4\nN\xccUS\x99\x02\xa2\xf2\x00ax\xc1\
+\xda\xedV+\t\x98z\x88R\x80*s\x00H\xd3n\xd7\xe3/\x80c\xc6{\x8d\xc7\x97\xa8\
+\x9b\x80\xf5ZBJ\xde\xdd\xc2\xba\x08\x93\xe4\xcdv\x08\x0f\x80\xf3\xf7\x00\x10\
+E\xbc\x13\x8f\x88X\xe9\xb2\x9e\x81,\xcbm\x87t\x0b\xd0\xe4Pb\x01\x10\x91\xf5e\
+\xa2L\x9e\x07\xf8>v\x85*vm\xd5\xd0;\x00\x818~nm\x0e|\x1bGQx\xb8\x19\x17E\x81\
+<\xaf\xae\x05\x17@\xed\x03b\x7f\xae\xf3!\xc4\xc1\\)\x05)\xab\xeb\xc6\xba\x06\
+8\xd2Z\xc3\x18\x03M\x1aRJ,\x16\x8bv\x00A\xc07\xf7< \xcfs(RX\xc6K(\xa5\xf0\
+\xfe\xf1Y9\xc3N\xdf\x8f\xd3\xdeoD\xbd\x03|\x01\xd0i\x97\xf5j]\xc4\xe0\x00\
+\x00\x00\x00IEND\xaeB`\x82q\x05\xc6\xef' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Print.py b/jet_tools/JetCreator/img_Print.py
new file mode 100755
index 0000000..cf2e00d
--- /dev/null
+++ b/jet_tools/JetCreator/img_Print.py
@@ -0,0 +1,62 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xf5\x03\n\xfc\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x03\xacIDATX\x85\xe5\x97\xcfK+W\x14\xc7?\x93d\x921&5\xc4TD\
+\x9fZ4\xb8x\xbc\x85F\xe9?P\xca\xdb\n\xee\xdd\xd5TT\xac\xbb\x16\xe9+B\x97>\
+\xa5.Z(B\xb7.\xba.v\xe9>\x1b\x17\x82P\x8a\xf8\xe3\x11SS\x13\'\xc6\x9a\x99;s\
+\xbbp2L\x12\x7f\xcc3\xb6]\xf4\x0b\x87\xdc{\xee\x99s\xbe|\xcf\xbdw&\xf0\x7f\
+\x87r\xdf\xc2\xc6\xc6\xc6\x0f\x86a\xcc\x02\x816k\xd8\xe1p\xf8\xc7\xe5\xe5\
+\xe5\xb9\xbb\x16C\xf7=e\x9a\xe6\xec\xd2\xd2R\xbb\xc5\x01\x02\x9b\x9b\x9b\xb3\
+\xc0\xfb\x11\x90R\x06\x00\xce\xce\xce\xda\xaa\xde\xdb\xdb\xeb\xe6z/\x02^\x9c\
+\x9c\x9c\xdc\xe9\x17BP*\x95H\xa5R(Jc7\x07\x06\x06|\x11|\x94\x80\xaa\xaa\x0c\
+\x0f\x0f\xdfY|oo\x8fZ\xad\x86\x10\x82\xd1\xd1Q_\x05\x9fD\xe0\xf8\xf8\x18\x00\
+)\xa5\xeb\xcf\xe7\xf3T\xabU\x00\n\x85\x02\x00\xf1x\xdc]\x1f\x1c\x1c|\x1e\x02\
+\xa1P\xa8E\x81\x83\x83\x03\xb7x\x1d\xe7\xe7\xe7\xa4\xd3ib\xb1\x98\xeb\xf3\
+\x12~2\x01UU9::r\xe7\xa5R\x89\xd3\xd3\xd3\x968\xdb\xb6\xc9\xe5r\x8c\x8c\x8c\
+\x10\x08\x04\x18\x1a\x1az\xb4\xb8o\x02\xe9t\x1a\x80b\xb1\xc8\xfe\xfe\xfe\xbd\
+\xb1\xb5Z\r]\xd7\x99\x9c\x9c\xf4U\xdc7\x81\xc3\xc3Cw\x9e\xc9d\x00\xc8\xe5r-\
+\xb1SSS\xa8\xaa\xda\xe0\x13B\xb4G@\xd7uR\xa9\x94;o>n^\xdc\xdc\xdc \x84h\x88\
+\t\x85\x1e.\xf1\xe0\xaaa\x18\xeeE\xe4M\xaa(\x8a\xab\x84\xd7_,\x16[\x88\xf6\
+\xf4\xf4\xf8\'\x90\xcdf?\x0e\x06\x83\xbfX\x96\xd5\rpqqqgR/\x9a}\xcd\xf3\xba\
+\x02\xd9lV\x86\x82\xc1B\xcd0>\xd9\xda\xdar7\x92\x1b\xbd\xb8\xb8\x18\t\x06\
+\x83\xbf\xaf\xac\xac\xf4\'\x12\t\xd6\xd7\xd7\x19\x1b\x1b\xa3R\xa9\xb4$\xbe\
+\xebx\xddG\xa4\xb3\xb3\x93\\.\xc7\xc2\xc2\x02\xe5r\x99\xb5\xb5\xb5\xdf\x92\
+\xc9\xe4\xcb\xd5\xd5U\xd1\xac\xc0W333=\x89D\x02\x80d2\xe9^0\xed\xe0\xea\xea\
+\x8ax<\xce\xf5\xf55\xb1X\x8c\xe9\xe9\xe9\x8f\xb6\xb7\xb7\xbf\x04\xbem\x08\
+\x9c\x9f\x9f/Y\x96%m\xdb\xf6e\x96e=\xc9\x84\x10rn\xee\xf3\xabz]W\x01!DBJI>\
+\xff\x0eUU9=}\x87\xa6i$\x93I\xa4\x94\xdc\xaa.AJl)\x01\x89\xb4Ar\xdb\x0ey;\
+\xb9]\x93\xcd~\x89\xaeW0\x85\xc9\xf8\xf8\x04\xb6-;[\x08\x00X\x96\xc5\xe5\xe5\
+%\x03/\x06\xb1,\x8b\xae\x0f\xe2D;\xa2\x9e\x88zr\x1a}@\xe3\xb6h\x8e\x93\x9c\
+\x15\xfe\xe0\xe6\xafk\x8cZ\xad\xe1\xe9\x86\xf7\xb4eY\xec\xec\xfc\x8a\x10&/\
+\xfa\xfb\xd14\x8dpX\xf5X\x98p$L\xa4\xc1"D"\x114\xcdk\x1aZ\x87F\x87c\x00\xbb\
+\xbb\xbb\x00\x18\xa6\xd1@\xa0E\x81\xbe\xbe>\xbe~\xf3\r\xa6i6\x04\xd6\xf7\xf8\
+\xe3\xaf\x97V\xa8\xaa\xca\xabW/o\t\x18\x0f\x10\xb0m\x8b\x89\x89\x0c\x99\xcc8\
+RJl\xdbv\xfa\xef\x19\xdb6\xb6\xe3\x93\x8e\xcf\xf63v~\xcd&\x02--\xf8\xa7a4)\
+\xfb\xef\x13\xf8\xaf\x15hnA}\x0fD\x02\x81\x80Q\xa9\xe8\xe1h4\xea\xe9\xbbs\
+\xde\xbdc\xdb{\x0f8{\xc1\xcfX\xdaH\t\xba~\x89\xa2(\x16\x90\x04\xca!G\x85\xae\
+j\xb5\xfa\xfd\xdb\xb7\x1b_\xf8\xf9\x8cj\x07\x8a\xa2P.\x97\x7fr\x08H\xc5Q\xa1\
+\x1bH\xbd~\xfd\xe9|w\xf7\x87\x9fI)}}\xae?\x01V\xa9T\xfaygg\xe7;\xe0O\xa0P?\
+\xde\x9a\xc3\xa8\x0b\x88:\xa4\x9e\xe3_\x91\x17\xa6\x93\xd3\x04\xae\x80s\xe0\
+\xb2\xf9\x05\xaf\x02a \xf2\xcc\xc5\x01,\xc7L\xc0\xe0iw\xda\xf3\xe3o\xfe\xfdT\
+R\xd6\x04\xb1Y\x00\x00\x00\x00IEND\xaeB`\x829\x9b\xe7\xcb' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Redo.py b/jet_tools/JetCreator/img_Redo.py
new file mode 100755
index 0000000..10624f4
--- /dev/null
+++ b/jet_tools/JetCreator/img_Redo.py
@@ -0,0 +1,79 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xb6\x05I\xfa\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x05mIDATX\x85\xd5\x95Yl\x94U\x14\xc7\xff\xf7~\xcb,\xdd\xe8\
+\x12JK+\xb4bE\x8a .mR7\x12\x16\x83\x0eBb\x9a\x10\x83\xf1\x05\xa1\xf0\xe2\x93\
+>\xca\x83/\x9a\xa8\xf1\xc1\x18[\x9e \x06\x03\x06\x0cK\x13pCQD\xa0-\x96\xb6`)\
+HK;\xa5e:\xcct\xda\x99\xf9\x96\xbb\xf8\xd0\xce\xf81|C\x01\x93\x1a\xcf\xe4\
+\xe4\xdc;\xdf\xbd\xf7\xff\xbb\xe7\x9c\x99\x0f\xf8\xbf\xdb\xfa\x16\xed\xd3\
+\x9d;A\x1ft\xff\x03o\xfc\xc7\xc8\x8e\xceJ\xedx\xe3\xc7\xf0\xfd\'\x00\x04 K\
+\xab\x1a^\xb0\x0b\xf43\x81f\x94\xcc:\x00\x00\xd4-^\xa5\xd7\xd7\xae^\xa2P\xad\
+sc\xb3\xe7\xe1Y\x07\xa0D\xc1\xe2\xca\'\x94UOn\x9c\'(\xda\x02\xcd\xea3\xb3\
+\x06@@@\xa1"l\x0f\xa0\xbct!\r4l\x9a\xa3Q\xfd\xc4\xfa\x16=0;\x00\x84\x80\x12\
+\n\x02\x8a\x885\x88\x82\xfcBlx\xf1\x8d\x1c\xaf\xee\xd9\xf7j\x8b\xda4\xf3\x05\
+\xee\xc3\x02\xcd\xf0\x03\xfaZ\xaa`\x13\x01\x9e\x86\xc4\\\n5\xaf)\xf0>\x86\
+\xcd.pi\x83K\x0b9J1\x08S\xd1z\xfa\xabx<\x19\xfb\xfc\xd0[\xf6\xbb\xff\x1a`\
+\xc3.\xcfJ\x10\xf9uy\xd1C\x9e\x9a\x05+rK\xf2KI\xae\xa7\x10~=\x1f\x94\xa8\x18\
+Nv\x81I\x1bB\xda`\xd2\x86\x97\xe6B\x85\x1f\xdf\x9d=\x10\x8f\xc6\xa2\xad\xf3\
+\x84\xb5\xb9e\x1b\xec\xfb\x06h\xdc\x0f\xdd\x1c\xd7>*\xf4\x95mY[\xf7\x9aoA\
+\xd1\xe3H\xb2(L\x19\x87-\x0c\xd8"\tK&\xc1\xa5\x05.\x19\xb8\xb4`\xf0\t\x0c\
+\x1b\xddP\x89\x17\xa5\xfa\xa3\xb8\xd0\xf3Gbt\xec\xc6y\tk\xdd\xe1-\x98\xb8g\
+\x80\x97?\xf3\xcf\xd3\xbc\xf6/\xf5\x8f\xac\xablX\xfc\x8a\x87\xc3DR\x8cCH\x06\
+&\xadtL\xa5>\x05\xd0\x9fhC\x92G\x01\x00\x1e\x9a\x8b\xf9\xdee\xb8\xd8\xd7c\
+\x0e\r\xf5\xf7\x9b\t\xbb\xee\xd8\xdb\x88\xa54\xd4\xbb\x01\xe8>\xbb\xa5n\xf1\
+\x9a\xaa\xe7j\x02\xca\x04\x0b\x81b\xaa\xd9\xa6\xc8I:\x12\xc77\x13,\x94\x16\
+\x07\x80\x1c\xb5\x18\xb6\xcd\x10\x1e\x0b1\x10\x1ct\x8a\xdf\x15 \xf0\x85g\xd5\
+\xfc95k\xea\x17\xadS\xa2v\x10\x00 \x89\n@\xa6?H\xcfR#\xe0\xa6\xd9\x97>\xa3D\
+\xaf\x86\x87\x17\xe2\xd4\xb9\x9f\x13\xa6e\xbewx\xab\xfdI\xa6\x8e+@\xe3~\xe8\
+\xd6\xb8\xd8\xbdzE\xa37\xce\xc3`\xd2\x04\x85\n\t\t"\t\xbcJ\x1e\x14\xa29v\x10\
+\x04\x8d\x0b\x88\xd9\xa3\xb0D\x02\x00P\xea\xa9\x011\xfd8\xd5~2aY\xd6\x8e#\
+\xdb\xec\xddnZ\xae\x00fD\xdb\\]Q[\x94\x93\x93\x83\x885\x08JTH\x00\xf9j)4\xe9\
+C\xdfH\xa7\x0c\x86{\x93c\xb1\x90\x88L\x84\xfdo\xae}\x87J)\x10\xb2\xae\x02\
+\x00\xca\xbcK\xc0\xe2\x14g:~M0\xce^?\xb2\xcd>\x94-\xd3\xae\x00\x92\xa2\xb6\
+\xba\xb4\xd6;\xc9\xc6 $\x03\x01E\xbe^\x8a\xe1[W\xf1]\xdb\xfed\xcc\x88\xee\
+\x85\x94\' d/\xa1\xda\xefR\n\x8c\x98\xbd0\xf88\xca\xbdK\x91\x8cq\xb4\x9d\xff\
+-.!\x03G\xb6Z?e\x13\xcf\n@\x15\xfaXqn\x19\xb1D\x12\\2\xcc\xd1\xe6\xe3\xcap7?\
+\xd6\xb6g\x84s\xd9x\xb4\x89\x9dN\xad\xdd\xb8K\x95\xa3F\x1f\xc2\xe65T\xf8\x96\
+#:6):z\xda\'\xc0\xf9\xea\xc3M\xac\xedn\xe2Y\x01\x88D\xb5\xa6i0\xc5\x04T\xaa#\
+i%p\xacm\xaf\xc5\x98Z\xdf\xba=\x19t\xae\x15R \xce\xc3\xa8\xf0-Gh$\xcc\xbbz;#\
+\x82\xe3\xf9\xa3M\xec\xcf\x99\xc4\xb3\x02\x08!K\x18IBH\x0e\x0f\xc9A\xe7\x95\
+\xd3I)\xf9\x87\xad\xdb\xad\xa0\xdbz&\r\\\x1f\x18d}\xfd\x97G\x04W\x9e=\xba\
+\xcd\xb8~/\xe2@\x96\x97\x91B\xd0\x1dI\xdc\xc4\xd4OK\xe0\xdaH\xaf$\x9c\xb8v1\
+\x00\\\xea\xeb\xb1\xfa\xfa/\xff\xa5X\xfaS\xf7#\x9e\x15\xc0c\xc8=\xe1\xc8\x80\
+M@`K\x0b\xb6\x95`0\xcc\xe8\xf4z\x05S\x99K9\x82\xc1\xa1\xee\xf8\x15\xbb\xe1\
+\x9b\x1d\xf1[\xd3\xcf\x95\xe9\xb53\xfe\xd5\xbb\x03\xd8\xf6\xc1\xe1\xa1\x9e\
+\x0e.\x18,\x91@\x91\xd7\x17\xb9\xd9\x0b\x1d\xb8\xc3=\x9c\x89\xd6K_Z/\xfd\xf0\
+\x01\x8c\x8cg\x9a\xc3\xd5l@\x8a\x1bT\xe7q\xd8+V\x8a\x11\xb5P\xd9\xe0\xf3\xe7\
+k\x8c\xc6\x153v\xf3\xc2\xc5\xef\xe5\xb5\x8c\xdb+7:\xc4\xd1\x1b\xdd\xe0\x8e\
+\x9b\xa7n\x9f\x99\x89T\xbc\r\xc2\t@\x9c\x87\x04;\xf8@e]<\xe4/*h\xc8\xc9\x9d\
+\xa3NN\x06\xcd\xeb\xed\xf6\xc9\xc48\x88\x13\xc0\x9aL\x97$\x13\xc0)\xe8\xe6\
+\x00 \x9d\x00\xd4\xb99\x1e\x07=w\xc0\xe8Z\xb0f\xfcJ^Aq\xb9?//\xaf\xb04::\xd8\
+\xcdB\xc6d\x1aVs\x11\x9eI\xd4ii\x00g\x8a\x9c\xf4\xb4}_\xf2\xaa\xaf"r\xaar\
+\xd1\xc2\x12_\xb9\xaf\xcaW1yk\xe0\x9c\x1de\xc6m\r\xe9\x9a^`\xfa=\xe5>\x96\
+\x99\x19\xc8FL.\xffhLt}\xdb\x7f.\xb7\\\x1d*\xa9*\xa8([F\xd5\xe8\xa8\x88\xc5G\
+\xb9\r@8\x9cO\x1f\x9c\x9aK\xc7\\f\xb8\x00 2\x89o+Cf6\x00P\xe4\x82V.\xf3\xe9s\
++\x85\x8f\xeaR\t\rQ\xb3\xff\xaca ~\xc7\xe1\x99 NP6\x1d]\xeb\xe2,Cf\x07g\xa6\
+\xda-\xe5\xae7\x9d\x1e\xf3i\xbfM\xecn\x96\x12\xcb\xecjdD\xd7\xfa\xe2\xceR\
+\xb8\n<\x88\x91\x8cq\xb6f\x9b\xd1\xfe\x06%@\xa0\xd7\xa5<\xcc\xbe\x00\x00\x00\
+\x00IEND\xaeB`\x82\xe2\xa8\xb4\xdb' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Save.py b/jet_tools/JetCreator/img_Save.py
new file mode 100755
index 0000000..9e19146
--- /dev/null
+++ b/jet_tools/JetCreator/img_Save.py
@@ -0,0 +1,99 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xb3\x07L\xf8\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x07jIDATX\x85\xc5\x97}l\x14\xc7\x15\xc0\x7f3\xbb\xf7\xe1\
+\xb3\xcf\x17\x1f\x0e&\xfe\xc06N\x1cc\x02D\xb81\xf9\xc0\xd0\xf0i\xd2\x98P\x82\
+\xd34*\x89PR\xaa\xf4\x9fTj\xaaP%-j\x155\x91h*\xf5\x8fV*\x8d\xfa\x0fU\xd5D\
+\xa5Q\x9aF!T\x90\x94\xb4I\xaa\x94&\x18T\x01&\xe5\xcb\x18\xf7(\xf6\xf9|\xbb\
+\xbe\xdb\xdd\x99\xe9\x1f\xe6\xb6\xb6\x81\xd0(T\x1d\xe9ig\xe6\xcd\xce\xfb\xed\
+{o\xdf\xec\xc2\xff\xb9\x89\xab-h\xfe\xc2\xf3\x9d\x1a\xf1\x15)X\n"j\xc0A\xe9w\
+\x8c\xe1\xe5S\xbb\xb7\xbe\xff?\x03\xa8_\xfdB\xda\xb2\xbd_U\x94\xc7\xef\\8\
+\xaf\xa9\xbcqV\x95\xbc\xae\xb2\x8c P\x9c\x19\x1a\xd1\x07\xfaN\x142Y\xe7#e\
+\xfc\x07\x06^\x7f\xe6\xec5\x05\xa8\xeb~\xbe>"\xcd\xfe\x8e[\x1as\x8b\x17\xb6T\
+\t)gGl\x0bK\x82%%R\n\x84\x80CG\xcf\xaa7\xf6\x1f\xca\x17\x03\xd35\xf0\xc6\xd6\
+C\xd7\x04\xa0\xbdw[4\x9f\x8f\xbf\xdd\xb5h\xce9\x84\xac?\x9b\x19\x9de\xdbve\
+\xc3\r\xe9\xca\x85mu\xd2\x92\x92\xa2\xa7\xd0\xc6\x90\x88\xd9\x0c\xfd+\xc7/_{\
+?\xa3}\xff\xd6\xd3o>s\xee\xd3\x02X\xd3\'\x12\x8dk\x9e\x98Q\x95\xb8k\xf0|\xae\
+\xed\xf8\x99\xcc\xfe\x91Q\xf7\xd9\x91\x9c\xf3\xca\xc9\xb3\x99\xb2#\x1f\x9fk\
+\xbe\xa9\xa9&bG,\x8a\x9e"_\xf0I\xa7\x12TT\xc4\xcbN\x0c\x8c\xcc\xc9\xf6\xff\
+\xe1\xe5\xcf\xe4\x81\xfa\xde\x17\xcad\xbe\xb8\xcf\x92\xf2\x96\x00u\xcf\x99\
+\xd7\x9f~g\xb2\xbe\xb1\xfb\xf9\x07gT\'~\xfe\xf0}wU\x8c\x8c\x15\t\x94\xc6\x18\
+CM\xba\x9c_\xfcf\x7f\xee\xc2ha\xc9\xa7\r\x85\x9c<\xb0r\xc5[\x81\xd3\x81V\x1b\
+\xa6\x1b\x078\xb5{\xeb\xaf\x87\x87\xc7\x7fw\xecDF\xd9\x96\xc4\x0f4E_\xe3\x8c\
+\xfb,jo.\x97\xe8\xcd\x9f\xc68L\xf3\xc0\xec\xb5\xcf\xdeo\x0cm\xb7\'\x0f\xbe\
+\xa4\xb5\xfeY$\x12\xf9\xbcm\xdbS G\xfd\x04\xe3\xd7-b\xd5\xd2\x0e\x86\x86\xf3\
+\x04j"\x17\xe2Q\xc9K\xaf\xbe\xc5\xe7*\x8fb\x8c\t\xd7+\xa5\x94\xe7y\xbb\\\xd7\
+}x\xf7\xee\xdd\xc5\xe9\x00\xf6\xe4\x816\xf2\xb4T\xeaoJ\xa9\x9fl\xda\xb4iyOO\
+\x0fR\xcapCc\x0cy\xb7\xc8\xdd[^DJ\xf0\x02\x8d\xd6\x86\xb1q\x9f\x9at\ni\'\xd8\
+\xb1c\x07\xc6\x98\xc9b\xed\xdc\xb9\xf3\x81\xbd{\xf7\xfe\x15\xd8\xfe\x89\x00\
+\x9egNw\xdexfL\\\x10\x8bW\xaf^\xcdko\xf7\xf1\x8d\x1f\xbdy\x89\xdbl["\x84\xc0\
+\xf3\x15Z\x1b\xb41\xc4"\x16N\xc1c\xee\xc6\x1fOY\xbb~Y+O\xf4\xaee\xdf\xbe}\
+\x8f\x01?\x04\xcc\x94\xbd&\x0f2\xfb\x9e\xfeg\xac\xea\xfe/\xaf\\\xb92mY\x16\
+\x9dsk\xf8\xce\xa3Kx\xf1\xd5>\x96\xdf5\x9f\xb2x\x14K\nlK"\x00\xcf\xd7hc\xd0\
+\xda`\x04l\xdd\xb2\x16)\x05\xc6\xc0_\x0e\x9e\xe0\xdc\xd0y\xbe\xfe\xc5\x05\
+\xa4R)\xe6\xcd\x9b\xd7\n,\xdf\xb5k\xd7\xde+\x02\\t\xf3\xa3\xdd\xdd\xdd\xe4\
+\xf3y|\xdfgMg\x13\x99a\x97\xdf\xbf\xfbw\xee\xbec\x1e\x99\xec8n!\xc0`0f",n1\
+\xe0\x95?\x1e\xc7-\x06t\xb4\xd6\x10x\x05\x0e\x1d9\xc9O\xbf\xb9\x1c\xad\x02\
+\x1c\xc7a\xd5\xaaU\x1c>|\xf8k\xc0\x14\x80)\t\xb6a\xc3\x869555\xcb\xeb\xea\
+\xeap\x1c\x07\xdb\xb6\xb1m\x9b\xcd\xf7.`Qk5\xef}x\x94\x99Ue(c\xc8\xe6\x8b\
+\x0c]p\xf8\xc7\xe0(g\xcf\xe7\xc9\xe6\x8b\xb4\xd4\xa6\x88\xdb\xf0\xe7\x03Gy\
+\xee\xf1\xa5\xa4S\t\xa4\x94\x8c\x8f\x8f\xd3\xda\xdaJ2\x99\xbc\xaf\xa7\xa7\
+\xa7\xe6\x8a\x00B\x88\xc76n\xdc(\\\xd7-\x8d\xd1Z\xa3\x94\xe2\xc9\x87:\x99\
+\x95\x8a\xf1\xde\x87\xc7I\'\xe3d\xc7\x8a\xe4\\\x0f\xa5\'BZ[]A\xf3\r\x95\xec}\
+\xf70\xdfz\xa8\x83\xe6\x1b*1\xc6 \x84\xc0\x18\x83\xef\xfb\xacX\xb1"\x1a\x89D\
+6_\x16`\xd9\xb2e\xb6\x10\xe2\x91\x8e\x8e\x0e\xf2\xf9<\xb6mc\x8cA)E\x10\x04\
+\xa8\xc0\xe7\xbb\x9b\x17\x13\xc5\xe7\xe3\x93g\xb9m\xee,\xa2\x91\x89BZY\x1e\
+\xe5\xb6\xb6\x1a\xdez\xf70=w6\xd2\xd96\x13\xa5T\xf8\xf6H)q]\x97\xce\xceN\x80\
+-\xdb\xb6m\x93\x97\x00\xa4\xd3\xe9{\xba\xba\xbaj#\x91\x08\x85B\x01)%A\x10\
+\xe0y\x1e\xc5b\x91b\xb1\x88V\x01\xcf>v;\xb9\xec(\x83C\x17X<w\x16\x89x\x84\
+\xae\x05\xf5\xbc\xffQ?\xf5\xd516t5\x86\xeb=\xcf#\x08\x02l\xdb\xc6\xf3<\x92\
+\xc9$\xf3\xe7\xcfo\xee\xeb\xeb[}\t\x80\x10\xe2\xabk\xd6\xac!\x97\xcb\x01\x10\
+\x04\x01\xc6\x98p\x13c\x0cZk"\x96\xe1{\x9b;8\xf6\xf1i\x86\xb39\xee\xbd\xa3\
+\x99\xfe\x13\xe7\xc8eGy\xbc\xa7\x15\xcf\xf3\xf0}\x1f\xdf\xf7\t\x82\x00)%J)\
+\xb4\xd6\xb8\xae\xcb\xd2\xa5K\x91Rn\x99\x02\xb0n\xdd\xba\xda\xea\xea\xea\xee\
+\x86\x86\x06\x1c\xc7A)E>\x9f\x07\xa0\xac\xac,,FJ)\x94RT\x96I\xb6mZ\xc8\x81\
+\x83\xfd\xfc\xe9\xc0q\xfa\x8e\x9c\xe4\xc9\xde6,a\x08\x82\x00\xad5\x00\xf1x\
+\x1c!\x04\xb9\\\x0e\xad5\x85B\x81\x96\x96\x16*++{z{{\xebB\x00\xdb\xb6\x1fY\
+\xbf~\xbd\xed\xba.B\x88P\xc6\xc7\xc7\xf1}\x9f\xf2\xf2r\x12\x89\x04\xd1h\x14!\
+\x04\x96eQ[]\xceS\x0f\xdeB\xdf\x91S<\xf5\xa5y\xcc\xac*\'\x1a\x8d\x92H$H&\x93\
+$\x93I<\xcf\xc3q\x1c\xa4\x94H)\x89D"(\xa5X\xb2d\x89\xad\x94z\x14.\xd6\x01!\
+\xc4C\x1d\x1d\x1d\xb8\xaeKmm-\xd9l\x96\xb1\xb11\x84\x10\x04A\x10\xf6-\xcb"\
+\x99L\x96\xea\x05\xb7\xa7\xd3\xfc\xf6\x07\rT\x94\xd9H)\xd1Z\xa3\xb5\x9e\xc8\
+\x17\xad\xc3{\xa4\x94\xa4R)\xaa\xab\xab\x19\x18\x18\xa0\xb3\xb3\x93={\xf6l\
+\x02\xbe_*D7%\x93I\xc6\xc6\xc6\x18\x1c\x1cDk\x8d\x94\x13\xe9aYVhPk\x1d\xe6Ci\
+\x1c\xb3\xc1\xf7\xfd)\x9e\x8b\xc5b\x08\xf1\x9fs\xce\x18C>\x9f\xc7q\x1c\xb4\
+\xd6\\\x7f\xfd\xf5h\xado\x0c=\x00\xc4<\xcf\xc3\xb6\xed\xf0\xc6\xc9\x07P\xe9Z\
+\x82\xba\x92~\xb2\xae$\x93u\xa5\xdc\x18\x1a\x1a\n\xfba)\x0e\x82\x80c\xc7\x8e\
+]\xb2\x81\xd6z\xfa\xe9v\xd9\xb9\xffv-@mmm\x08\x1b>R\x89\x08\xe0\x83\x0f>\xa0\
+\xaf\xaf\x8f\xfe\xfe~\x06\x06\x06\xc8d2d\xb3Y\x1c\xc7\xa1X,\x86E\xa6T\xe5\
+\x8c1\x0c\x0f\x0f\xd3\xd0\xd0@6\x9b\xa5\xa1\xa1\x81\x91\x91\x11f\xcf\x9eM6\
+\x9b\xa5\xa9\xa9)\xbcN\xb7\x15\x02LvW)\xeeWj\x97s\xfbt\xdd\x95\xc2\x01\xa0\
+\x94\xfad\x0f\xc4b\xb1)\xf1\xfe$\x90\xe9\x9bO\xd7]\xaeM\xb6\x15\xe6@\x89\xca\
+\x18C{{;J\xa9\xb0\x84\x96\\\xae\x94B\x081\xa5\xce_\rf\xfa\\\xe9\xd56\xc6\xe8\
+\x10\xc0\x18s\xf2\xd4\xa9SM7\xdf|s\x988\xa5\xe4)\xf5\xaf&uuuh\xadioo\xc7\xf3\
+<\xda\xda\xda(\x14\n\xb4\xb6\xb6\xe28\x0e---\xe4r9R\xa9\x14\x83\x83\x83\x18c\
+\x8e\x02q\x1b\xc0u\xddoo\xdf\xbe}\xbbeY\xf5W\xf5\xfb5hA\x10\x0cd2\x99\xe7\
+\x80h\xa9Z\xc4\x81\x19@\x15P\tD\x81\xc85\xb6\xeb1\xf1#T\x00r\xc0y\xe0\xfc\
+\xe4\xcfr\x01\x94_\x948\x97\xf9k\xfa\x8c-\xb8(\x05 \x7f\x11\x88\x7f\x03\xca\
+\x8e`}\xd3\xc8\x9b>\x00\x00\x00\x00IEND\xaeB`\x82\xfca\xccs' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_Undo.py b/jet_tools/JetCreator/img_Undo.py
new file mode 100755
index 0000000..d5339a4
--- /dev/null
+++ b/jet_tools/JetCreator/img_Undo.py
@@ -0,0 +1,81 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xf3\x05\x0c\xfa\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \
+\x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08\
+|\x08d\x88\x00\x00\x05\xaaIDATX\x85\xd5\x97klT\xc7\x15\xc7\x7fs\xef\xdd\xf5\
+\xda^\xdb\x18;\x88\xf2\x067\x01\x1a\'$\xc4T\x8aM)\xc1\xc9\x07\xac\xa8\xe9\
+\x87\x88$R\xe2\x00!\tAj\x91"%RZ\xa2\xaa\x8f\xa8\xfd\x10\xa9B\xfeDKx\xb6Q\xde\
+o\xa1\xa0\xe6\x81\x8c\r\x08\xec\x90\xd4\xe088\x10\xe3\x801v\xec]\xaf\xf7u\
+\x1f3\xd3\x0f\xf6\xdd\x98\xcdbl\x17U\xea\x91\x8ef\xee\xee\x99\xf9\xff\xee\
+\xcc9s\xef\x85\xffW\xd3\x1a\xd1\xb4\x9b?\xb6\xec\xa0\xe4\xbf\x99GLe\xd0\xb1\
+\xfd\x14+\xcd[JQ\xebjJ\xef\xda@t\xaa\x00\xc6d\x074\xee\xe5&\xa5E\xdb\x9c\x9b\
+\x1fX%\x0c\xcb\x99\xaa\xf0\x94\x00\x9avQgY\x85\x9f\xddX\xfd\x9b\xb93\x16\xfd\
+"\x80@\xff\xcf\x00\x9a\xf7\xf2|h\xda\x827+k_,,,\x0e\x8b)\xee\xde\x0f\xcc\xba\
+V\xc0\xc1}\x14\x16\xc1+\xa5\xb3\xd6\xdc=\x7f\xf9\x93!\xed\xb6#\xed\xaf1\x82\
+\x0b\xb9\x1e\x10\xe3\x02\x1c\xdd\xcd\x02%\x8c\x7f\xcd[\xb6e~\xf9\xbc\xda\x80\
+L\xb7\xa0e\x1f\x08\xeb\xba\x88\x8f\x0b\xd0\xbc\x8b5\xe4\x85\xdf^Z\xfd\xa7\
+\xa2\x82\xd2yB\xa6Z\xd1*\x0e\x98\x8c\xec\x9c@ \xcc\x80A\xcf\x91}D\x11t)\xc5\
+\x1bBq\xa0f\x03\x1d\x13\x05\xc8y\x1bM{y\xba\xa0\xf8\xc7/\xdcT\xf3\x87\x90\
+\x15\x90H\xfb\x0ch\x1b\xad\xedL\x1b\x08\xaf\x055\x8c\xf4\xa2x\xce\x00N\xfc,\
+\xb1\xfeo\x9c\xbe\xee\xcf=-\x9d\x8f<\x97M\xab\x1e\xa3\x7fR\x00\x9f\xee&\x144\
+\xd9S:\xab\xf6\xbe\x85\xcb\x9f\niy\x1e\xed\xf5\x02\xa0\xf1@\xbb\xa3\x00\x0e(\
+\x17\xad\x86\x90^/\xa8a\x84\x11\xc6\xcc[\x8a\xb0\xe62p\xe1\x0b\xaf\xf7\xebci\
+\xd7\x8dl^Y\xcf?\'\x04\xd0\xb4\x93"\x11\xa0y\xf6\x92\x87\x17\xcf\\|_\xd0K\
+\xb5\x80\x8e\x93)\x14-\x01\x0f\xad]Pq\xdc\xc4!\xb4\xfc\xe1\r\n\xa3\x14+\xff\
+\xa7h\xf36\xba\xda\xdeK\'\x06\xbf<\x1a\x1ddm\xddV\xec\\\x00\x992\\\xb9\x89a\
+\x0c^\x1a\xb8\xd8$];\x82a\x95\xa3U\x1aT\nT\n\xad\xd3hm\xa3e\x04\'~0\xa78\x80\
+V\x11\xdc\xc4Adr?\x15\xcb\xef\rM\x9fs{u\xc94\xfe|\xcd\x15\xf0\xed\xf0\x1e\
+\xd6ZyE\xaf/\xae~\xae \xbf\xb0Dx\xe9\x13\xa0=@\xa1e\x0c7\xf11\xe8\xd4\xe8\
+\xd0\xf1\xcf!a\x96a\x15?J\xe7\xf1w\xd3\xe9\xc8\xd9U5\x1b9qM\x00\x80\xe6}T\
+\x82\xf9IE\xd5\x96\xe9%3\x97\x99^\xa2\x11\xe5v\xe3\xa5\x8e\x83N\x02\x10*\xdf\
+\x06Z\xa2u\nt\x1a-\x07\xf1\xd2m(\xe7\x0c 3s\x99y\xb7\xe0\xc8\x15\xba\xf3\xf8\
+\xae\xf3%!\x16W\xae\xe3\x8a\xe3;\xe7IXS\xcf)\x90\x95\xe7Z\x1b\xbe\xeci\x7f\
+\xd9\x11\xd6\x8fPnWF|\x04]p\xeaP\x83\xd7~xO\xf2\xec\xc9\xf7\x87/wu:\x1eU\xe4\
+\x95\xfd\x1a3\xb4"\x13&\xed\xd3\xe4\x87\xb5()\x9f\x7fC$\xc5C\xd9ZW=\x8ak\xea\
+\xe9\xb3=V\\>\xf7\xc9\x87\xe7Z_MY\xe1\xfb1CU\xdf\x07h\x17\xcfKK7\x11]=\xdcw\
+\xf1\xc1K\xe7N>\xdbyl\x7f\xcf7\x9f\xedN\ns\tV\xc1\xea\xd1\xe9\x152\xd5BQ\xf9\
+\xdc\x02\x03~2a\x00\x80\xbb6\x90\xbe\xf3\x11~\x19\x1f\xec\xda\xde\xd1\xfc\
+\xf7\x946n\xc5*\xbc\x1b0\xd0jd5\x1cA\xe7\xca\x8d\x1c\xf8\xd9z\xb6G#,\x8a\xf5\
+G\xffz\xba\xf9\xb5\xb4\xe7\x15`\xe6\xdd\x0c\x80\x92\x03\x04C\x960\x0cn\x9d\
+\x14\x00\x80\x10\xe8\xeaz\x9e\xb3\x93COt4\xefO\xda\xa9\x00\x81p\x1dZ%\xc8N\
+\xc2\xba\xad\xd85\xeb\xd9\xa6\xa4\xfd\xdb\xeeS\x8dI#\xb0\x00D\x10\xad\xe2X\
+\x01\xd0p\xe3\xa4\x01|[\xb9\x9e\x7f(\xd7\xbd\xa7\xb3\xe5\xc3\xa1\xc8\xe5\x8b\
+j\xa4\x12r[O>\xdbS\xf1\xf8\xb7C\xdf\x9d\xd7\xc2\x9c\x81\xc0@\x88\x04ZS6e\x00\
+\x80\x9a\x8d\x1c\xf1\xd0\xb7_\xfc\xea\xe4\xb7=_5\xba(\x9d\xb3\x8a\xd6\xadC*\
+\xcd\xdfR\xb1~W\x88 \x18\x05\xa4\x87\xfb\x11\x82\xf6\xa9\x02\x88\xd1X\xf3\
+\xe7\xf5tw\xb6s\xc7\xc0\x85\x0b\'\xb4V\xc1\x7f\x9f!\xc0\xc8C\xcdw\x130\x94b\
+\xd8sS\x1e(\x84\xc8ch0jK\xc9\x07\x13\x05\xf0\x05- 0\xeaA\xdf\x1f\xfd\x0b\xa9\
+_5Pg\xdb\xbct\xe12\x01 o\xec\xff@\xd0\xf1(V\xca\x10h\x89\xeb(\xa2\x97b\x9e\
+\xe9\xb2#[(\xfbq\xec\x0b\xfb.\xb2\xfa\x19omE\xd4>\xc93\xc5\xc5\x88QQ\x18\xc9\
+J\xbdb\tV~\x88\xcd\xa5e\x96\xad\xb4J\xf6\x9e\xed3l\xa5^\\\xf381\xfc\xda\xcc\
+\x01`L\xc0\x05Y0\xb1XF\xd8o\xd5\xd3\x1bX\x1b\x0e[N _u\xf4v\x0fN\x8b\x0c\xd9G\
+\xb7\xed\xa4\x81\x91\xed\xf1\xc7* \x93D\x82\xef\xdf4|\xcf\xbe\x1e\xfb\xdbX\
+\x10\xdf\x14\xa0v\xfe\x9e;\x97T\x88M3\xe6\x05\x93\xc9\xa8S<\xd0\xafO\xff\xae\
+\x81\x17\x9a>\'\xe9\xc7\x8c\xba\x04\x94?\xc1\xd5\x84\'\xd2\x17\x80\xaa\x98\
+\x83\xf9\xfcfV/Z@Ua\x11v2A\xe0|7\x1f=\xb3\x9d\xc6K\x97p\xb2\x85\xfd\xfe\xd8\
+\x15\xc8u\xd7\xb9V\xe1\x8a\x15\xa8\xba\x85\x82UwP\xb6l)\x15\xb3\xca\x99\xedH\
+\x86\xa3Qz?=B\xdb\xcew\xf8.\x87\xa8\x7f-\xc7n\xc1x\x10W$b8\x8c9}:V\xc5l\x82\
+\x95\x8b(\xbca&\xf9\xf9`y\xe0\xf5v\x13oj#\xd6\xda\x81\x9d%\xa8\xb3@\xa4\x9f7\
+\xb9\x0e\x92\tUA\x8e\xb1c\x13\xd1\x17\xccn=\xb2\xce\xefk\xbd[\x8f\x0706f\xac\
+\xb8\xdff\xaf\xc0U\x05&kW\x13\xf7mR\x9fk\xff\x01\xb0\x15\x95\x01\xe1\xb3\xd2\
+\x81\x00\x00\x00\x00IEND\xaeB`\x825\x1a\xdc\x86' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_favicon.py b/jet_tools/JetCreator/img_favicon.py
new file mode 100755
index 0000000..39b2dde
--- /dev/null
+++ b/jet_tools/JetCreator/img_favicon.py
@@ -0,0 +1,61 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\x01\xd4\x03+\xfc\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
+\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
+\x08d\x88\x00\x00\x03\x8bIDATX\x85\xc5\x97klSe\x18\xc7\x7f\xef9\xed\xda\xb5\
+\xdd\xa8\xeb\xec\x9cd\xc9\xe6\xb0&\x18#\xc6(\xd1\xb0(\n\x0eG\xb8\x05GLX\xe2\
+\x17\xa3\x04\xd4hP\x13t&\x98\r\xbf\x90h\xe2\x06Y\xf8`d\x18!r\t\xa0f\xb0\xa0s\
+\xea\xbc\x8d\xc5!c\x85Ywi\xe9\xd8\xad[\xaf\xf3\xf4z\xfc\xe2t\x10l\xc7\xba\
+\xd6\xff\xc7s\x9e\xff\xf3\xfe\xce\xfb\xbe\xcf\x93\xe7\x08!\xc9\xfc\x9f\xd2\
+\x00$\xe21u\xbe\t\xa2\xfc\x8e\x8a\x96\x1cJ\xe7\xe5\x97\xe6\xbb0\x80\xcb\xe9b\
+[\xfdS\xf4xN\xcf;GZ\x00\xa1\xa0\x82\xe3\xd28\xae\xc0\xb7\xc0\xfc61-\x00I\x96\
+\xc83\xdc\xc6h\xf0\x02\xed\x83u\xd9\x07\x00PU\x15\x93\xc1\x8c\xdd\x7f\x90\
+\xf6\xc1\xfa\xec\x03\xe8\x8d2mGGq\xfc,\xe8\x8f6\xf3\xd5\xc0.Tu\xee\xc7\x916\
+\x80\x90\x12X,\x054\xbd:\xc2\xe9F\x0f\xfd\xcag\xb4\xbbj\xb3\x070\x1d\x88\xf3\
+P\x95\x91\xc3g\xdf\xe3\xf2\x17e4l\xbf\x8a}\xeaSz=G\xb2\x03 !\x11\x0c\x06\x88\
+\x96\xb4\xd0\xda\xd5\x88nb\x05Mo8\xe9\xf1\x1f@\x89y\xb3\x00 \x0b\xc6\x06TF\
+\xc6\x878\x1f|\x87\x83gw\xe2\xe9\xb6\xd1r\xbc\x93\xf1\xf8\x8f\x99\x070\xe4\
+\xcbt\x9d\xf3\xb1\xa7\xe6\n=\xdd\x7fp5\xaf\x81\x17k+\xe8\xfa2\x8e\'|)\xf3\
+\x00\x01\x9fB\xf5\xf6{yv\xfd\xcb\xec\xd92\xc4\xe7\x87z(\xad\xec\xa6\xd0\x9a\
+\xcf\xc4X\xea#\xd0\xa4\x0b@B\x837<\xc8\x0bu\x1bX\xf9h\x0b\xeb7\xadE\xda/\xb1\
+\xe95\x0b"\x91\x93\xd2\xbe \x8d\xc8\xa03\xf1M\xdf\x07\xdc\xb7\xd6\xc3\xf1c\'\
+i|\xc5I(\xe4\xe7N\xeb\x92\xcc\x03\x18\xf2\xb4\x1cmt`\xef\x08\xd36\xb4\x9b\
+\x077O\xb2\xb5\xfay\x8e5\xb8\x91\xf5J\xa6\x01T\x0cfXv\xd7j\x9a\xdf\xf2\xd1\
+\xbc\xdb\xcd\xd7}{Y\xb9-\x84\x7f8\x07\xf7x_\xa6\x01\xc0\xe7\xf3\xf2\xfa\x8e\
+\x9dtv\x9e\'2\xf0\x00o>}\x11\xaf\xda\xcd\x86\x97,(\x81\xd4\xfe\xf4[\xb1\x10L\
+(\x1e\xca\xcb\xcb\xf9\xbe\xe3;\xaa+w\xf0\xf6F;\xd6R\r\x8b\x8b\xeeN\xe9O\xbf\
+\n\xfe\x86\x98\xd1\xbe\xfd\x8d\xa0\n>\xaam\xa2\xe2d?**\x02\xf1\x9f\xde\xb4w\
+\xe0f\xfap\xdf\xfb\x14\xe9\x96\xd1\xf5k\'q\x82Ic3\x02 KZ\xaa\xd6l\xe4\xda`\
+\x08A$\xfb\x00\x006\xdb\x124\x1a\x19\t}\xe6\x00\xb4\xc2D4|\xf3\xe1cl\xca\x89\
+>W\x87\xc0\x984\xc7-_\xc2\xc9?\x1d8&\xcf\xe0\x8f\xb8\xf0\xfb\xe2\x94\xd8\x0c\
+\xc4c\x80\xee\xfa8\x87\xfb\'V\xad{2e\xbe[\x02\xe8p\xee\xe5\xb7\xd1C\x84\xe3\
+\x01$\xa1A\x96%\xee\x7f\xdcDTQ\x99\xfd\xa1\xc3\xc3nb\xdaQ*\x96>\xb70\x00*\tZ\
+\xaf\xec\xa2w\xea0\xb9\x9a\x02\x0cZ\xcb?\xef\x94\x88\x97\x1bG\xf2~\xd7E\xb6\
+\xae\xa9\'W*^\x18\x80\x8fO\xd43b\xfe\x84Bs1\xb3\xe7M\x95\x04\x92\xd0\xb0H_z]\
+\xfc\x8a\xe5\x95\x90\xa4\xf6g+\xe5%\xbc\xe6\x1e\xe5\xc8\x89\x03\xdcQl%\x91\
+\xf8\xf7\xb9\x10\x02%\xe6\xc1fY\xc7\xed\xc6\xa57\xb8\xe6\xb6\xf8\x9c\x00"\
+\xb1i.\xfc0\xc2\xb47\x81\xb9PK\xaeIFgT\x99\xf4\r\xb38g5O\x94\xd5%\xedt\xa9$\
+\x84$\'\xfd9U\xd58\xcfTo\xa6\xed\x97S\xac\xaa\xc9gQA\x0e\t\xc5\xc4\xf2{\xb6P\
+S\xf5.\x06c\xf2:O\x1b`F\xad\xe7\xce`\xef\xbdLiY\t\x8f<\xfc\x18\xd6\xa2\xc2\
+\xb4\x16\x9e\xd1_\xa4\x9cO6P\x7f\x0e\x9e\x00\x00\x00\x00IEND\xaeB`\x82\x80\
+\xab\xe3<' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/img_splash.py b/jet_tools/JetCreator/img_splash.py
new file mode 100755
index 0000000..beb94a9
--- /dev/null
+++ b/jet_tools/JetCreator/img_splash.py
@@ -0,0 +1,1157 @@
+#----------------------------------------------------------------------
+# This file was generated by C:\Python25\Lib\site-packages\wx-2.8-msw-unicode\wx\tools\img2py.py
+#
+from wx import ImageFromStream, BitmapFromImage, EmptyIcon
+import cStringIO, zlib
+
+
+def getData():
+ return zlib.decompress(
+'x\xda\xbc\x9b\xe3s%\xbe\x1f\xefOmlmlm\xb7[\xdb\xc6\xd9\xb6[\xebT[\xdb\xb6\
+\xdd\x9e\xda\xb6vk\xdb\xc6\xd6\xb6\xad\xfb\xfd=\xb8\x7f\xc2\xbd\x0f2\xc9d&\
+\xc9L\xf0\xce\xfb\x95\xf9$BEI\x1a\x05\x91\x00\x11\x00\x00\xa0\xc8\xcaH\xa8\
+\x01\x00\x10,\xff\xa5\xef\xf0\x90\xff\xd5\xd0|\xefW\xfd/\x83r\x12\x93\xd5\
+\x80\x87\x87_\xdfQ\xde\x06\x00\xc8\x00\xb2\x12\xa2\x1a\xeeY\xe7\x9dnY\xd9\
+\x9a\x1e\xcc\xaf\x02W\xaf%\xd6\x91\x9e\x841\x94\x95\x7f\xcbVa+\xff\xf8\xdb\
+\xfc5\x84/\tEZ\t\xb5\xc7\xb4\x87\xcf\'\x10\xdb\xce"\nsT\x16S\x84Gh\xd9\xd9\
+\x1fp\x8d\x86\n5\xc4d\xbd\x84-Dm\x01\xea-\x14\xe3\xbbX\x16e\xccNX\x80\xfc\
+\xce|\xaeH\xad}\xbc\xecV\xc1\x82\x89\xe8\xbcNv\x8fww\xca9\xab\x8d\xdd\x0f\
+\xd5\x8f\x8fn\x9dQ\xa0P\xf61Z\x13x\x91Oe\xb7\xbch\xf6\xb1pZu\x13|3d\xec\xe4\
+\xf2\xfah\xf6P\xc4\xb1\x05-)|q\x183+\xc6\xc2\xe8\xfc@v\xaeT\xd5!\x15\xd1\x0c\
+\x82qZ\x93\xb2<\x85\x18\xb3d6\r\x95\x92!\xf61<\xfa\xff\xd7\xcd\x0cE\xac8{&\
+\x0c\x01\x0b\x13P\x80\xa6\xe3\xad]R\xfd\x07ko\xfaT+\xa6WM\x88\x90\x1d,\xf9?\
+\x04y\xa1\xd72\xde8\xe1\xb9\x10\xa0\x904\x90 \xe3\xa7#w\xa4X\x1a\xba\xa7\xc6\
+\xfd\xc1n\x0e\x1a\'\x1fS\xc5\x0fB!\x05\x14\xf8\xc2\xf0Fh T|*\x91"A\x9b\xedK\
+\x80E\xbe_\x88o\x0e\xf4/\x01(\x9c\x9c\x18e\xec\x8f_x\xcdCh\rW\xc1\x08\xe3?E\
+\xf7Z\x19\xd3\xaae\x1e=\xca\x85\xf6\xbfR;\xfbnsC`\x16\x0b\xcc\x17\x11\xb7T\
+\x196\x01\x92^\x94\xd6b7\x15\xe6\x8d\x97C\xae\xb0Z\xcd+e\x9d\x8e\xa4 \x16\
+\xaf\x83\x8a\x90\xe9H/}Nf\x06p\x80\xa6\xf3=\x10E|\x12\x17\xfc\xc3\xc8QY7o\
+\xe6\xab\xc9\x02\xae\xa2\x0f\xed\x07\x8e?\x14\x90\x87\x9b#qF\xa6\x0e\x94\xe3\
+\x0c\xf0\xc7\x92A&\x03\xdc\x91\x01\x9b\xdb\xfd\xdc\xcc\xfd\x18\x14-|\x07\xea\
+\xfe\x02eM\x96\xe0ez^{\xe5\xd0\\\xfc3\xe2\x7f\xa9Q\x1f}\x81\xfb0\x00VtT\x08h\
+\xe5\xa2e\xac\x10\xd8\xe8P\x8f\xae\x8ci\xef\xeaV\x14\xa1\x93\xd52\xa7\xb2=\
+\x80P\xde\x14\xccL\x98<\x10%J\xfc\xb95dP\xaf\x0c\x88"f\x8b\xc7\xff\xe6\x8d1\
+\xfa8x\x11\x83#\xaa:Eb\x96\xe8\xb0\xb5\xab\x07\xf9>\xb8\x07Z\xe2\xc6\x0b\xc2\
+,\xff\x8bF!!\xa3\xeex\x1c\x1e\x13\xca\xbd\x07"D\x94|\x02\x1bYN\xe6\x99G\xc8L\
+\x05\x8c\xd7{-\x8aH\x19tO\x8a\x08\x8f"E\x85\xae\x83\xfa6 FQQ\xe4\x9f:Y\xdb;\
+\xaf;\x1b-\x9b>\xfd\x879t\x05\x12o\xd8\xd6\xb8\'v)\xb6\xa6KH\xa4\x8c\xb78\
+\x9ax\x83\xb5\xa5\x1c\xdcT\x80\xd3\xe7\r\x9e\xe3\x97G\x96"`\xcc,\xeb\x9b\x08\
+\xf6\xc0\x01DK\xe0\xf1\x0f\xf5a\x91\xafO\xd0\xbeO\x88R\x7fG\xcd|\x81C?\xfe\
+\xfe\xa5\t\x7fg\xc0\x1d\xb8:#\x93\x8e\xf2\x1c\tQ^\xcf\xea\x13"\x98\xec\x02\
+\xc3\xaa5\xe5v\xf9\xdb\xcf}}\'\xec\xc9Y\x00\\\xc1K?\x91\xd13A\x9f4t\x83\x125\
+\xc4\xc3\x08\xe6{\x1fZ;\xbd\tv~l\xf9\xdc\x7f\x89\xaam\xdf\xde\x1a\xe6\xd2\
+\x0f\xc5|4C\xa0\xd5\x10\xf1\xb7\x8a\xff\x833\x90\x13{\xe0\xc5\xe7\xd7\xd9\
+\xed$\xe9\xf5\xf4*\xb4\x85\x00\xd0\x803?\x8c\x18\x88\x87\x9c\xd3\xe0\xf7Z\
+\xa8C\x1aB\xf3\xdaHV]\x06\xfe\t\xedc\x0cK\xfd.Y,\x92x\x15\xd1\x87\xa4KD\xb6H\
+f\xfd\xab\xbb\xc0aL\x02R^\x1dB0\xba\x88\xe3\x0e\xb7\xe5]RkA\x84\x10D\xe2\\\
+\xbc\xfc- \x88\x17\xa6\xa8\xd0n\xb9\x05J\x98\xf3\x8e\x1a\x0b\n\xa2\xb0\x17>\
+\\\xcdR.1\xd4\x01%-\xceZ7\x8b&\x1f\xb3\x05\x8cP\x06\x920\xc5\x06\xdc\x05G\
+\xbc\xf9~B\x96\xb1\x96&&q~\xeb\x13A\xc1R\xc28\xa5+\xd0\xdfv\xbc\xbf3WH\xe7p\
+\xa2l\xb6 \xcc6\x15\x0e\x87\x80Cu\x11\xfekUJ3@\x0b\x8a\xd4\x1dC$\xb7\x10r^a\
+\xd89_\x839\x8a\xc6W\x85i\x93\x81]]2\x0fH\x87\x04"\xe5e\xba\xb9U\xf9y|\xbd\
+\xa6\xad\xb7^\xf2zn\xab\xc4E\x84\x01\xe3I\xe4T\xa0p\xf19:@\xbf\xeeF\xfe6J\
+\xb1\xe4\xf08\x9c\x8f\x9c\n\xc4\xe0\x9bd\x92\x9c\xb2O\xa5\x18,}\x84y\xf9\xfe\
+5\xd6A\x85V\x17\x91Q\xd3\x93S\xa4-\xb1\xf2W\x93\x95\xbe\x93R\x93u\x1c-H\x9d\
+\xa58-\xc2\x92<\xdd|\xdcS\xaeZ3>\x8d\x06\xfb\xd7!rp\xa8V$\xdc\x17\xa8\xf0\
+\xa9\'\x98\x0e\x84\x9fZ\xe3*Q\x1b\xa2\x0f\xf1j\xd0+\xf3MS\xad\xe4\x935\xf8C\
+\xf2\xc7\x91\xaa\x19\x08=\xf7\xd8\xb5\xfa\xb4\x18\xc4\xd1i\x19p9HLf\xa9>\xe0\
+\xa6\x07:\x87\xc5\xda\xfc\x98kli\xfeH\xc8\xbe\x88\xab\xfa\xf7\xf2\x92\xb0\
+\x88\xabH\xe2`\xe35\x8d\xc4{3$\x01\x94D/\xdb)\n\xef\xe0\xee`\x98\xaaF?>]\x7f\
+\xd1]\xdf:\xffq\x1b\xae\x1c7khoh\xa0\xbe\xf1^2\x96\xba\xd3\x7f\x98^L9ijp4/\
+\xf8\x01\x17\xfb\xf8\xfb\xf8\xd8\xc3h<\x80\xf4\xdd\x93\xb0$\xc6\xe3\xe9o\xe1\
+\x96`\x8b\xd17\xc6&\xac\xe7$\x1aL@@\x8d\x0cF\x8d\xcc\x02\xbc\xbfO\xf6\xf8#\
+\xb1\x9e\x91Z\xd5\x1f\x15C\xc4\xc0\x1c\xfb\r\x9fv\xe6\xdc\x9b8\x7f\x7f\x15\
+\xf7\xfc\xc6\xe8\x19yFJ*\xb9\xd2\xcf&\x93\xafY\xf35/\xfd\xe3;\xe2\x9c)F\xc5\
+\xe3\t\x05\x8e!\xf5\x83S\xe9\x85\xc9\xf2r\xf0\x0f\x82(\xaa**\xe5\xf8;X\xe5}\
+\xaa8\xda\xce\x1er\x19z\xe3\x8bq\xaf\x96\x0b\xa1\xcd\xf5\xf5\xa3\xc9\x85\xf2\
+2\xdcJ\x99\xecH\xa0\xfb~V\xa4j\x88J\xff\xbdl\x8c\x894\xe7\xd4\xe0\x08\x19\
+\x9dr\x17\xb3j\x87\xd6\x93C\xf7\xe3\x01\x94\x8ek+\xe9\xd9\xa3\xfd\x18\x19\
+\x01\n\xab\xaceJ\t"\x8f\xddP\x99d\xa3\xcePl\x15w\x8fz6\xc2\x1d\x81\x1c\xd6\
+\x02\x02\xb2`\xe7I\x90\xb2\xad\xd7=\xcf:\x1a\xf19;_3\x11\xad\x894\n\xe1\x96\
+\x06\xc7\xd3\xbf\xfa\xe0\x122\x7f\x1b\x9b04\xa2\xd3\xadY\xd7\xe7\xed\xd9\xbd\
+\xca\xe5\xab\xce\xb3\xb0i=\xfb\xcb\xe73\x96\x8c&\xd3\x9b\x05Z\xf5r\x10\xc6I\
+\x18A\x19\xb3\xfd\xe2T\xce\xe7\x91\xbe\xd0\x1f\xd0:\x04\xc4"Fq\xa4e\xc1\xc8\
+\xac\x04\xe5\xfc!\xd5\xb7\xd1\x0c\xbe\xfe\x97F)\xa6\x1c~\xfdP4\x81o\xfe\xa2m\
+^\x8eg\xfaV[\xb3K\xb3\x1bI\xea\x9el\xe0\x19\x85~4+\xe8\xa6\xae\x16\xab\xd3\
+\x81!|Lp\x9d\n\x00H6\xcd\x08\xbbFIejb\x84o\x98#j\x8b\x0e+\xd0\xf8R\x98\x89\
+\xb6\x81v\x1al\x94f\xc0y\xc2\x013\x85\xd2#\xb0-r~\xa2u\xe3\xbd\x9e\xf6\xe4Z#\
+\xa5A\xc6\t\xb3\xe5\xfc\xa9L\xc3\xc9L"\x11jZ\x80\x90\x10f%mp\xb6\xb6d\xf7\
+\x9b\xef\xbe\xf6\xfe\xfbw\xab\xbf71\x9aJ\xa4\x0f\x8f\xa3\x85\x9b*\'\xa1\xd2\
+\xcd4\x1d\x94\x02\x00\xbf\xa6\xb7\x904To\xbf\xf5^\xd2\x877W\x00\xbd\xed{]\
+\x08.\x8f\x1fHwO\x0c:\x9b5V\xa8\x8b\xad\x82/^!\xc2L\xff\xe5\xdf\xea\xe7u\xe8\
+\x86\xdb\xe5\x996\x08\xc4\xfa\xad\x0c\xa6\x02j\xc0}4\x99E\x8fu\'N[%W\x8ev\
+\x07ln\xbf\x1a\x99.\x8a\xb5\xb2U\xef\xfd05M|\xbez\xcc.o\xabq\x91%\xa9nK\xba\
+\xee.\x99\x958\x92\xaf&\xde\x07Igl7\x1c\xe9C,_\x99\xf8\xea4\xd4\xd5\xba\xb6\
+\x88;\xa04\xeb\xb6d\xb3\xf9V+\xbc\xde\xe1;\x0f\xf1#\x04W%[\x8cx\x81\x12\x02w\
+\x02\xff+\xe2\xa9\xb3\xa5ki\x90\x97h \x1c\x9e\x00Z\xbc\xd5\xef\xf5~\xdcM\xa0\
+N\x7f;\xef\x832%\xa7\xcc{:\x13\xd4\xf91\xa6\xdd\xcc\x85\r@\xa6K\xc0\x92\x7f\
+\xa8\xbbg\xe5w\x15xJ*\xd5\xff\x1c\x9dgyz\x04\x97S\'\xcf*.\xf20\xf3~\xcdy\x14\
+\x0eG[\x84\xf3O\n\x9c\xdc\x9a\xac\xf9\x95Y\xaf\xb6m\x08{X\xcd\xe7S\xf8\x80\
+\xe4\xc6\x8eK\xd62\xb78\xbdF\x0bF\xa1\x03=\xa4\xe4\xbf\x8b\xd3u\xc4\x06\xc0\
+\twy/\x0b\xc6U\xf1\xdb\xd7\xf1\x9b\xf4(YX\x15\xf5&|\xdc\xa7M\xda\xbe,\x90\
+\xeaii\xeaA\xf8S\xd1\xf4\xc6c\xf8S!\n\xdc\xdf\x1f\xf2\xfb6\xa5\xaa\x14\xf6\
+\x12\xed\x10\xef\x9f\xc2\xa8\xdbvX\x1d6\xc9>\x88b\x14\x90\xcd5\xe0 ~?\x82N.\
+\x1a\xea\xf8\xa5\x0cIG\x06\xfb\xf2\x90\x1ag\xc3\xef\xde\xadZ\x1a\xe7>}\xc0\
+\xf1\x85*\xf2(\x87\xf0>\xc2\x9e2\x9d\xa7\x83\xfanx\xf0\xf8\x95s\x97-W\xb8B8\
+\xfdq\xb2\x12*Is!}\x13i\x0e\xa9\x8d\xedks\xb6%\xbc\xf5\xd8*\xe5p\x88\x82mB\
+\x80\xc3(p4Na\r\xca\xf8\xdb\xf3\x85e84\x12\xc9\x84\x1et\x7f\x14k\xbb\xe2\xb7\
+|Wm\x90\xd2\x93tr\x14\xfdq\xfd.\xdb\nl3\xf28t\x0cl\x9b\xb6\xc0\x01\xbd\x91Ik\
+@\x13^dL\x92n\xb8>\xbf\x89\xaasq$\x0cLD\x07\xb7\x9e\x1d\tp\xd6\x1b\xddD\xf0\
+\xed\xe7%e|Q^.\x08m\xdd\x14\x1e1_\xde\x11\x93t-\xc5\xb8\xbfIVV&~s?\x00\x87\
+\xd4\xc0\x0ej\xa5\xd9\xbe\x1c\x1fB\t_\x17\x18\xab\xd1\xa1\xf0L\xf8\xdaK\\d\
+\xe5\xa1\x83~6\xb6\xe6!\x89\xd8\xbe\x1d\xaa\xb3\xfc\xe6+R\x13\xddb\xf9\xbe\t\
+\xcf\xc6\xf5\x95T\xdbY\\\xd3\xc27\xd2)\xc2\x07E\x11\xc2\x07\xbf\x07\xd9\x8b\
+\x7f\x13\xe3\xa5)\xdcb\x0c\'{\x94\x81h\xdb\xfe\xb2k\x8d\xa3\x14\x13\x8aa\xa8\
+\xf1\xcb\x10Q\n\x0c\x86\xc6i\xf7\xbe\xd8\xb4\xfb\x18?-\xf4\xf1a\x95=\xd1\x91\
+\xe2|\xdf\xbaJw:y\x07\x85|\xebMG\x83B\xabpw&\x95\x88\xc1rw>\x98\xba\x12\xba\
+\xf66\xb7\x9bRE>\xe981\x11~\xe9\x92\xff\xef\xa8\xc7\xe0\xacU&_tt\xcd-z\xbd{\
+\xa1Uy9\x0f2C8\x16\x00\xc7\xb0(\xcb\xaf\xf3|.L4\xa7s\xd8\xd8\x0b\x0f\x92\xd4\
+\xe5[\xa2\x01\x86`5\x0c(\xa1w\x9e\xee\x95\x96\xcaLN \xf1O\xcd\xb7\xde\xd6\\\
+\xb7\x8f\xc0\x93\x10=\x15\xc3\x84\xa0Bh4\x19\x14\x9c\xa6\x1f\x1cAT\xb0\x1e\
+\xbe\xcb[\xb6B\xce\xdd}`B;-\x81;\x1d\xd8\xd9<\xba`3M\x93\x99\xf5\xc0]\xd4\
+\x0c\xb6\xc3\r\xcd\xdc\'\x16#\x8cr_\xb2T\xfb]\x8b\x10g\xd6\xfb\xe2\ra=\xa7\
+\x8bF\x1b?\xeb"r\xe0uby\xeb\x96}ow\\\xd5\xdf\x1e\xc6\x88JIl\xd0f\xbf!Jo?\x00\
+3\x1d\x08\xe4\xd4\x13\xd8\xe1\xb0\xcb}\x93\x08\xb1f-\x9a\xa2_\xbex\x1b6\xf8\
+\xed>\x9e\xbfG\x18\xcd\n\xdd\xfds%\x00\x91>/\xdf\r\x9f\xa6R\x06\x02\xde\xe8\
+\xb3\x01\x7f\x8b@\xdd\x8d\x0e\x06\x11\x8e\xa6\x04\xb20\x83\xcdA\xb9\xd3^]\
+\xfa\xcb\x15\x1a\x91\x96-\xd7\xb5\x0f\r$\xc4\xa8v\xec\x05\x9c\xfb\xe1s\xdf\
+\x9e,\xf7\xa7\xbcpK\xfbB\n\xf0Ws\x12\xb8M f\xac<\\1\t\xe7\x9b\'VH\xe0\xa1\
+\xcb\xafy\xf9\x87\xb8\x179\xea\x9d\x0b\xce\x81\x13a\xe13D\x87\xa5\xba\xbc\
+\x9a\xf3N*-(,\xbdl\xea7\xbe\xaa\x9eND^ \x98\n\xa6\xf6A\xb6=W\x04\x19\xb61o1(\
+\xd4\x81"4a*\x8f\xfe}\x80\t\xd7\xa0\xd1\xde\xca4L\x03Z\x19-\x1f\xb9\xad\x15>\
+\xff\xde\xa0\xfd\x88\xc7\xa0\xc3\xfad\xe7S\xa8/\xe0\xef\x7f&\xe2\xb1\xba\xbb\
+\xc3\xea\xe6\x1d\x04\x80\x86\xda1\xfd\x07D\xf6\xa72D\xb4\x8a\x7f\xa7\xbbX\
+\x93L\xba\xfa\x98#,m\xe6,:\x15\xd5n7\xfb\n\xeb~\x1fh\xe0\xb2\x81_\x17J46"\
+\x9b-\x82;\xb5\xa6\x17v\x1d\x1f\\f\xb4\x1d\x8dp\xb3\xffP\t\xcf\xc07\xc2\xf8\
+\xa0\x806\x9aH\x99Q\xc7\xf7\x1e\x01\xd1\x8b\xdapj\x15\xcf\xe49\xf7u\xbf\xee\
+\x85,\xb3d\xd5\xec\x16\xaa\xf3D\xce\xb2\x06\xfc\xda\xef\xe4\xe7\x95S1\x11c\
+\x1f\xa1M\x84=;\xb5\xf3q\xb8"\x86U>\xa0:\xf2\x9e\xc5/\xa36\xa2C(\x04a\x92\
+\xc3\xc2\x04\xad\x85 \xa0 \x1d)}F\x03\x8cS\xec\xc1\x1e\xaep\xc8\xb0\xd0\xbb|\
+\x0b\xed\x9f\xc3\xd3\xfa\xee\x7f\xec\x98\xb6\xf3~M\xee\xa4)~-\xbd\xec\tI`\
+\xe2\xb1\x933\xce\xb2\xa1\xd3r\xbcK\x87\xd5\xe5PN\x88\xcfh\xf1<M\xde\xe5\x10\
+\xa8\xf3\x0b+\xb8yM\x0f\r/u]\xd8O\xbfNH[e\x88$}\xbfb0\xcc#\x0f \x10\xe1c\xcb\
+\x14\xbc\x11\x07\x81\xeeK\x8a\x82\x13\xbc\xdc\'\xb5\x97\xd5Y\xbc\xf3\x86II\
+\x82a`\x81`\x1c\x81\xce\x80m\x141\xb1\x8c \xa3\xdbp\x9ep>6%\xfc\x1b8C9\r\xd3\
+l\xd5\xaccL\x9e\xa2\xb7\xf6\x1c\x88A\xe4\x85`\x90\xf1\xbf\x8d\x07d\x05,\xde!\
+\x14=\x08S}3+\x1b\x97\xdb\x96J\x10=p\x1b\x06\xb2\xb8\xc0\xf8\xaa\x97\x04w\
+\xc284\x9b\x85\xea\x11\xea\xae#\x14z\x11OSe!\x8a*\xeaP\xe9\x8f\xafQ\xd3n4\
+\xedD\xa9\xa8!*\xa4\xb8\xb9\xdde\xa1\x81\xef\xe9(p\xbd\xfcP$\xa4\xf3\xe2\xa1\
+Q\xc8\xe5N\xb2\xff\xb4"\x9d\xc9\xe7z\x18T\x85b\x88\x88\x83C\x10\nM\xa9\xa2\
+\xae~\xf3\x10\xa7\xef\xf1Q\xac>\x01\xef$\xe83\xdc\xed}\xd4 \xad\xd9\x8a:\xa0\
+\x08\x8f\xcb\x1d\x9d\xfe\xb60\x99\xa6\x90\xf9\x03\xe3\x90\x81\x1c\xc4yc\xac\
+\x14c\xddJ2!\xf5\xcb\x88\xb17\x05\x1a\xda\x125\xa2\x17\xb5\xe5\xe9\xf2\x9f\
+\x0b\x9e\xff\xb7\xdc\xb6\x93]\x8f\xa3X\xd4\xb6\xfcoF\xa4\xd4\xa9H&\xe4\x86\
+\xa9\xad\xddJ\xb1sJ/\x1dG\xd4T\x7fOz\xd0^\xbd[\xf1\xacN\xc1\x84\x15\xd4\xc3!\
+\x01\xf4\xc9\xb3.9\xa4\x1du\xd7K\xa6\x8f\xe9h?\xc7Xa\x14\xb3\x91\xa0\rG\x11\
+\xbcw\x07ffv6Q\x84\xb7\x9eKH\xb6LN\xa1s\xe2\x97\x95>\xfa&f\xafb}\x8d\n\x8e\
+\xbf\x06\xa1<\x16A\xc6+\xb8\xbf\xab\xc0\xf7\xafNW\xe5\xce(\x94\xa2g\xcbS\xd2\
+\x06\xee\xeb\x8f\x8f\x06\xc8=%\xec\x98\x9c\x8ekX\x80\xd1\xff<d\x9f\x8c\xe1\
+\xd0\xb1pQ\xe2!9\xb6\\\xfa\xca\x7f\x9d\x8e\xb7x7\xa0\xbb!vH\xa1\xa4\x0e\xfe\
+\xd1_\x9d~\x97\x08\x89\xcb\x82x\x00\xaa\xa3T4\xa9\xba\x1c\xc1lT=\xbf\xc1\xf4\
+\x07!RNq\xc1r\xc3WD\x04\xfc\xec\x97\x9a\x9e\xd1@\xef\x12\x1a\xb1\n\x05\x10n\
+\xe8\xd4\x82gU\x806\xdf)c\x10o6\x04\xdb\xaa\x1f\xec[ \xd5\xf8\xa6R}&\xcd\x93\
+\x93\x00+\xff<\x17\x82]\x16\xb1[\xbb\x9c\x94\x844\xf0\xaa~\xe0\xcd\xfd \x86\
+\xdc\xd6R\xfe2x\xdf`1\xd8\x9c\'\x97\xa2\x128\x7f\x85I\xfa\xdc\xcf\xbec\x04\
+\xf3\x0eF\xa52\xd9?\xe2\xe4\xd4}\xbfyhvI\xc9\xedx\xe1\x9a\xa7\xe6\xc6_\x14\t\
+y\x1b\x12\x1c\xbfz{\x19g\x05\x94\x1f\x04\x99\xaaJ\xe0\xe7#\xd7\xe8\xf5/\x0b\
+\xbb><\xef4\xd3\xc5\xa8\x10\x08`\x1f\x92zZ\xdd\x05\x88\x0fsLw\xe2\xdd\x06\
+\xa80U\x15f\xa3\xe7\x90\x9f\xc7\xc2-\xd1\x1b\x8d\xb5\xeazL-F\x95\xa8B\xf0\
+\x91MB\xd2\x03\x18\x1al\xc7\r=\x8dF\xff\xe4j\xeb{R\x82\x96<\x9f\x85E\xd0F\
+\xea\xc6\x0e\xd19U_\xab"\xd44\xf4\x80\xab\xacN\xe7^}i\xdf\xb01H\t\xe2\x18\
+\x86\x03c\x17f[\xeff]x^\x9b\xe0j\x0c\xa2A\xbe\xbd\x0b,\xca\x84w5\xa2Cl=\xc7\
+\r=\xf5\\\x1a\x8a\xee\x1f\x1e_\xe3\x1ep\x85\xb8\x17W\xc2m\xde\xe3\xc3\xa8\
+\xa4[2s\r=\x0b\x80\xf0`\xd5\x04\xce\x05\xcd\xd6p\xa3\xa1\x95\xcc\x92X\x8f011\
+\xf4>\r\x8b\xa5\xb5\xcf\xeb\xc6j\x83\xc9tO\x0c\x03\x92\xfd`\x94\x8a,/\x8c\
+\xe6\xc5B\xd9\xc0_\x19\xc8\xc3=\x1d\xbc\xac\xe1\xda\x85[:\x83\xa1\x86\x94\
+\x19\x88(\xb8\n\xcfy\x12g\xd9V\xf0|\x81\xdauD\xd0".\x8a\x8e\x17\x05\xa3V\xf7\
+\x04\xc1\xdd\xaa\xef\x85\x95\xae\xb0\x05S\xc9\x1f\x01\xcb\t\xacEl\x18\x84U\
+\xa2\xc6\xfd%>\xb0\x1ew\x8b\xf3RqD\xb0\xe3Z\xf6\xe9\x01\xdb\xdcf\xc8\xb1~\
+\xaf:\xcavv6\xb5*Ed;\xcc\xd9\xf3\xef7:8\xf1\xa8\xa0\xe0}\xaa\x8b\xf2~\x00\
+\xd8\xfbbO\x13\xb9R6/\xd6X\xc3m\xba\xff\x8cDo\xc3J\x06\xaf\x9c\xe6~\xa9\x01\
+\x08K\x9e\xb7\xb4\xb4{G\xaa\xff;\xe0v\xf2\x9b\xfdv\xbd\xed\xdb\xd9\x95\xd0`\
+\x8f\x04\xc4\xaf\xfaVe\xfc\\\x14)\x95\xacx\x9a\x9fM5\xba\x8dI\x03Y\xda\x14v>\
+G\x92\x19wl\xa8_\xce\xc8C\xe1\x18\x1a\x94\x01\xf2\t\xb3\x9e\x02swb*M\x96d\
+\x16\xbd\xb3\x04\x8cCj\xbe\xda03\xec\xef1ym\x02N\x863\xa5\x9c\xeb\xed\x18\
+\x18n\xafs5\xa5\xfa\xef"\xc2m\x13\xd1\x87\x1a\x8e{\x99\xcc\xc2~\xffHi\xf08\
+\x7fx8\x0e\x90\xf7\xcc\x9a\x1a\xf7\x9f\x1dr\xf9\xb1\x97\x95\xc0>\x16g\xa3\
+\xd5\xfa\x98\xd4\nC\xe6\xb1^\xd1\x04e\xc0i\xf4\xacU8@F\xaea\xcf\x7f9h\\\x10%\
+Z\xfd%\xeb\xc4\xd6Z~L\xc9\x96\xf6M\xfd\xa6Fn\x94\x97\xdc(\xe0\x8c\x81\r\x92\
+\xd2`\x9f\x1f\x96s\x81\x05$\xd2k\xfa\xf5w\xd0P\xa3\xf9%\xa9/\xfd\x19\xf2\x98\
+l\x9d\xcc\xbd\xc7\x17\x9bP#3[\x823m\x17x\x96\xb2\xedp\xa4\xde\xa1\xcb\x93\
+\xb5\x87\x13\xe8\xaaX4\x9c}\x07\x9d\xf0H?\xd0\xd4\xa9\xe2\xfd\xac\xb2\x11\\\
+\x92N\xefv5B\xba\x0bd\xfd\x81\x16\xdfZ\x102S\xfbqf\xd7\xda\xea\xda\x93\xa5\
+\x86`\xa0\xae\xf3r\xa2\xc3l\xec\xcc\x1cLG@yc\xe8_~\xcd\xe52j\xe0s\xacf^\x0e\
+\x0e\xa0O\xf5\xbe\xbb< \xe4&F\xac\xca%\xdb\x951a\xd7\xd6\xc0\xea\xb1\xc4\x0c\
+\xddz\x9d\xf2(\xb9/\x80\x97\xefq\x96?\xab\xbf\x12\xa8c\xe6\xf0\xde\x1d\xa3C\
+\xf4\x9dJ\xaf\x15\x07\xfcwc\x7fL\x91\xd6\xceJTTT|\x9d.\x8d\x07\xbd)\xec\xdc\
+\xa9\'\xbc\xac(CA\x99\x0f\x85\x9c\x93\xc2\x90\x15\xad/\xd7c\x1d\x02\xf0<\x9a\
+\xbc\xc4\xc3\x9d\xb1\xd5\xfe\xc9\xdd:\x8a\x84-\xebmkh\xec\x1d\x8e\xd8)\x9b0G\
+\xb3\x07\xbf-5\xf9\x1e\xd8_\xf1\x01\x8b\xe5-\xe8\xa9\xd3\x94\xde\xc2D\x89\
+\x85\xa1\xee\x85\xef\x99\x94\x18\xca\x82\xf5Yo\x94\t\xad\xa8gi\xe4\xa0\xaaI\
+\x05\x95;\xef\xa5\x83\x1e\x84-"\x8c\n0\xb8 \xd4 \x97\'}\xcb\x02]\xfa\x06\x08\
+\x81\x02\x832B\x8d/&p\xed<\x90\xfe\xb2\x86ZA\xdc\xfa\x1d\x86 )\xca\xe2~d\x87\
+\x98\x88\x92S:y\xd0\xc7\x95\xf0/\xefm\xc1\xb4\xdf]e\x07\xf3\xd0\xe8\x81e\xa9\
+\xa6\x96\xb4\x890x\xdf\x87b*xK\xff\n\xad\x80\xa4\xa0f6\x92\xbc\xac\xcc\xac\
+\xa3E\xb0\x04P\xa1\x1e8\xa3\xaa1\xfd\x85Z\nRdp\'r\x05l%:a\xd6g\x18\xd59\xc5t\
+\xbf\x81Z\xbc/>KS\xaf\xe0k\x91\xfe\x9d\x04\xd9e\xaak\xaa\xdb@\x17\x02\x9e\
+\x8aW{F\x8fzE\xf8\xc4\x0e\xa7\xf0\x9cF%\xf7&-\xd0\xc14\xf4v\xddN\x96n\xb5\
+\xad\x13{\x05\xacs\xb3\x86\xfe\xc1\xa3y\x89\xde\xe3)\x8d8\xd3\xd9d\xd49K,\
+\xbc\xe6\xe8\x7f\xc2r\xd6\x1a\\\xe7EE\xa2\x03\x9c\x81\xdb\xec\xdc\xa6\xa2M\
+\x18\xc7\xe7\'o\xa7>\xdd\'\'B\xb6\xf4\x06\xef\xf3\xf2\xa7\x95Id\xec0\xbd[\
+\x96?T?\x84$\x91;N{\xfd(\x08\xdeD\x0b\xa1\x87)\xde\xbc\x97\x03\xa6\xb6\xfc\
+\xe9\xeb~\x9f!\'\x9e\xfcLX\xd8lx\xde~.\x19\xff\xd6\xe7\x95\xd1\x1fV\x16\xae\
+\x0f\xdd\xd8\x99-\xcfs\xd9\x1ed\\\xe4\x01\xe3\x8fo\xb25\xaf\xb5\xe8\xb8f\x12\
+\xf9\xc1\xe2Hg\xd6\xe6\x1b\xb20\x9dyo)\x87h\xb4\xa2\xee\xc5\x9f\xd6Y\xf7V\
+\xa3\xde\x98+\xde.\xf53];\xa6\xb2\xddw\xcaO\xa3^\xc8BT\xa3\xd02\xf7\xca\xf3\
+\xb5X\xc7\xd3\x07\xce\xbf\x80V\xfdd\x88\xc3\x8e\xc40\xc8\xb4\rr\xa4\xdd\xb4\
+\x90[\x92\xeb\x90X\xc09/\'\x1e&\xeb(U\x9b\xc7\xb6\xc3\xb3\xe5\x17F\xa2\x85a\
+\x84\x7f\xb7~;\x92J\x0fZ*\xbef2\x1c\x9c\xf3\xc3k\xca\xd9Z\xb4\xeb\x05\xd5^\
+\xd04\xe3\x17\x8d\x18\xba`\xde\xf1\xc9\xc5\xdbPge\xe3\x11\x10e/lY\xbf\xdb"\
+\x92m\xcdI\xc5\xe4\xf0\x9a_Pk"\x88\xfa9\xbfu:\x0c*?G\xe5\xf92\xe7\x9e\xd0\
+\xad\x96\x1a\xa2Xv\x86\x85K)P\x0b\x19\x1a\x0f\xf9\xf1\xe6\xa9\xbb\xa4\x8b\
+\xef\x9bIH$\x02\n\xdd@\x97\xfdQC\xcd\xe6\x9e\x84\x0c\rz\x14\xb8\xc3\xcc\x9a\
+\x12\xd0\xe5\xf1\xd4\xb8\xae\x13\x978Z}\x95\xcec\xfe\xfa\x83c\x9a\x11\xde\
+\x92\x95\n;\x84%\x97\x0fv\x95\xd6\xa3\xb4\xd8\xb2`\xd3\x10\xf5\x9e\xfa\xf1\
+\xf7\x99f\xc1\x90\xday\x98\xc0KUD\x06\r\xbd\xee;8p\xe26\x18I\xd4\t\xb3f\xf3Q\
+8\x1a!\x85U\xfa0\x99\xcf\xaf\xcf`\x80[\xc9Z\xe1\x85\xc7\x9c>T\xd4\xd8n4\xe3?\
+4\xaf\xf2\xac\xf00\x91y\xc8\xaf\xb1|\x03\x19\xac\xb1\xad\x08Hl(\xf7.\xcc\xd5\
+\xb1\x16Z\x81X\xd2_\xa9 ,T*\x113\xc2\x89?\xf8\xfd^\xe6\xe0\xd8Y\x8aAB\xeb\
+\xa2f,\x13\n\x94*\xa3\xdb\xf2\xe9T6\x90b3\x19l_\x88\xb7\xd0\xfc@\x132\xaa\
+\xa9\xa1\x82\xd5\xbd\xffV\xfc\xf8"\x1f\xfc\x9cb\xfdtaw\x85?\xe1\x8e\xaah]\
+\xc7\x02\xefc\xbc\xc8\xdd\x17cU\xd1\xee\xec+\x1fO\xce\x86\x81\x13\xaa\x80Q\
+\x11\xeaE\xea\xfb\xb7\x90\xc7\xdc\x98s_\xc2\xe26\x87g2\xe2\x90\xb3yf\xfb\xc4\
+ZX\xca\x9f\xdc\x0ejOj\xc9\xe0k-\xb5g/\xdb#q|\xf9B\xfbmN\x083g\x1b\xcc\x8eP/\
+\x13\x00\xc1\xdbGPA\xffy\xba\x04\xc3\xa9Zj%EY\xb26\x7f\xd6}\x9f\x9d\xd2\xebg\
+\xa2\x9a#\xdd\xed\xb3R#\xecq[\xbcx\xf6D\xf0o\xf0n\xa8\xeba\x0c_\n\x9d\x99o\
+\x13~\x8e\x92O\xfa\x9c\xd1\xaab\xc4\xfb\x0c6\xef;\xbe\xd4,+\x19\xc7\xba\xdf\
+\xef\x93\x92\xa0\xa7\xb7h,\xf9k@P\xbe\xccr\x13f\x90\xd8\x8d\x1fu\xe6\xe3\xd7\
+z\xc2\xde\xd9\xa2\xa3\x7f\x85\xbb*O\xfeN\x83\x11\x103!\xc3\xa6\x94\xab\xc2\
+\xdf\x825\x9d-s\xb2\xa7\x16\x9cY\xa3\x1cL\xb29\xd1_\xb4\x8c\xdaQw\x15\x8bq\
+\x81\xf7]9\x82\x91\xfb7$<2\xc7P\xc7[\xc7\x8d\xdf.\x99\xf3p\'L\x10\xe3\xea\
+\xfa\xcd\x19p]\xd8\x0e\xbd\xd1\xd0K\xde\xb2\x1dK\xf5\xcdph;\x921%)\x9a\xfe]y\
+\xa2\xe3\x1b\x8e\x1br\x94\xe3\xeb\xfe@-\x9c\x15\xb29\xa0*\x9fY[\xc0\xce\xfb\
+\x99Z\x9c\x19\xf7\xfd\x96\xebM\x94\x838\x19)X\x83\x9dmdo\\\xc7\xf2\xb3\xe1\
+\x05\x13@\x95>\xed\x1b\xa3\xae\xc8\x95\xd4/\xddF\xea\xed\xc8Q\xce\xca&\x0eYY\
+\xa9\x883\x9d\x89\xde*\x8e\xacd\xcd=X\n7W\x86\xa1\x97\x15)\xe9D\x03\x1bG bW\
+\xde\xb3\xf16"\x1f\xcc\xfe\xe7\xa7X\xa0W\xaa0\x00s\xe6\x1b\xe4\xce?\xfc=;\
+\xcfy\xc6\x10\'\xd8 \x1e\x1a\xab\xbf\xcd:h_\xfb\'iA\xb3o\x915\xd5[\xce\xdb\
+\xff\xc9\xa5|\x86R\xe6\x8a\xfd\xfa\x9b\xbe\x89\xb6V\xb3\xa1Kf\xf03\xd3\xe8\
+\x1c\xaf M\xe6\x16\x84t\xa5G/\xbc\xe1~\x1do\xaf-4\xf4\xa1\xc0\xfb-\x13\xe9\
+\xba\xe7\xf5\xe7\x8f\x96\xc4F\x9c\xc5\x93w\x9e\xbb\xa9\xad\x9e"d\xca\x16\xa2\
+\xd7.\x1e>yCT\xea\xbcI\xbc\x870\x82\x18\x14\xb7\xb6B\xfa\x0eb\xe6\x8a\xa8\
+\xd4\xce\xect\xdeG=\x12\xde\xef\x11P\xc1\xebuG5\x01\xeb\xe1\xe4\xfc\xec\xf6\
+\x82\xe10\xad\xed\xb0\x0c\xfb\x89t_\xd3+C\x83\x1aj\x0b\x98\x9c\xea\xc6{]\xfc\
+\xdc\xa4\x1d\xc2\x97|\xfb\xb0\\\xc4w\xef\xf4wY)$\xa9\x03\xceT0\xe2B>\t\xd0#\
+\xb9AQ\x1b\xa8\xc2a2a\xe8\xf1\xd68\x8a\xa1\xb8\x98\x1c\xb4\xf9\x08\x07\xf94\
+\xfa\x91l}\x01\xe5}\x80t\xb47\x0f\xeb\xf5\x84\xea\xf68z\x87\xc9@\'\xc6xZ\x13\
+J\xaa\xe32\xf2\xd8\xd7(l\xfe\x98_\'\xe5\xd7\xbb\xf4\x0b\xbfg;n\\\x9a\xb1\xfd\
+\xfb\xa0z\x87m\x8di3\xf2\x05\xaf)E\x00\xb4Fd8z+\xba\xeaz\x92l!\xe1\x85}v+\
+\xce\x86o55\xde\x1dc\x88\x97x\xcd2\x05\xac\xb4\xe1O]\x0eS\xd1\x11\xfd\xbb \
+\x95P\x99\x07=a\xddn\x1fM8\xc8\xa7BO\x00\xb5\xa0\xe0G\xeb\xd6\xdf\x7f\xfe\
+\xaf\x15`\xe5\x8d\x86\xbc\xfc6\xbek\xb7q\xeb>\x95`N \xce_&%\x13\x19(2R\xd3\
+\xfas\xd6\xe5\xa2\x9de\xc2\xa0C\x13\xe3\xdd\x88\xe2L\xe1\x16\x87+\xb7\xbd\
+\x80\x84]\xe9(\xdf:\xc6\xe4o\xda\xe1f\xea\xb9\xab<C\xba\x9a_\x97\x9d\xd9\x0c\
+\x94u\xda\xaa\x1c\xea\xa7\xcf\xaf\xe3\xc4\xfb\n\xbfw\x8c\xb1$\xcbn\x85\xe3\
+\xaa2t((\xcaK\x90\xff\x1e\xa6\xbe\x9a\xf0\xc7\xbe#\xc3\x8f\x85\x83\x08\xa8\
+\xd9\rd\xd2i\xd9\xa4\xdc\xa4\xb3\xa0\xc2!\xa6\xeaL\x0f\xbb\x174\x15\x10h\xf0\
+\xa21\xa6\x88\x187ng\xc5\x05\t\x91\xc9\xd1ss\x8f4\x154\xc8q\x85\xb7M#"%ms\
+\xce\x97e\x9a\xd4\x04\x9aV\xe0M\n\x0cY!\x07\xf1V\xe3\xf1\xc4\x07~OM\xb0\xed\
+\x85.\x97e\xa8\x13T\xe7\xfesk\xf2\x1a\xaf)$r>\x9b\x81!\x1d\xc2dX\x83\x17,\
+\xfdk\xb4V4\xa4\x80\x07\xa2\xa4"\x7f\x02\xc0\n\xd4{W\xc6\xf3\x82{2J"M?\xef\
+\xd9\x1c:\x98\xef\nQO\x1d\x98!\xa0\xc8vT\xd2!\xeeJ\xe3\xd89T\x14\x83\xe4\x8c\
+\xd1\xbe@a\x92m*\xa1x\xa2\x1a\x13\x94\xebI\x90\xb4\xd8\xad~\xc7\x91\x15|\xfe\
+\xb1*\x08\xa3R\x0b\xebW\xaf.n\xca+\xef\x18\xd4O\x8f\xed\xa9.OE\xd0B\xcf5\x7f\
+\xe7\xa9\xb9\xbe\xe7\x8c^K\x8a\xc3\xdb\xee3Jy.\x99R\x10!\x12p\xc0+\x90\xb8ds\
+\xc0\x0b\xe0a\xcca\x16W\xaf\xbc\xac\xd2\xe8w\x94\xb3\\\xd0\xf3%0\xae\xa6\x8e\
+i\xb9;4J\xb2\xc5\xc6gJU\xae\xb8<\x0bO\x04bq\x01\xc3\xff\xbc\x8e\xd1\xb9,2\
+\x85\xdf~),,`\x90\xf1\xb6}\xb2\xcf\x10eU \x02~\x9d\xf5\x0e7|\xca\n\xb5\xa9\
+\xb3\x7f\x96Ld\x85\xa5\xc8\xee\xa5\x1cmhZ\xa2\x00,\xac0\x10\xb6q\tm\xcf\xa7\
+\x89\'\xd7\xb3$\xd1\x8e\xe2\xd6PKz\x0f\xdf\xd0\x93\xe1\xc7\x87\xdb#)w\xc8\
+\xee\xd4\x93)pd\xa5j\xff:\xd3\xcb\x86\xfc(\xe6\xa0z\xd9fi\x05g\xa0(\xe0G\xc3\
+E\x0e\xfa\x0fk\xda\xe2$Y\xe3(1\xdc,1U_\xc0\xc5\xddgj\xdfo\'N9OU\xd5\x17\x0f{\
+\xfd\x82\x7f\xa3\t\x85\xcbw\x9a"&\x9c\xda\x95\xb9\xa7\x1d\xb5\xcb\xb1i\x0c,t\
+ \x83\x97\x90\x8e){\xb9gJ\x13UTX\x80Q\xa5|\x8b{\xf4>\x92\xb6\x16\xdb\x03\\\
+\xba\x1bN\xbe\xa6\xa1\x11\xde,UNw>6\xab\x174\x17\x00\xb2QA\x11Y\xdf\xe5\xbc\
+\xe68\xc8\rIz\x8a\xbe\xd9U\x18\x07\x15\x88)@\x83\x7f1{\x17?\x8c\x98\xa0U{m\
+\xe4\xd4\x00\x8d\x90\xe1\xcb\xabE\xa4\x0ci\xc8\xe4\xd6\xfe\x8c\xef\x06,tJ\
+\xa7Q\x8b\xa1a\x96\xcd\xe0\xcf\xed4\xe3\x06\x07\x89E\x87\xce\xacR\xcap\x10j\
+\x0b\x13\x0e\xd5\x81v\x92|\x86Sa)\x04\xef\x16a\x92\xa3\xff\x95\x99\xca\x12\
+\xe2i\xf3\\Df|QJ\xe1\xa1\xe8O\x14\x8bT\x10\xa3\xd9y[\xdf\x8fg\xf706\xce\x0b\
+\xab\x15|p\xdaiBe\x98\x89\xf9\x1f\x9fZ\xaf\xcf\xffu\xbe\x0bx\xcb\xb5\xfb\x18\
+\x1b\xcb\xfe\xba0\'\xb4}\x7f\x19\xe2A[\x90\xbbO3\xdd$\x02\xc0\xeav\x1a\xb2Ot\
+\xec\xbf\x05\x99Y?\x91\xf0_6\xfe\xdbH\xe2\xc1J\x0e6\xb3v{\xb8\'\xc8[fL\xd75\
+\xf6\xe5\xb6\x15J\x9f\x83\xa2\xee\r\xb1\xfe\xa9\xa9\xce\xeb*q\xd1\xb3\'\xe8c\
+zs}\xbd~/\xebR\xc2\xe9\xae\x8e\x183\xe6#\xfd\x8aJ|\xca\xdf\xdc\xe2|\x93\xe5\
+\xb0\xcc\x046.\xaaH\xd4\x90V\x17\xe8r|+dp\xed3\x1d\\[\xca}w\xc4\x1c\x00)V\
+\xbd\xcd\xac3\x8bl\xaaR\x8c\xe5\x95\xe6\xa2$\xdb[\x99\x13\x85\x90e\x91k^\x10\
+\xb0d\xbe\xc4\xc5\xd9y\xd6z\xe4\x87\x87\xcfhp\xaaj\x99o]\x1a\xfc\xeep\x18\
+\xbb&d\xce\xfd\xbc\xa8(\x12\xe2\xf85\x10-I\xccd\xean\x86\xa1&\xb6w\xd1I\xb8H\
+(\x11\xb8&\xf3\xe7\x9b\x0b.\x16\x9f\xfd\xcfoD\xbe{\x04,\xda\x14E\xda#\xa0\
+\xe2\xbcJ\xbb\xa3\xfa\xcf\xce\x07`\xa1L\x98\x18\xb5\xe4(\xea\xbe4\xc4\x0c=u\
+\xcc\x9f\xc3>\xd9V\xc1\xf5G\xae\xd2\xeb\xfb\xd8\xfb\xd0\xfeN\x16\xa1\xc6\x07\
+\nM\x857\xd7G\x85z+O9\xd4\x0b\x05\xe2\xbeS\xe2\xdc\x8d\x86+i\x9f%Q0O\xa4d!\
+\xba\x06\xdd`&\xcc\x8d\x96B\xd72\xbb\xad\xe9F\xc7\x89\xc9\xd2o^C\xf26\xc3\
+\xa5\xf5\xf9n+\x9b\xf5D\x98#\xe4\xa2\x1f\xa1\xd7\xd9\x81\xca\xb5\x16\xc0\xe7\
+A\xa7\xcc\xb3Szy\x95\xbd\xc7\x16f\xb6\x1e\xfa\xe8\xf2\x0c\x00wsq\xd7\xbe\xbb\
+2\x1a(\xd5\xf2\xf4f\x99w~\xf8\xa0R0\xbd\x18z\xb1\xf8\x8c\xfbB\x8f\xdd\xea\
+\x94L\x11"\x1f\xb1\x0fn\x8b\xcdm\xfc\xb2_g\x9dO\xb2\x93\xed\x87\xe9K\'\xa9\
+\x90\x81\x01\x83A\xe7\x91v\x00\xe1\xcfA\x16\xbf\xdd_\xa6\xe6\xc61\x18\xbfqo\
+\x9f\x18\t\xa3\xb0\xa6\x0c\xdf\x80\xe8k\xec\xbbk\x19\x85\xb6\x05=a\x02t\xe22\
+\x89\xc8\xd5\xe7|\xb7\xd7P\xd3S\x1buG^\xa13\xe3\x1d\xb9\xf6\xfe\xad\xa6\xab\
+\x84z>6\xbb\xf0\xd0\xb9\xe9\xe9\x19\xfd\x0c9|\xd6\xa6\xf8J\x1f\xeaI\xdesD\
+\x14\xd3\xb2\xb1\xc4[J\x7f\x18q\x02E)0q\xa6\xa2\xa0KI\x80\xdb\x87\xcam\xd5f\
+\xcf\xcd\xe84\x00lH\xec\xde\x12(\xe1\x95?G\xb9\xc6\xea!\xde\x95\xa2\x7f-\xb9\
+\xf7\x15x\xf0\xf5y\xae\xfe\x1d\xea\x94\xc6t\x95\x97\x18\x17\x04\xd1\xc6`q`V\
+\xe3UqE\xa0z\x8eG\xbb\xec\xb6\x85$\xc1\x97\x1dP\x82\xf3\x1eGn\xb3\xa9~\xff\
+\x10S\x1f\xb9\xd9t\x1e\\\xdb\x00\xd4h\x0b\xf0\x1en\xa0\xf8\x0b^\xe8A\xf7$\
+\xdb\xf7\xb2%\xd2\xca5\xf9\xb5\x8dt\x14\x1a\xf9\x8a\xf0U\x90S\x16\xcb\xa0\
+\xf6\x86kn,;\xbcc\x81\x13\x93\xc9\x9aZZn\x93x\xbaq@\x04j:\xf0l\xce\x8fO>;\
+\xb1\xc0NA\xf0\x9c\xdf\xe74\x0ej\xa5\x0b\x9b\x94U>z\x98\xd8\xbd\xe9\xd2gF^\
+\x13\x7f\xb2\xe4l\xc8\xf6t<C\x89\x9f\xd3P\x99\xb03l\xb8G\x1far\x85\xa1\xca\\\
+\xecY\xfd"\xbf\xd0\xd3|s\xad\xf0J\'s\x96p4\xdc\x90\xb1\xc89\xe5T\xce\xe3e\
+\x1a\xa0\xcc\xaa^\xdaNw\x0f{\xf0 \x18\xb6\xe2\xd4\xa9DFji\x92\xa3\xa8\x88\
+\x8d\x7f\xed*_\xdak\x89\xa21\t\x029u\x11]M6YB\x06\x1a\xa1\xf7py\xfaOa=\x085/\
+ov\xe0M#\xaf\xd2\xf5_W\xa9\xd3K\x04\x03u){\x99}^\x80s\xf0\x14\xd1\xc7e\x14\
+\xd8\xcek\xe3\xbe\xb6\xfbl\xf7\x88\x06\xad{)c\x8e\xf3\x1b\x84\xd3/v\'V\x0e!*\
+~\x84\n\xc4v\xab\x9b&\xa3f`@\x8e8\xfag\x88M\xd8#\xe9%\xfe\x1f|Y\xe4\xb8\x9a\
+\x7f%\x126\x8a\x0f\xbb\x1e\x98\xf2G26\xd6\x82b?f\xf0\xd5h#\xf6\xdf\xb5M`\x89\
+\xaf,E\x10\xa5\x98\xc57\x91y\xcd\xe6\xf0\x82\xd0\xea\x93\x168\x8d\x0e\x0e\
+\xb3\xe1\x94B4\x12\xb7\xe1B\x95\xacInz\xdd\xa6\xa0\x18\x18\xf9\x9b*\x01\x03=\
+\xf7\xfdv\x99\xc2~k\xd0b>\x9bpXsH\x88\xfa\xa1[\x1e\n\xc5_Yt\xa9\x8c\x16\x8a\
+\xd4i\xd3\xcfaw\xc3\x9flN\xb1X\x01\xb8.\xfe\x01B\x88\xb2\xafA\x82\x84~-\x8fc\
+G\xae>\x8beC\x9f\x98:\xe1#\xba\xd5k\xb9g\xac\xee\xf9H\xeb\xbcR\xa1L1\xb2\x81\
+\x8c\xce{\x8c\x05\xf4\xe5\x88\xc63\xc9\x1e,f\x06\x97\x16\t\xa4{XL\x01\xee]\
+\xd4\xa7\x8b\x12!\x0b\xba^\x97_5\n\xa1\xe2\x9d\x91?0\x0e\x17v\xd2X\xba\xbe,\
+\xb6b\xbf/\xd7\xb0W\x92\xdb\xab\r\x08\x1f\xc4\xf9\x84\x14\x9aZ\x81\xc4\x92x\
+\x93\x84P\xa8q\xac\xac\xca\xa1\xe4\xd1]{\xf1\x04\x98\xe9\xfeo,\xc0\xb3i4\xd3\
+\xc0}r\x88\x13\x8d\xff\xc4\x15e\xad\xb7\xf1b\x0b\xc4\x1b\x14\xc0\xdd{\xd0UY\
+\xb7\xdb\xbe\x81\\\xd3S\xbf-\xceK\x1c\x91v"\xa1$Un\x14~\x0c?p\x16\xe1\xdb\
+\x8a\x82O3\x94\x82\n\x04\xd7\x0c\xfe\xfbM\x8e!#\x8a\xc0\xf4K\x15\xc2\x80\xac\
+\xdf\xd9\xffh\x8a\xc4\xf2\x94\x162\t\xd1E\x05\xbc\x1a\x9d\xba#\x8a\x15\xe6\
+\xb0\x9f\xbd\xe8\xcbJ|\xba\xe0@O\x90G\xd6v\xcf\xe9$<\x12\xe8\xf4y\x94xwY-\
+\x86B\x96\xe7\xc6f d@\x1c\xae_WrQ\x89V\xd3\xd9\xd0\xfe\xe4O\x9e6\r\x13\xa1\
+\x1e\x7f\x10\x08Q\x10\x1cnA\xde9\x0e\xc58\xc0\x1d\xc5\x8e>E\xcdX\x8f\x97\x15\
+CSl\xf2\xad\xce{\xc8>\xa1@\x08\x80e\xf4k\x02\xae\xb4%\x03?\xd9\xd9%%)u\xa2C\
+\x9aJ5]\r\xd8\xe1\xce[C<\xddq\x1f\xd0\xd54\x1e?\x9e9P\xfb\xe8x :\x87\xdc\xf4\
+\xbb&\x10\x97f\xaf\xaf\xf0\xf4\xb0\xc2#\x04\x06_\x95\x1d\x8aA\xdd\xa6\xa0b\
+\xa7\x81\xb5\xb6\xe3a,#/\xa4\x90o\n\xab\xc7d\xdc-\xd9\xc2\xdcZg\xb8\xa5e\n\
+\xba\xc3\xb3\xd7\xb4/%\xf5\x1c\x96K\x83\x95r\xa0W|{\xfa\x94\x00\'4\xcb\xfc~\
+\x01\xfd\x95\xf3\x0e\x92+?C\xb6\x0c\x9d\x87\x95\xeb\xf0c\x02D\x02\xff\x88!\
+\xf4\xd0\x15\xd2\xdc\x0f\xcf!~J\x07i\xa7\xf4\xb9\xee\x96\xea\xfaD"\xed\\V\
+\xcc\xb6"\xfd#9\xe7k\xbe\xa7\xbcE\rK,"`j\x94\xa2\xc6\xef\xc8zI\xc9\xb6-\xf6\
+\xd4[\xc8=f ,R\x12\x16p\xd1\xc9\xf2\xe9\xfeq\xe6J\xe2!\x80I\x86\x01\xe59\x81\
+\xdb\x07\xff\x9fH\xd3\xd4\x07!\xa1\xc6a\xb4?\xde\'\xd4\xc6\x9d6\xce\xa5\xa9^\
+\xe3tR\x90\xce3d\xa3\xa0\xe1\xec\xb7\x98\xfe\xc5\xa5\x1bu\x88\x89\xc2Uz\x81T\
+$:\xd6\x00`\'AuZk\x9e\xbf\n\xa8B$\x15\xdc-\x81\x1d\x88:\xc4v"\x86\xb7\xa3\
+\xee\x03\xdf\x19\xc3hI\xc5Q\x99\xabM\x0b\xcc\x91\x05B\xc3\xf4\xba\xc8\x8d\
+\xd7\x8a\xda\xd8\xfa\xad1\xd1\xa9\x11\xc9\xff~YL\x87*\xc2\x19WTB\xd4\xf3Yh9x\
+\x98\x1e(\x01\xd4\xc1\x99%\xcaV*\xc8\x97\x7f\xe0-\xf7\xc9\xc5\x11\x86\xaaL\'\
+W\xc5\x9b\x02\x1a1J\xd0\t,!=\x0b\x0b=M\xeeQ\x90[\xa6\x81\x8a\rw[\x00\x15\xcd\
+L\xef\xd3C\xf9pD\x7f\xc3\xa9\xf9\x88\xb5\x9b\xbd\xff\xd9E>\xd0\xcaw:\xfar\
+\xfa\xa00+e\x14s\x91y\x8cl\x08+\xa0\xec\xde\xd9\xf4\xdb\xb2\xf5\xd1gY\xbd\
+\xa3\xf1\x08\x0c{\xa91\xb2\x837\xb62G\xae\xab\xe0\xc3\xee\x9b\x01\x9f\xb3\
+\x81\xee?t\xe4\xb1@"\x90\xbe\x04g7W\xff_\xac\xd9\xef\x8b\xf2\xd3-\xba\xc9\
+\xbb\xc6h\xfel\xd4\xc2\x07\xf2\xd0\x11\xda;G\xf3v\xc2l\x1c\xe2\x9e\xbd\xcf:\
+\xe61\x9e\x95\xf8@\x0b\x15L\'j\x9d\'\x9f\xaf\xbfz~H\x90\xa9\xc1\xdb\\\x8c\
+\xa1\xf62e8\xe6\xc4\xef\xbeF\x80\xf8\xffV\xfbN-]U\x84\xab\x96S\xa8\xc5\x98_\
+\x9c\x96\x0bg*Z\x16\xd5_\xa8\xd3\x18I\xa1Fo\x052\xd2\xbf\xb8\x95\x8c\t\xde\
+\xad4\xff\x19H\xfb\xc3\x8d{\x18x\xbd\xc8\xb1\x00\x0f\xc1\x98\xfa3\xaf\x8f\
+\x1f\xb3^n\x9e\x08\xae\xf1t\x82va7\xd9\x07\x8a\xc4\xdb\xb11\x83fg!"\x14\x93|\
+4\x94\xe9\x8d\xd4\xdb\xcai\xdf\x1a\x87q\x19\x8b\x8f\xb69\x97~\xaa\x08;Q\x84\
+\x99\xc8\xdb\xb9\'\xf8\x95aS\xe0*#*\xf3+\x10\xa4\xcfE\xeb[0\x95\x8c\x18x\x08\
+#\xdf\xeamz\x17@\x1aF \x9d\x7f\xaexm\x13XS\xff\xaa\x0b\x0e\xae#\xe6\xd8\xdc8\
+\xe2/\\\xe0$\xac"\xb8\xcd \x0b\x96j\x14?\xc0\xc9b\xeb\x0f\xcb\xceE\xb1\xbf~\
+\x15\xa4\x12\x89\x971t}\x89\xbeB\xae\xcd\xa8\xbe\x96\x92e\nI\xd8\x07o\x18\
+\xac\xaf~\xda/\x08$\x08\x07\xe2\xc1\'~\xefM\x17x\xcb\xcfN\xda\xbd\xf5(D\xf0\
+\xe7\x9d\x89\x08\xcf\x10l\xb1:\xaa\xdfDO\x82\x0f\x97%f\xc8\xb1;;\x7f\x9a\xb2\
+\xbf\x08,\x0f\xa8\x93\xb3\xbe\x9d\x07\x87=\xcdC\xea\x84"\xc4do\x9c\xac\xde\
+\xc7Pd\xd1E\x18\x0bh\xf5\xd1G@\xa02\x87~\xe5\xab\xeb\x0c\x1d5\xdfCd"\xea\x9b\
+\xc97\xf4Q=/\x87\x19\xac\xfd9\xf1\xe7\xa8\xae,\x1fC\xd8\x96\x10cc\xd8nG\x13\
+\xe86\xd3l\x17\x15-\x06KC\xe2\xef\x1b\x1e\x97\xef!\x9b\x0f\xab\x04\xbc\xf3\
+\x12\xe4\xd1\x97w\xdb\xcf\xd9\xd8u\x1f\xaf\x1e\x0e\x8d\x03\x17\x07\xe6\xe3MU\
+!\xfb\xfa\xe9};\x0f\xc4\xf9c\x05\x88rY* \xf5\x81\xb3X\xa7\xea\x14\x0b\x9c\
+\xc1\xe0\xb1\x1d\x93\xb3~\xbas\xfb\x96\xeb\xf3Hiq[\xbf#z\x86"\xdc\xc8\xf4t\
+\xed\x14\xc2\x0c\x96\xa4\xa4\x94I\xd2\x01M\x87\x86\xa1\xbb\x1c\x9a\xa0\xb5\
+\x07\'\x92\xaa\x9dk\x15?\xe0`\xbf\x05\xfd\xf4\xef\x97F\xf1\r\x16\x80\xa8\xb8\
+\xcc\xadcz\xd1\xea\x95\x1e=,\xf4\x86\xdf~\xf8\xeb\x7f\xe0\x18<`\x12DS\xbe\
+\xfb\xb3fUo\xa4\xde\x9c\xb5x\x7f\x16\xe8U\xd6\xb2\xdd\xf7\x1f"\xa5\x880\x97\
+\xcdP\xfd\xeb\xeb\xb7J\x0c^\x8d\x90\xa6\xf7\xbf\xf0Z\xf7\xa0\x97\x1eR\xc2\
+\xae\xa8#\x1bj\x14\xc9\xfa\xedm\x89\x06@\xcb\xf5\xa7\xeb\x90^S\x19W\xc3\x97\
+\n\'IX\x91\xc5\x84\x1c_\xbe\xec\xba2\'\xd4\xf8W\x94Zb\xe7\xe7*\xab\xee\x12"\
+\xb6\xa8[V\x91\x17\xdf\xdde\xbdzC\xc2\x05\x1f\xcc\xbct\xf4,\\R\x0e9\xbb\x98\
+\xd6\x8fp\x03\xe0\xfc\xd9\xf2\xcf\xfeS<\xaa\xc4\xb7\x1eR\xd1\xde\x9f\xf5\x8f\
+\xa9\x04\x03\x11D\xb9V\xcb\x0b\x85\xa0\xa5\xf9\xac\x978I4\xa9\xaa\x93m\xfd\
+\xaeQ\xfd\xcf\xdf8T\xe2\x97\x8c\xbc\xf1\x8dS\xfd\xc5\xae\xba\r\x14\xb1\x01\
+\x900x\xeb\x7fX\t\x95\xc2u\xc8\x18\xb0]~\xc5\x1cq\xec\xc1\xf4\x11h\xfa}d\xc1\
+e\xbb\xb1\x05\xd2\xf1:,\xd5\n\xfe\xb7\xab\xf6\xdd/-~\x98d\xc3@}\xa3G\xcfar{\
+\xa1\x13\x11\xd5gk\xcd(`^p`\xc7\xb6\x13\xf6\xb0\xe2\x88_$\xe2\x80\xb2\xaf*\
+\xb5\x1bl\xe36o1\xddIF\xf7\xfe\xe9\xcepr&TjVT\x81\x81\x0e\x8eTG\xca\xf9\xcb\
+\xc1\xbb\xe7\xe7\x98\xae%3\x1b\xdd\xae\x1a\xe3@o\xb2*\n\xa6aI\x17\xa2\x96W&\
+\x068\xe0\xf6\x90I\x9d\x0b/<\xc4x\xf9U\x1e\x9eV\xd0\x7f\xbe.\r\x9aH/\xfa\xdc\
+\x1e\xbe\x18G4.\x90\xc9q\xec\x1az]i^\xea\xc7\xb8\x0fm!\xc1R<\xab\x0f\x9d\xa8\
+\x9b\xec\x84\xcb\x0e2\xb8`W\xe0\x83\xe2\xba\x1fB\xff\n\xdbVt\xf5\xf4\x87\xdd\
+\xc5\xb3\xe1\x0e4\xb1\x08>\xeev]\xc1PP\nA\xc4\xdb\x8a\xf5c\xfbC\xc2\x16\xd2\
+\xc9l,\xd7s\x92\xf8\xb3\xde.\xb2\xbcn\x18\x0eA\xe9UZ\x920\xfeq\x95^\x9a\x18o\
+\xc0\xdb5\x1a\xe1\xa0F\x1c\x9fkGamTmnd\xc9\xbcQ\x0c\x88@P\xcd\xa8P\xab\xee4\
+\xbf\x98\xcf\xdcL\xa3"!\x00\xc0\xee\x8d\xfd\xf3\x0b\x9c\x85\x1f`\x92\xe9?\
+\xc3U\xc6\xff\xa9\x8dp\'\x87\x1e!\xd2z\xc9\xdbp\xa5\xc2\xe2_Uc\xf9\xfa\\\xc1\
+\rpi^\x8c:RDN\xc9i\x10\x14 \x03)\x13\x92\x7fG\xe5\xe40\xa7oj\xce|\xca\xda\
+\x80\x0f\xeb\x8d\x92\x08\x13\xf6\x7f\xc8|\x0cD\x1e2A\x0cRU\xf8O\x19\x89\xaa\
+\xbe\xb1"A>\xdc\xefw]o\x1cdo\x0cg\x1f_\xb42\x13\xf1\x1b\xe3\xe5E\xfd\xe7\xf2\
+<\x8d\xe4GL\xd8\xfe\x88\xdc\xca\xf3\x9c{8\x9a\x11\xe2\xbd\'\x98\xc7\x04\xe4\
+\x08q\xc9\x91\xc7\xfa\x15,\xab\x16>\xda\xe2\x019\xae^y\x85m,=\x0b{\xe0K9Q\
+\x18s[\\\xbe|"\xd1\xcbF\x11\x1dEi8u\xe5`\xbf\x80\xc8\x1cMn\xb5\xd3\xba\x1e\
+\x8c\x88aY\xc4\xf1\xbf\xaf6\xb5\xe0/\xe7\x01\xc4\xcb\xee\x11\xd0\xea\xdc\x98\
+\xcf\xe6`Gt\x90\x94x\xfc\x0fN.\x93\t\xafi\xe27\xf2_V%X\x85eb\x8f.\xd5Z]\x1fN\
+\x04\xff\x88w^Q\x80\xf5?x\xcd\xad\xeb\x82z\xa1\xb4\x1a\xdc\x1b\xd2\x83\x93*#\
+\x83\x87\xc2r6\x83\x19{PFL*]g\xec\x1f\n@x\xb4\x19F\xef\x15\x8d~\x83\xe9\xc2&\
+\x02F\x04\x88\x92\x19\x10\x87;\xb7T\xa7S\x90\xf0aU\x12|\x00\xc3\xf5K[\xad\
+\xb1\xed\x04\x8c\xc2\xd1\xaa\x08\x97f\xaeR\xea\x18\x94`\xdf\xff\xa0\xad\xe5\
+\xf2\x14\'\xc1m\xcb\xfe3+\xc3\xc4\x18\xdc4\xfa\x9f\xd7\xf6rt\xead-\x869S\xc0\
+\xdc\x13\x16\xaa\x03Y[\x87\xc1\xcd\x9d\x1bN_\xc5r\xcck(\x83\xd7\xb1Q\x13\xa1\
++e\xadt?\xb8\x8e69\x9c\xee\xdb\x1b\xc8\xb8;3\x027|l.6\xce\x90,\x1f\xe4D\xa8c\
+:\x84\x12<{\x00@p\xef\xd3\x8b\x89t\x1b\x0f8\xdf~yn5\x98\xe4X\x0b6\xca\xd9\
+\x90\x92l~\xa8\x0bN\xa4\xcb\xd9\xb8\xeb\xb6\xef\xa4\xafa\xa5\xf0\xcf\xf1)<C\
+\xf3q\x16K\x8ce\x07\x91\x7f\xe3@\x18|\xc3*\xd2[W\xbb\xcd\xd6\x07lD\x041z\xb0\
+\x98h\xb7\x9fUQo\xf4N`\x0e\x15R\xe9@\x15\x17\x1b\xab\xb4\x06\xa3\xce|\xea\
+\xa2Aju5\xf1\xfa/4\xf1?\xfb\x89\xe5\xda\x90\xb5}\x88\xa6\xf6j\x0bK\x0e\xb30L\
+\xf4\xa9\x90\xfe=\xba\xc6m\xb6\xe3\xf9\x8d\xbc\xc1\xe7\xc8\x8a\xe1\xc9\xd5^7\
+\xe8\xaa\x08\x98a\xee\x9a3\xc0\xa8)^.\x14i\x80@\xbd\xe3\xf0\x1eC+\xe1\xbc\
+\xa4\xd3\xd0\x96c?\xcc\xce\xdd\xb6vYm\xf5\xcb\xfed\xda\xd2\xcf\x9f\xc1\x13TA\
+\x1cE\x9d\x82\'<Wz\x17y\x08\xd7o\xad\x84Q\xa7\x95\x03\xc7\xf3\x08\xd8\x9aE+\
+\x8f\x0c-\xaf\xb7[d\xf6\xab\xf7vq5\xd9\xc4\x81p;h\xe5y\x9d^>u\x8b\x81{\xd4\
+\x02\xeb\xec\xbb\xaf\xf1\xb5\x14\x80\x04<\x17\xe5#9\xc8\x11\xd2\xdc\xaf\xec\
+\x844\x17\xfeu\xd2\x93E\n\xfa\x1czC+P?}\xa6\xf4\xe2Tl6j\xb6\xf0\xa8\xbc\xb8}\
+\x833:;w\x0fc\xde\xd8\xf9\xd07H\xf9\xa9\xce\xf6|\xc7Hs\xfa\xeb\x92\x04;&\xeb\
+\x0f\xef\xb7\xfa\x91\x1c\r\x81\x82\xbb\xb3\xa7+\xf5b\xd0_\xbd\xf4M\x01\xef\
+\xb5"\xa9\xbe\x83;\xd7G\xd2\x0b\xed\xe6G\xff>\xf1\x9b\xe7\n\xbd<\xc5\xcc\x15\
+\xd6N3\t\xac0j\xaa\xd3\xaf\xa1\xc3\x00I\xdcX\xb4Y\x98|\xfc\xc5\x00k\x03\xa1\
+\xf7\x8a\xf3\x115\xc8\x16\xca\x86\x0e\x03Z\xff\xa9\xcd\xcf\x03BW\x82\x8e\x0c\
+:\xdd\x02\xc8\xe3\x02\x8f$\x84\x87\x90\x89\xb0\x82e\xba\x86W\x87r\x88\xffx\
+\xab\xcbs\xda\x0e\x02\x9f\xc7x\xc5\xdc\xf6\x97\xbf\xfbt\\\x18\x1b\xd1V\xf6\
+\xa6\xae\xdf\x1di\x96\x1d`\x07>\x04\x18\xffo\xce\xf5~\nn\xc9+/\xbe\x04C|\x98\
+\x04\xbe\x96O\xb0\x16\x18q\xe7U\xfb\xfe0\xb0}\xc1\x00\xb3\'2\xc9\x98s\xc3\
+\x05\x976\tFX\xf1\xf1v\xeb\xc0\x0e\x83)q4\x1a?:\x9eO\xe7.\xd0W\xf9k\xf9\xf5\
+\x14\x13\x94\x03&E\xaf]\xe2h\xd4\xc1w\x19\xde1\xf8\xf1C\x0bF2B\x8ec"\xf7~51\
+\xc6\x8c\xc4P\xbeG3gE\xc0E\xe3\xef\x8ef\xf6\xd3\xf4\xe2o\xb5\x1f\x89\x90\x85\
+\xcf\xba\x9e\xe7\xb31vg\x0f\x1b"\xfak\xef\xfa\xa8\xb5\x1d\x90\xda\xaf\xe1\
+\xc1\xfc\x845\xda+\x05\xac2#Q\x8a\xd8\xef\x0f\xafz\x90\xe6\xf7\xca\xcc\xeb\
+\xa8N\x9a,Vt\x7f\\\x11\xdf\xf24\\\x07\x12%\x93h8\x97\x124(,*\xb4\xba\xf2\x9b\
++y\x98\x1c!\xe1\x1b^gK\xe3\xaa\xba`\x17\xf2*\x97\xcea0\xa1Dj\xaf\xcb\x8f\xb9\
+\xa2J\xa3\x89\xb5QD\x1aL\xd9A\xc5]\x93\x80\x88\x947#Ye\xe5\xcbG\xfe\\r\xd6*\
+\xa9\xcf\xd3\xa5\x90\xc5~zd\xb9\x9ey\xf8\xb9\x8e0\x7f\xcf=`#s\xf6<Q\xdf|\xb2\
+\x06P\xd6\xe3\xb8<M\xb3z\x83\x98\x13\x86k&\xd2V\x0bk\xcd\xd2\xb3Z\xae\xd6\
+\x131\x98p\xce\xd9n\xb9a\x12\xee*\xec@\xf0\xb3\xb4MOjY\x9f3\xff\x84>\x98\x02\
+\x05\xc04\x9cN\xfe\xad*\x996\x90\x80\x9ef\x8a\xc0\x0b\tP\x1c\t\xc5\xe9V\xe4\
+\xb6\x91p\x99v\xa1\xa1,\xd0\x81!\xa9\x9d7h\x19<\x97\xeb\xfa[\xf0\x19\xca\x8e\
+\xfaX\x1d\x08\xe6\x06*\x83=\x10\x17\x93\xfa\xff:f9\x80s z\xca\xd6x\xff\xc3\
+\x188\xe92w\x80\x06\xcd\xa1\xe7\xf9\x0c\xc5\xd9\xd6o\x00\xddK\x81\xda\x17\
+\xbe\xa1\xbe\xca58\xe4\x08\x9e\xebR\x1dh\xee\xee\xb88e\xb2\xd1Y\xb9L|~.\x8f\
+\xa0\xd7p\x9d\xf5\x9bL\xb4\x08P\xbb\xbb2x\r\x08x\xea\xf0h\x11\x85gA\xc7\x0c\
+\xba\xb9\xc1^\x87\xdf\xd7\xd3\x95\xa4\x11V\xc6\xb5\x8a.n-\xbf\xddf1\xf0\x7fQ\
+!\xa3\xbf?=$\x9cB\xf3\x8e9\xbcK\x0b\x84\xce\xc571\x8auwZ\xc2\xbd\xf4>\x14\
+\xf1b\xd9\xb1\xf5\x19\xee\xb0\x9cT\xec5e\xadH\x87\x12\x8fgBY\xb4\x15\xf2\x1d\
+\xb7\x1e\xe2YD\xc8\xe2)\x0e\xf2t\x1f\xe3\xb9z\xb1Y\xbe\xf9\xf4V\\\x8b>\xc9E\
+\x90\xcbQ\x85O\x8bA\x9c\xf8\xf3427\tw9|\xe2\xf5ndA\xe2\x0bs,~\x18\xf8=B\xde\
+\x82-\x8a\xee\xab\xf9\xefdn\x15"On\xa81|Oh\xa7\x9b\xd9\xebM\x86\x19\x1dt\xc5\
+\x93d\\)\xe1\xe3?I\x88\x01\xff\x01\x19<\x96\xecO7\xbc\t\xa3$\x8f\xc4\xdba\
+\xed\xa9<\x1eK\x0b\xf3\xdbk\xc2C\xc5gg\xd6"\x936^\x86\xb5\xb7%\x96\x8d\xf7#\
+\x0c\xf1\x0b\xf3\x9f4\x87\x9c\xed\xea\xfb\xbeAu5KL\xe9\xf3\x8c\xb0b!\x08\n\
+\x8c\x07&\x95\xeb$:\xf5)M\xff\xe1\x16\xa7\x86\xa6)\x1dbir1\x16s\x97\x96\xe3\
+\xe8s9fX\x14/W\x10\x15\xe4\xd3\x0cK+l\'\x15\xf5\x93\xa4\xf5\xc3\xfd\xb7+YJ\
+\x94\xb3\x96\x8b\xf5\xb95\xec\xd3\xfe\x1e\x89\x10\x9e\xee\x9b\x8ezE\x17\x15\
+\x84\xb4e\x90\x12\xe2\xb3{X\x06\x86\xac=\xe2\xd6\xdbuhs&_\xf0\x7f\x1d\rX\xe3\
+(F\xf1\x92\xd7\xb7\x10|\x9bF\xa2\xde!r\xf5\xa9~\x1b`\xc2=\x8aw*\xb3\x07\xd9\
+\xcb\xcdN]\xde\xcboi\xdb\xc6{\xd7\xe8\x96\xb8Y\x94\xcf\xaf\xd3\x88\x97\xf3\
+\xa2\xb8p"\xf3\x06R\xd7|\xac \x99\x7f\'\n\xff\xb6\xd8\xd8\xdfN\x0b\x9b\xe0\
+\x80\x92\xf6\x8d :\xe2\xf9;`\xe1\xf54U\xbe\xc4{V\x1bC8o\xdd\xf3\x1aN\xda\x93\
+;8xt\x9e\x00\xb84\x9a\x97\xa1\xb1\xbf5\xe7\xe2=\x1d\xf5\xf6\x95\xc6\x13D\r\
+\xda\x9c\x9d\xc2\xbf\xabA@\xd14D?\xe0\x907~\xcf\xee\x93\xbb\xb9\xfd\t\xc0m\
+\xf6n+>\x89\xa1Tw\xa1F\x7f`]\xf5>\xde\x9a\xa0\xadk\x04\x0bq\xb3\x8ay\xd0\x8a\
+K!;^\x14\xc8\x92o\xd7\xb2J\x17\xae`\x8e\xb7\xb2\xe8,SW,\xeb\xf3Q\x91\x1aI\
+\xfbB%\xb7\xd8\x89\x07H\x95?/(\xe2\x02\x17Ee\n\xc1\xab\x9f\xe2\x1bh\x9f\x8eI\
+\xe3\xa5\xbdb\xfdL?JQ\xe8e\x8a\xe1\x1c\x8e\xd0xB\xcf=J\xa1]\x0e\x9fK\x08k\
+\xc10\x08\x12;\xb7\x91\xe9\xb2\x04v#\x13\x1aK\x07g\xae~_G,\x90|.\xc6\xfda\
+\x9b\x0f\xe0*KI\xf4\xd8\xf0\xa7p:H\xd9~2\'\xf3X\x9f/\x14\'d!I1\n\x14\xe7\xd7\
+\x7fK\x1f[~\xeeAZ \x8fA\xbc\xb4,f\x1d\xa6\x94\xb7\xe9\xf4\x04U\x88\x1e\xc6\
+\x8b\xd6K\x15\x997\xe0\xd4\xd0\\\xbb4\x82\xc3\x88\xd2GYlY;\x99\xa36\xfa\x0b\
+\xbd\x16\x08\x00\x8a\xf6xjb\xbbB\xce\xa8\xe1\x9d\xc4\xbf_\xd6\x11\x82\xe5M"\
+\xf2\xb8\xc5\xd6T\xaf\x80\xec\xde\xbcM\xc8\xd1\xe5\x0f\x0e\xcc\n\xe7\x04N\
+\xebp\x1e\xc8\x075c\x1d-O\x04#L%S3U\xb8\x93S\xe9\xd4\xa6?V\'4\x1fE\xa5gDPU \
+\x07\xf2u\xdc\x08\x8b\xabR!\xd7\x93\x01 \xf0p\xa639=\xf7Y\xa1B\x96\x94\xa9\
+\xfe\xda(\xc3\xf9\xb7\xad\xad\xc18\x81\x1b\x07\xe8\x01\x89t\xc4A=\xe2\x1a\
+\xf3\'L(\xa4\xbeo!\xac\x10N\xb1\x94\xff6\x84\xfd\x1c\xfc\xbc\xd0\x98\xdc\x9f\
+dJ\xccY\xf5I#C3\xda\xaf\x8eX~\xdb\x08\x8a\xb9Ct\xae\xd4\x9d\xeec\xb6v\x94\
+\xd4\xb4aP\x870\xe2\xd3\x8fT1\xa7"\x02\x8d\xde\xd0\x9e\xbc\xf7.\x81VU\x95\
+\x8a\x9f\xa7\xca\x06\x9a\x9a\x17\x05\x889/\x910SyR\xc50<\xd2#\xb7\xfd^[\xce\
+\x9b\x1d\'&\xc62h\xc1s\xa2\x97oq1:na\x15\x1d\x96?\x19\xd0K+\xe7S\x9e]M3p\x88\
+eK\xc0\xeb.\xfb\xb8\xf9\xfaHJ\xf1\xf1\xc5\x06t\xd9\x7f\x89\xd9\xf6&t\xc7\xaf\
+y4\xa8\xebW~^\xef\xb2\x96t\xfe\x00\x18{_$\x7f\xf1\x16<\n@dk\xf4\xb8\xff1\xff\
+\xb8\xab6\xe0\xad&7\x9b\xd9\xb5/\x13\xe5\xbe|+\xfd\xd0\xab\xc4SJ\xa8\xd0\x18\
+\xbb\xae\x9cjo\xa7\xb7\\\x10]@\x88\xb0uV\x9b\x1d\x97\xe10\xff\xde\x95\xae\
+\x17h3TQ\xf4y\xea\xf4U\x1d\xe9\x02H\xb6$\x93\x9f\xe9\x97\x91\x9fZ\xca\xf0\
+\xc7`\xbd\xab\xdd\xfd\xd7\xe6~\xf9C$:\xf5$\x15\x96Z\x93)k\x1f0\xd40\xf82\x0c\
+\xb7\'+\x07!\x81\x91\xae\xdbh\xbf\x96(\xff\xab\x0c[\x0e\xb8r\xa3\xff\xf5\xe0\
+\x95\xc3cZ\xcc\xc9\xc8\x1b\xf4\x82\xec\\\xd7\\\xbd\xb7\x99k\x97GK\xdb\x94\
+\x1c\x1f\xf75\xbb[\x9eD@\xb9\x0f\x0b]\x1b\xfc\x12\x7f\x8b\xe3\xea\xf8\xc7O\
+\xa9\xe7\xcb\xe1\xe8\xb6f\x8f\xec\xbd\xd4N\x9f\xb5\xa6\x9c})\x9aM\xc2\xcd\
+\xa1\x89\x11z\x10\x8e\xd2\xe4\xc4\xf1e\x9b\x9f\x05\x8b>\xff\xe6\x13\xad8\xd1\
+\xcfgx\xaef\x93\x13\xa5\x95\x89\xd5\xc7\x85iI\x1d\x7f\x010B\xf8\xfc\x92\x85w\
+;\xb8\xe1\xf6p\xf4\x1a)\xf6U\x1a\xec\x93=.CO\xc4\xc9c\xde\x97\x0b\x12\x10\
+\xe7\xb8\xfc\xce\x98L\xbaL\xa0\xf9\x15\xca\x8b\xc6\xbd4\xc9\xddh\xff\xfc\xbc\
+\xdaRk\xbb\xee\xf7e\x19{\x86->\xdfI<\x9e\xbb\xee\xdb\x1e\xd6\'a\x08L\x89Y\
+\x9e\xf0\xcd\xabo\xc0\xf7G\xf6=\x88\r\x0e^\xdc\xec\x82\xc2a\x08\xcc\xe5\xe6\
+\x99\xbe\x14\x9eP\xce\xe6\xd4\x88\xe9>\xfd\xf7\x06\xa0\x95\xe0\xf2\xce\xb9\
+\x1a\xbd\xda(\x95k\x8bw\xb49D\x01\xa8\x07u\\T\xdcAp)Z\xc8\x04*\x19\xba^\xf4\
+\x8d\x8a\xc0s\x85p\xc4\x80\x85\xcd\xb5\xe9E\xc5\x02\xe3\xa8.\xaf\x95;\xbf\
+\x9a\xe5q\x14\x7f"\xa55\x15[\xf3\\|\xbdy\xf9a"c\x04\x99\x8f\xce\x1c\x8c\xbe\
+\xa1]\x9c<\xb6~|$\x05(\x82\xf1\xabv1y\xf4\x1cW\xb3\xd8\xf2\x85(9\x10H*IAV\
+\xa6e\x10\x9f1\xed~\xf3kK\xa8EN\xa0h\xbd\x1f~\xde\xb4F\xc7\xbb\xf6\x81Dr\x00\
+\x99d\x1c\xedD\x1c\x08\xb2\x90\xdc%]a\x0fcuN{hz\xce\xbc\xce\x8f\xe7\xb4K-\
+\x9c\x8e\xb2\xd0\xfd\x147\xc6\\H\xd8,\x18~\xb6\x87\x08\x19\xe8\x13\x87\x8b\
+\xe7\xc5T\xe1\xbb\x80\xd7F<\xa5\x95t\xa1\xf9\xe3[?D\xe6\xd5\x9c{\x8b\xb3\x1d\
+\xe9>\xd9y:E;\x92\xad:2?z\xe4\x97K\x8c\x1b\xf3HQ\x86\x8d\xeb\xb3\xa9\xaf\xd9\
+2\xf0\x1d\x84\'\x1b\xa0{\xed\xf6\x88\x85\xf4\xd9wbC\x95L\xfdY\xe5\x0fA\xf9\
+\xf3\x1f\xc3\xa5o\xc1\xc6\xef?V\xda?\xcb\xd3l\x7fj\xcd\x04\x9c1d\xd6vo\xd3\
+\x98X\x1d\xe2\x1e\x1d\x1f\x08r\xa6J\xc6\xdc\x12\x14\xe2\xc3/\xa7\xb9:?\xd8\
+\xd0\xfcm\xdf\xd3\x84\xd7y\t\x86Z\xc0\x0b\xee\xe5\x10>\x85\xa4\xe6d\xee2\x1d\
+\xc3\x1d\x94a3\xbf\xbe\xd6W\xc2\x10\xae\x13\xdb{\x99\xa5\x8bf\xc6\x932E\xe47\
+\x92\xec\xab!\xa8\x92\xbfAv\x8a\x0e\xa7\x86\x1e\x8a\xad\xca\xd9\x14H\x94\xd9\
+\xe1\xc4F\xb7\xb5r\xeao:\xdc\x19Q\xdb\xd9\xbbd\x900\x93gg\x87\x15\xd6#P2\xf0\
+9M]\xd6o5d/\xcf\xc1\xbaNH\xe1\xff\xbc4\xf0M\x1a\xcc\xeag-D\xeb(#S\x97\xba\
+\x00\xbb\xc5\x0f\xa4\\\x9c*\xda\xbd\xbd\xd1xyjh\xc4\xd8j)\x1e\xd0l}\x81\xcfp\
+\x14\x99u\xc7\x93\xb0\x15\'e\xd8\xa4~D\xbb\x1b\x0fN\xd1\xfc\xb4\xbdc\x15\x1d\
+|@*\r\xe6N\xc1\x89V\xf8\xce\xd0\x96d\xc02$\x89\xb0\xd6\x16\x04u\xd0\xf0w\x92\
+U\x19/\x06\xb4\x99\xe1\xd1\xd8\xac\x83\xeas\x91\xb3\x85\x17\x8cO\xea\xe9\xe2\
+\xdd<\x10\x8c"\x13C\tT7=\xd0Yp\xacC\x04\xd8\x10\x0b\xeb\xb4\x1dL\x0b\xdc~ a\
+\xa5\xb4\xbdw\xba\xb2\x99\x1b\xf4\x9f\x04\x9b\xe9|uL\xbb\xa94\xfd"`\x16|\xba\
+b\xd5\xfa4\xb7\xa8\xe3\xc6w\xd8{7\x98$\xfa\xf0\xfb\xbb\xade\xdf`<N\xc3}\xd9I\
+1\xe0\x02\xc0\x0eD4-5\xac\xb27\xd6\xea\x1b2\xfd\xe3\x97\xb8\xe2\xb8\r\'\xecu\
+\t\xdes]\x90)\x8e\xa9\xec~\x8f\xb9\xcb!\x02\xf2\xc3yK\xeetj\x99\xcf\x16\x102\
+\x81\xeeu\x88\xca\xce/~A\xc8T\xb4\xd5:\xe1C\xf4$\xddD\x1b\xc8\xe8\xf06\x8e\
+\x0cg\xdd\xb4N\xe1}0\xabW\xa8,\xf0\x8e\x87Es\xd9\xf5@c/O\xe0nx\xd0\xe7=\xed<\
+"\xa1<\x0cQa\x9d\x92QR\x16\xeeECR\xd9\xfduD\x04\xdc\xc8l#\x7f\xbe\x0e&%\xfc\
+\x15TBR\xfd\xa2\x99(\x9b\x99iW\xee\xb0\xeb\x1d\xb6\xfbEi?\xb6pL\xaa\xd7yq\
+\xf6$\xe4U\xc5\x82\x0b\xcf\x98\xfe\x8aSC\t\xef[\xe0>\xee\xf3\xe3\x0f\xde\x9a\
+:^\xcb\xd5\xa9\xe7\xd7\x91oWc\xb5\x0e\x02\x95tL\xd9\xfb\xd8p\xadnC\xde\xa0\
+\xf1\xabX\xef"VQ\x08\xdbd\xed\x14&C\x8b\xdf\xc5\xe5\x168<`\xecs\x96\xe2\x14\
+\xcf\x8d\xf8[\x10)\x95\x935\xa5\x99\xb3=\xec\x8dN\xbe\xca\\1\xb3\xaa;\x9a\
+\x8f\xe7A,\xcd\xcf\xdfp\xf3\xee\xf3\xb2R\xc68\xda\xbf\xca3\x18_xd\xfbd]\x01b\
+\xb8?Byl\xf9\xef{\xd4Y|\xc96\x07\xb0F6\x12\xe3-\xcc4\xa2\x08P\xdf\xe6j\xcct\
+\x84\xba\x1a?\xbe%.\x12\xf9n\xfc#\xd5\x94W\x1f\xc5\x96\xf8=\x03\x80BIS\xfeZ\
+\x02\x1e}\xc87s\xce0Ri^=\xbfS\x10n\xf9\x9e?\x93pDA\x03\xc8\xda\xf7Fo\x1dX\
+\xefv\x1ekL|\xdaI|w\x86\x0cl>\x9c0\xaf\xb6\xb7/\xfad\xfdnBW\xbf\xf43\xa3G(e]\
+u\x94h\xf5\xe5\xd2=\x10\xa6z\x04\xa8L\xd5\x9e\x06\x05\xdd \xd1\x1d\xde\xdaR/\
+\x84\xda\x04\xb2\xe6$\xfe\x94Fi<s\x8c#\xe6\xed\xde\xe2\xfb\tW\xb8\xc6\x82\n\
+\xa8\x91x\x11w\x8b\xcd\xea\xf0\xa8\x9c\xf0I\x93:\xb3\xe5\x83\xe2\xd6\xc4\x98\
+\x1b\xa9\xfe\xf7~\xc3q\xfe\xfd\x0b\xbd8;`\xa8\xdd]?\n"g$\x02\xd2u\x92\xeah;\
+\xfaR/>\xfa,q\nfCYf\xb0\x19)\xb3\xfb#M\xbe$\x8eTI\x03c\xf4?V\xfa\\\xc5%Y\xaf\
+\xe3]HD\xb4\xca\x97\xde!0\x84\xc0\xa7O\x85\xc5\xd5rS\xf6<\x8a\xcb\xfd\x9a\
+\xef\xf6h\xe2O\xbb\xbb6n\xb3\xbf\x0c\xe6\x17r\xfb\xe0M"\xe8\xd7\x8b\x175B\
+\x180\x06i\xcc\x89\xc8\xcb\xdc)\xe4\xfej\xf9 |\x12\x82\xbb\xb1\x92\xfa93\xb3\
+\xb8l\x07\xab\x95\xce\xa4\xee\x8es\xd3\x9b\x14\x87\xac\xe2x\xb5\xc3\x0cd\xe9\
+]\x08SS\xa4,W\xf1\\Y\xbez\x9b\xf4\x11\xde"\xb0\x8a\x1f\xf6\xc2\xe7\xdc\xe4KD\
+\xf8H\xe2\xb9\xb0\xed\xfexL\xfa\xec\x9d\x083t\xd6\x8a\x12\xd4\x1b\x7ft\xadA/\
+\xfc\x06\xebz\xdf\xd9d\xb6%\xb8\xe9\x19\x06l\xd1\xa0\xea\x0f\x02+\xf9\xa7&N\
+\xf5L\x86u}UN\xfa\xfds\xfdm)fY\xd1\xb5=vwI\x02\xe4\xe7Iz\xfc\x85\xceK\x03a\
+\x12\x8b*[\xa4\xc8\xd5\x08\x8ay\xf3\x7f\xd9\x0c\xda\xa84\xaa\xa9\xb4{\x1b\
+\xfa\xd8:;\xb5\xc3\x94\xf3YD\x8e\xc40\xa5\xe5\x8e_\xa4\x83-~\xb8\xee\xb7~\
+\xf2\xf6:I\x8dsyl\x0e\x94+=B\xf6\x9bn>\xda\xda\x98s\x8eA\xaf\xb3_\x90G\xc6\
+\x89\xb3`\xcfq\xffNuJ\x85\x83\xa9\x0e\x7fr\xba\xfeP\xa9H\xba:\xff"\xe1\x8a\
+\x82\xb7\x1e+\xb0\xfb\xe0\xbeW\xe7\x84\xa9\x89\x84\'>\xaa}g\xae\xa6\x99\xe4\
+\x7f\xb2Po\xe7\xe71-\\z\x1c\xb0]p8\x1d*$\xa5\xd7\xa0R\x07g\xa5\xb2\xab\xb5\
+\xb5Y\x9c)\xb4\x96}\xa9\x19\x14\x1eJ\xbc2\xdb^^\xef*\xe5\xe3"\x7f\xf8n\xea\
+\xfa\xfay\x1f\x9dV$\x86Jz\xb9>\xb7\x94P\xc6\x0c\xde\xea0\x9d\xc5\x12\x9b\xde\
+\x16\x88\xca8\xb4\x83\xf2\x0f\x90\x88\xdf/\xe2\xe4\xfa\xd9yr{\xe2\xc2\x85\
+\xcdb+\xbc\xed\xb46\x9f)\xd4\xd2\xe6\xb2W"N\xc7\xe6j\n\x04\xaaaK\xa2q\x86\
+\xa2\x8b\x14\xda\xd8]\xbc\xef`\xb2\xf8~\\\x1e-\xd9\xf1a\xa5\xda\\6\xcd\xe6z|\
+\x05\xba\xe9\xf24V|\xcf\xf7\xc8\x87\xfeH\x1eq\xd4\xc4\x9f&\xf7_Y[{A\xdb\xf2{\
+\xfc,m\xe9\xa9D\xa4G\x8f\x8c\xfa\xb5\x1e0u\xc8\xa6\xb8\'\xb5H\x9e,\x14l:\xac\
+\x03\x8b\x87+\xb3\x9dOc\xf0\xba\xb3\xa7C\x8b\x85bD\x17\x897\x11$\xfbc\xbe\
+\xd9v<\xe0!3naQC\x81\x80g"\xd0\n \xaeOX\x98s\x0fC\xa4\xd5\x8d8(&B\x9b<}\xf92\
+\x06\xce\xdeJg\xbb\xbc\xe0\x90\xeaj\xffxFZG\xfb268r\x04 \xcch\xf0w\x97\xdb\
+\xfe4Y\xa4\x0cB\xdc\xb8,d@s\x13\xcel$\x01\x80\xd29.\xc5\x9dt\x84\xb4\xf7B\
+\xc3\xb89T\xb9-\x06E\xb90\x8d\xec\xef\xf1\xc8\xf4\x86\xddl\xf8\xee\xedtP\xff\
+m\xcdw\xafO`\x91\xf1\xb6\xf8:\xa8\x03\xe5m\x0e$0\xe1,4\xb7\x1e\xff;gh\xceU\
+\xf0\xe5f\x94\x92\x04%\xd1\x1d;\xbb\x08\xad(\x95\xb6\x11\x08\xc7\xb3\xe04\
+\xcc\xbb>\xa4Q\x0b\xd1^rK\x19R\x9d\xa7\xd8\xc2\x8f\xf6\xd8\x83\xc7\xa0\xf2\n\
+e\t\x05\xd1\xe7\xf8_\x90\x86\xee\x93\xff\x084\xdd6\xb2ra\xd9\x89\xabF\xdeG\
+\xe1\x08A\xcf\xf6[CaK\xba\x08\xc7p8\xc4\x13\xceL\xa4Q\xe4\xe8\xb0z\xcd\x17w\
+\xd2\x9e\x06\xc7\xa7\xeb\xab\x1b\xedp\t4IVy<\xd4ya\xc0\x1fK\xd15\x99\xc2N\
+\x13\xfc\xe6\xd1\x1e\xad\x9dmsS\xe5.\x16\xabk-,>n0)\xb9\xed.;fw`y\x87)\xd1!\
+\xec\xf4B6\xe1,\xda\x1a\x98\x12\xa9\x93\xd3\x0b\x83f\xd0S\x90\xb2\xdd\xea\
+\x9c+\xe5\x02n\x89\xd1XQh\x04\xc5i\t7\x1a\x1aFi\xe5\xb0\x9d\x9d\xf5VWea;\x8b\
+\xf9i\xb4\xdc\xf3\xe0\x1dx\xeb\xf65\xc3\xc9<\xe6\xa7;\x9b\xb9*\xfd!\x8f\x17\
+\'\xa7\xc8\xc6\xda\xdaM\'i\x8d\x9f\xa5\xb2\x97\xb2\x1d\xd8_(y\x82\xa3\x08uw\
+\xb0,8\x81\xbd\xad\xfc\xb5D8\xdd\xef?\xb5\xa9\xd7RpM\xb8\xabt8\x82\x9a\x16nq\
+<\xaa\xcf\xd9\x9b\xa2\xef,\xe2\x84X\x9c\xd7\x95IA\x107/\x96\x9e\xea\xed\x9d3\
+j^\xf4\xbb\x8d]\xfe\xcf\x9aA&@b\x07\x80\xe1\xc0\xb3\x01\xe5lN\x0f\x18\xfecN\
+\xed_\x87\x83\x1b[>\xc3\xd69\x95r\x04\x10e\xe4G\xed\xa8\xae\x82\xf3\xae\xa6\
+\'\x9b&\x0c\xa8\x0f\xbf)\x17\xb4\x8dU\x8a!-\xca\xb2\x16\xe6\xeeK\x90\xa8\xbd\
+\xeb\xba\xda\xa8\x98Q\x8d\xe9q\x1a\xdb\x0b`CCwSA8D\xc6\xa9\x07\xb1x\xa6vGs\
+\x1f\xc4\x0cO\xae\x9f\xb7\xf6OQ\xc3\x0feQ]c\xb472\x15\xc6\xbf\xf7\xcc\n\xad\
+\xa7I\xd7\x9d\xb5\xc7\xaf\xcf\x06t\x17\x04\xf03\x05\x03\x0c\x95\xd2\xd3\xfb\
+\x1e\xb6\xc97\xeb@\xcb\xcb\xc02\x99l>\x07\x05\xe0 \xed\xac5\xc6G\xe4\xd4{\
+\x87l\xeeT&C)\x12\x07\x9eb\xb4\xd5\x7f\xf3`\xffV\x8f\xcb\xfc\xfbSaF\x15\x869\
+Y\x04\x89G\xc1F\xf0\xba?\xb6Kob\xc5\xaa\x0c\x02\xdbV\n\xbet\x83\xcb\xca\x93h\
+\xab\xce]w\xcf\xac\xc7\xb2\x11\x16\x80\xc8b\xcf\xf3\xd5\x9a\xfb\xf4\xf0\xf8u\
+}f7I ;\xfd\x9cw\x8d\xa0;m\xa8\x10A\t\x93X\xe5\x85\xb8\xde\\y\xf7R\xfd\xc6\
+\xdd\xe1m(\xaf3\xbc\xd9\xbfA\x9d\x8f\x91\x83\x80\xf9\xeb\xf4\x84\xd6s\xea\
+\xf9\xdb|\xe6\xd9\xd1\x04Q\xf3q_j[\x05"H\x8d\x87^\'\x10\x12A;\\\x99\x12\xf0H\
+z\xe9\xe1\xf8\xf2\xb3\xd8\xb4\x05\x99\x0e\xf1\xc1\x10\xc6_\xeb\xb2g>b2G\xc8s\
+\xaa\xaa^\x82\x80^\x05\x85\x92\x99\xc4\xa0\xde\xe9k\xc7\x163F\x12\x96\x0b\
+\xa2\x03!\x1b\xad \x0b\xd9XA\xad\x0c*\xa4nu^z:\x9b\x8b\x00\xbe\x81\xc7\x8f\
+\xe3L6\x15$b5\x11\xcd6\x1a\xc6\xce\xe5Z_gv\xb7c\x88\xc7\xe7\xd4\xde\xe6\xd9\
+\x8a\x89\xad\x11M\xd8\xf5\'\xde\xac\xdf\xdb\xba\xe6\xf8[\xf2&m\x05\xfd\xf7\
+\x97\x0fS\xd1/\xd4\x80QL\xdd\xd8\x86R\xa7\xf2r\x95\xe4\xd3\x88\xb1\x1d\x97# \
+\x90\xd3\xac\x96y7y\xec\xdc\x94\xba\xe13\xb7\x89\xc6\xeaZ\xf2\x84\x7f,\x10+\
+\xe6\x9cLI\xd7\xa8\xd6<[\xc0\xa6\x9a\xb5\xab?\x1d$\xa9\xbd\x80\x1e\x18\xb3\
+\xc3\x18\xa7\xf5G\x90\xe5L\x84\xba\xc5R%\xf4\x1b]\xb4\xd0\xa1\x14\xe8\xb7\
+\x92x\xe8\x97!\xdfH\x8f`_\xec\x12E\x89@i1\x1d\xfd\x9d9\x19qTnD\x80\x10Y\x8d\
+\xc9\x10N\x90@\xb4>\x0f\xe8\x15\xa6\xa5\xb5\xc5\xed\xe19oS\xed\xf7\xf9u\t[a\
+\x05\n\x01\xaaf\xe8\xa4!\xf4\x04\\\xaf\xb5\x14t\xdc\xafT+\xfc\r1\x87\xea\xbb\
+G\xa7HFq\x1ad:e\x17\xc9\xec\x98h\xaf\x9e\x0f\xcb\xf0\xb4\x06K\x19i\xe5\xa0yJ\
+F\xc4\xdb"#\x12{I8&\xe3-Ny\x17\xe1\x93`T\x85\x98\x90\x08\x06\xf6\xbdj\xfbD\n\
+\xdb\x04\xef\xf6T^\x7f(\x86f\xe8\x1d\xb9\xa7\xc0\xab\x9dE\x03\x06\x18\x06,\
+\x84\tJ\x9d\x00$\xe4\x85\xed\x08\xb0\xbd\xd1D\x00\xbc\x19\xab\x15\xdd)\x90z"\
+\xe0x\x88j/\x18\x0bO\xa2\xde\x97\x10\xff\xee$\xaa\xb0\x80n2,eW)f\xe7F@\x99\
+\xce\x99\xdd\xc2\xc2c\xcb\'\x9d"t\x01\\\x16\xefe\xda\x1a\x9dM}\xdb\x01\x80\
+\x87c\xe4\xe4\xa5(7Xq\xdf)("\xbbPv\x8f\xd2v\x15\xf7(\x1a`\x85\xc3\x87j@\x7f\
+\x8d\xaaA+H\xdf\xdb\x8716\x12\xff\x8eb\x9eW(\x18\xd8w:,^\x1a\xd0\n\xa2w\xfe\
+\x96r\x8a\xe0C\x18\xfd4\xd0\xbd\xce\x80\xa2\xd0\xb8\x82[l\xc6C94\x1a\xad\x89\
+\xc2\xfe\x83\xf6\x02\xe8\x81\xfb\x8d\xea|\x88}\x8cvO\x18\xbb\xfb)+\xee\x7f%,\
+\xd5\xff?\xff\xac\xff?5\xeb\xd5.\x1dr7\xb2\xdfQ\x93\t\n\x91\xf7\xd8#\x9d?\
+\x17\x17\xa9>\xae%\xc8\xe52\xed\x16S\xfd\xa3;\x07\xe0@\x1b\xf7+\x92\xbc\x08\
+\x93V\xc3\xf2{\xab\xea\x03|\xe8\x11.\x9e\x1b\xf8\x98\xf1\xa08\xfc\x1a{\xae\
+\x8diT\x88\x01\xdd4h\r\x9d\x05C|\xffw\x93\xdd\xc9\xc4v\x07\xef\xc6\x15\x951\
+\x18\x95\xa48\xb8\x0b\xac\x07o\xe0WA\xf6\xf5\xb9\xd4\xca\xf9;\xb3\x8eo\xf1\
+\x10=\xf1\xa4\xa8\x10L\x10\xc1\x8b#\x92\x1dhS\x0f\x9f\x0b\xad\xb1\x96qX\xea\
+\xeeM\xc0\x93\x05\x0fIM\x10\x1a^\xc3\xf2&H<\x14\xc5$\xdf|\xd9e\r\xb6\xc7\x18\
+8\x90sG\xb6$g\xed\xd7\xd2t;\xbe\x07\xd7fx\x0c\x9d5py}oW\x12ho#\x86-\x84\x14\
+\x0b\xea\xc5d\xd6\xed\xf3M\x85Mq\xa1\xd0\xd5\xd3?\xbe8\xd9\xa2\xd5\xb7`\xfa\
+\xe7@\xeck\xbf\xf3\xf8;\x88\x86xA\x04g\x8bT\x7f\xc8\x8f.Kr\x95V\xdbZ\x15wR(\
+\xa1\xa0\xbb"\x03.\xef\x1a\x1el.U\xb6s\xf7\xcc\x15$\xe9\x03\xec\xc1\xf2\x17e\
+l*\xe4\x15\x89t\x16,j"\xd3\x13\x8d\x0fq\xb5\xb3\xfb\xbf\xb1\x00SeE\xd9\x1bg\
+\xf3Q\x16onY\xcc1\xd5\x19\x00\xc8\xb6O\xdd4Q\x112\x95v\xf4`\xd1\xf1\xa2g\x03\
+L\xf3\x18\x82(\xacj\xca\xc4\xd4lCK\x985$1\xa9md\xa1\xdf\xbd\x82\xff\xe9)\xc9\
+\x07\xf6\x0e@+O\xc1\x8aLlnB\xd7`\xad\xde\xe1\x8a\x18`\x01Y\xcezG\x86\r\xa8\
+\xffRY\x8e\xcaD\xfd_x\xa1\xc1[}\xdd\xf2\x13\xe9i\xc8\xe8jJ\x0c<\xfd\x03\x02\
+\xdfi\xc6\xa4\x8e~{(\xc4t\xb3\xcf\x81\xca\xf4\xebn\xec/V%\xc5\xa9\x96U\x9d\
+\xa1/\x9e\xf8\xac\xe6T\xe4\xca\x96\xaf\x03\xa4\xb7\xaf\xbf\x06jl\x19\x82\x97\
+\xca8\n/\x8e\xcavg\x91z_>\xcb\t\x06\xee\xf2\xd2\xcbz\x1a\xe4.\xf8\xd9\xc7\
+\x9bS\xd8\xb8<~\xaf}\x87\xca\x17\x17\xbf\xba\xfe\x1b\x9f\xa9\x7fK\xc5@\x90N\
+\xf6\xe5\xab\xe5\xa9\xba\xd7FW\x13|\xc4M\x9bY`S@\xdeM+\x0c\xafR\xcf\xe4\xb9\
+\x87>9\xd6\xafF\xfdM\xc4<q\x85\x8b\x03\xf68\xbb\xdaB\r\xf3\xdb\xbd\xadbV9S\
+\xc0\x02\xa8$\xf1\x85\xdc?g\xd8\x93=\x02\xc1\xcaz\xd9\xd1U\x0e\x901<\x96\x19\
+\x81\xc0\xc7\xafm\x99\x88\xee\xef:T\xf0\x7f\xca\xf5\xca\xbf\xa8\xda\xf0\xcfa\
+\x08))i\x10\x10\x91\x90\x90\x92\x92\x92\xee\xee\xee\xee\xee\x90\x87nP:\xa4\
+\xbb\xbbAr\xa4\xbb\x9b\x01\x06\x18\x1a\x01\xa9=\xfe\xf6\x1f\xd8\xfd\xec\xcb\
+\x9dW\xc3\xcc\x99\xfb\\\xf7\x15\xdf\xb8\x0f\x1c\x1d\x12\xc9\x91o\xa4=0\x85S\
+\xde\xeb4/\xbd>\xb2\xc7\xf5\xff3\x8cP\xad\x13\x1bNE\x8ag|\x1f\xa0\xae\xf2\
+\xd9as\xb8\xad\xe5\xccs\xb2-\xde\xd5{\xe71Q\xf4\xcc\xaf\x07\xde\xde\xb1\xb8\
+\xf9g8\xe2@\xb9\xfei\x7f\xc9\x93h\xf2\xe9OpS\xe0\xb7 \x94%\xbb1\x14\xd0\x83e\
+<\x8d\x81\xdfU\xff\xba\xedi[\xc1[\x1a?i\x7f\xd7I\xb9\xea.{\xf7\xfd\xd8PJ\xbf\
+\xd1\xc5Z\x19\x04\x10qD\x0c\x1b\x8a\x11\x08\x11\t\xc9\x0cs\tZQ\xf3!\x88\x90\
+\x93&!\xa8/\xc9\xe3\xa8\xd71\xce?\xb0i\x9c%8Q\xe0y\xb7y>\x9b[*\xce\xd4r\xff\
+\xf4\xec.YT2\xd7\xde\x1e~\xa2\xfd &j\xfb\xee\\Pxwi$F\xb1\xf2a\xf2\x8c%\xfb\
+\xef\xf8\xc5YM\xc7M4v\xfb\xb3\x8a\x88\x87\xa8\xed\x81\xe5\xa2d\xe0\xf9\xe5\
+\xb0B\x96\xc0\xe5\xcf\xabOg\xb5\x8eb\xacV$\xbb@\x00=\xae\x97\x04F\\\x10z\xbf\
+\xfd\xee\xc3\xec?\x9b\x0cA4\x08\x9d?^\x0e\x80m\xbd\x14/t\xf8\x9cm\x84\x19Q\
+\xd5\xbe\xe2\xeb~\xfcuB\x8b\x14\xc9&*\x82d\xb6\xf9x*\x1b\xbe\xa9\xf5X\xb1-\
+\x0f\xaf\xa8y\x85\x82\xf2\x0b\xf1v\xed\xe8L\x97r\xe5\xfe\x13\xddd7\x86\x111\
+\xbdt\xecl\x1c\xe77#iJv\x94\x88\xd1\xda\x0f\xba\x98\xf4RI\xe3A\xe0~\xc26xp\
+\xf3\xb1\xfe\xe5\xeb[\xcf\xc6\xc0\xa3\xc3\x9dh\xcb\xcdq\xff\xc2\xab\xd1W\x01\
+7B\x97%\x86\xf7^\x8b\x9b\x03\x06\x9d\xda\xb5\x17\x88\x11a\xda\xe5\x9c\xde\
+\x1dy7\nb\x0b\x1eO\xfaxT\xac!l\x88\xc26\x1d\xcf,!\xef\xd8\x916"\x95\xdb}\xc7\
+\xcf6Y\x1c\x91\x82\x9a\xe0\x99\xa7\xb5\x81\x19b\xc6(`)\x14V\x10Zm\xafp\x12\
+\xa8\xc6\xb1\r\xe6y\xfa\xf4\t\x13A\xd8$F\xe0\xaa?\xd50#\xfb\xe5_\xc0\xf6\x8b\
+\x88\x1c \xccH\x04\xe6?\x8f\xcb[t+:\xf6SJ\x1f3R\xec\xb7F\x8c\x9f\xb2[&\xa9@\
+\xa6\xa3\xcb\tFF\x88Hhv \xe4H\x04D\xd1_\x84 \xfa\xa0:\x99\xf6\x1f\x08\xa2\
+\x16\x95\xf5\x90b\x11dA-\xab=MK\x10}\x1d\xd8\x9d\xcf\x8a\xed\xd3\xb6\xd4\x08\
+M\xd8 q\xa3Lpm\xb3\xff\x1b\x90\xdb\x08\x19B"\xa1\xf8W\x91\x90\x14\xe7^\x90\
+\x05#3r\xdf[xn~\xdbB\xddn\xb4j\xe4\xa2\x9f\xff\xed\xc6\x9c\x87\x80ABn\x8a@\
+\x0c\x99\xae\xa6\xffo8R\x10\x18\xaeh\xa05\xc7|j\xf6\xcd\xfa\xe5g\xafhT\xe6\
+\xebA\x9c\x01\x11k\xb4\xd4\xb3l\x96\xe0w\xd9<W\xa7\xaao\x84\x842&9\x90z\tg\
+\x9b\xb7\xf1\xf4\xd2\xb8\xae\xf0R\xa3\x1eJQS\xe0\xe8H\x198\xfd\xee\xccX\x88[\
+\xebWt\xc2& \xd9\x11av\x1a3\x94\x8e\x04ui\x90X\xdd\xc1\xc5\xd7^u\x92\x10\x92\
+\x90\x19>:0!\x86y\xd1ki\xd0\x101\x9c\x9f\xb0\x85\x9c\xb1\xdf\xc8h \xf0/\xdf\
+\xb7\xc6\xe9\xae\x94iB\x10!N!\xc6(\x05\xaan\x8b\x1f>\xc8\x0et\xf2\xcb#"\x98\
+\xca\xd8z2\x08!\x01T\xa4\x8f\xcf.\x80`\xfd62\xb8\x8f\xd8\\j\x9f\x81\x8a\xd5\
+\x8a\xd7\xfd\xcfz\xcfK.LT\xd2\x9d\xd3\xae\xb7\xd7\x08\x19\x14I\xe0=#\xc2\xb7\
+\x19t5\x98\x88\x84\xd9\x9b2#S\x13<`\xc9\x9a\x16\xac\x06\xa6FD!;\xa3\xe0\xf5>\
+\x8d\rM>\xb2\xc9\xf4\xcb\xa6\xe9q\xb5q\xa8zR-\x99 \xcf\x99\xecY\x9a\xbd\xb7t\
+\x84q\xd0\xbc\xff\xe5\x8dRi1\x8a\xaa\xf7\xdej\xad\xb3\x8e\x14\x074\xc9\xa1\
+\x01j\xfc\x9a\xe8\x85<dWC\x1e\x1b\xe0\x05\xa7bE\xdb\xe6\x0fQ\x1f4O\xd2)\x08A\
+\x02\xd9\xec\xbf\x05\xddM\xe9c\x81@\xac \xbb\xcaV\xe9p\x10\x1e>\x89\xf9\xe3\
+\xe5\xcf\xe6\xdb\x83\x9a\xd3t\xde\xca\xb5\xd9\x0b\xcf\xfe\xcc\xe7U\xec9J\xf9\
+\x9e\xe6i\x7f\xdd\xc3HE\x84^\x88\xeaA\xcf\xed\xaeaU\xf7\xc5\xdf\xd9\x92\x89\
+\x03\x17\xefM\xcbv.zO\x0e$\xb3!\x08\x87\xec\xe43U\xd7m\x117\r\xa7`\xc7\xf6\
+\xa9\xd0S\xffFs\xb3\x06h\xe9\xbe\xbf\xadM3\xde0\xbc9\xb7;\xed\x1ej\xeb\x88 \
+\x93\xbb\xbe\xd5\x0e\xdf\xe7\x1aZ\xcaI\x1f\x1b\x1b\xed\xe9jix\x1a#\xe1wy\xbc\
+.\xf1qx\x19\xa3\xcbF\xd6\xd0\xd2\xf2\xba\r\x17\xfar\xd7\x90\xfe\xfe\xbe\xa1\
+\xe4\x19\xd6\x13`|\xf6\xbc6\x11(U\xa3?\xdb\xfc\x14\xa9\xe8\xb6\xc6\x84\xf8+\
+\xd6\xdes}u\xdf\x04\xf5\xed\xc9\xac\xd8\xb3\xaeCkn\xdfGP\xeb\xfa\xb3t@@p\xdf\
+G\xfajR\xe6\xc0d\xd5-\xff`\xc6I\t\x94\x91B\xf0\x8a\x81\xc0\x9f\xa7\x96K\x06#\
+fT\xd9\xb3\x1c!\xef\xc7\xb1OB\x9d\x7f6C\x82\xa9\xaaD\n\x9f|`*do$\xb8JR\x8bsP\
+\x82\xfe\x1b3\xa2\' \xd2\x8e\xfa8\x05j\\\xefy\xdew\xf7\\\xbfN~\nW\xcc\x11hyZ\
+\x0e\xec>\x9b\xd0\x15t[\x8c\x05\xe1}s\xfb;\x06\x13\xa2\\\xef\xb1\x1f\x1d>k\
+\xebx\xbb|\xbf\xb8\xc1\x7fO\xf9\xf2\xd7\xd2\xf0)\xe9b9\xd6\x13Qj\xf3a\xbaF\
+\xf0\xf6\xe7q\x85\xa1\x8f\xd3\x99\x1c\xcbev*B\xc0\xc5<3\xa5$kX\xcf\xf5\xf4\
+\xd9\xd3\xdd\xe2\xe6\x83a\xcf\xe3\x1a\xdd\xa2\x80\xd3&\xb3x\x13\xda\xf9xd*\
+\xbb$a.\xe9\xf3\xb5|UU\x84\xac\xe5\xe4N\xee\xf3\x8d\xa2\x05vW\x17\xf3y\x9d\
+\xee\xdb*\xc3{Q(Qx\\\xb6\xdf\xd5uK \xeb(\xe6I\xd7\xd9\xc2\xb2\xa8\xa3\'\xcf\
+\x89\x1a\xc2\x7f\x84(D\x1e\xf7\xb8\x98a\x15\xa0\x9f^x["\xaaL\x89\xe3\xf1\x11\
+\xaf7Zq\xad\xd8\xf5\xbc\xb9\xc0\xd9&\x88\xc3\xb1\x94\xddp\ts\x08=k\x18\xdd$9\
+o\x80\x042\xc8\x08\xf3.\xdf\x1c\x96 \xe3.\x85\xc2A\xdc\xf0\x8e\xaa \xd9\xa6\
+\xd1=\x0e$\x07F\xc3!A,\xeb\xed\xb5\xe8="\xab\x16\rR\xa5\xdb|\x1a\x9a*q\xb4\
+\xb1\x7f\xda\xdd\xec\xb8\x1dXv\x1d\x0b:x\xa3-\xfa\xd1\xaf\xb4\xe1-\x0eqe\xfb\
+m\xdc\x9f\x1c_\xc7\xfb\xd3\xf8\x03\xc1\x8e>4LAXw\x83\xe5S\x15h\xe8\x80\x9b\
+\xaf\xe4\xd62\x80=\xf9\xe1(%\x9e\xe3{\x10\xf3;\xf0O\xb3\xda+\xf1\x8f\xeci\
+\x02\xd7\x0b\x1b\xa9.\x817\x94\x82\xfe\xa6\xc7\x1fAF*\xb2\xb6d~\x17\xb7\x8b/\
+\xeb\xb7s\x93\x0e\'G\xda\x81\x8c\xe0\xc6\x1a\x83A\xe2\xc7\xd3\xb0\t\xecu\xb8\
+p\xd0l\xc9d\x81\x18!/"\x0fv-zT\xf4X\x90\x12\x886}\xe2\t\x84?>\xa7\x0e\nB\xdd\
+w\n\xb6\xc5\xd3\xecW17Z"\xee(\xaa:\xf8C&\xa4\x10\x9d\xeb\xf5f\xa3u\x05M\xb8D\
+\xcezNr\xa80\xe3\xa0\x0f\x13\x04+&\xab\x9eU\xcc\xf2\x87N]\xbc:u\x05\xb1j\xf0\
+oh\x1b\x191\x82\x96\x88\xa9\x08\xed\x8c\x10\xd1\x88\xc1T\x1b\xce\xc7\xaf\xb9\
+\x9b\x9ag\xd8\xfe#\xd3lA\xf8u\xddc\x86\x91t\x91\x1d\xe8\x17\xcaF\xfb\x8e\xd5\
+\xec\xb7\x7f\x0b\xc9\x10\xdc\x0c\xe3Oe\x0f\t\x08\x87\xedj\x1f\xd2\xb2\xc1\
+\xdb\xa7\xe5\x1c\x1an\x1c\x1dK\x82G<f\xf5\xf5\xe7\xec\xab\x19E\x0b\xacUb\xfe\
+\xdagB#;\xb4Z\xe44\x84\xda>\xf8\xb7\xb32!\x7f\xb6\xd9\x86g\r\x14\t\x04\r\xe9\
+\x11A\xb7\x16\xc4^V\xe1\xf6<{\x02\x93>#.2k\x11\x94A\xc4Z\xa5\xfaz\x98_\xd6\
+\x80\x0c\x13\xf3\xf4\x0f\x04\xb8\x96\xc1r\xfc\x87\xda\xfcV\xf4\x94\xf2\xe0\
+\xcaV\n-\xfe\x14\xb4\xde47\xcb\x87\xa1\x996\xd5\x99\xd8\xd4\x1c\xa2w;\x92\
+\x0b\xeb\x97;\x9f6\xf9\xee\xf6\x01JYW\x19{\x03J\x7f\xddk\xa4\x04\xd2C\x1eCA\
+\x81\'I\xe1\xd92P\xe1\x13K\xf5b\x1aq\xb1&H_\xfd-\xcb\xc1^\xef\x86\xde\x9eJ&\
+\x10\x13\x07%}\x1d\xed\x05\x81\x9c\x8e\xaeg\x84h\x0c\xe7\xe3r\x9fF\xf8\x1e\
+\xaaZ\x9a\xba\xd2\x85\x86A\tl \xc4\xd7\x10\x10\x9a\\&\x9d\xa3uI_\x10\xd9\x8a\
+]\x90\n\x06\xe2\x7fW\\z\xee\xaad!\xdf.\x9a\x03\x8a\xd5\x9e\x8a\xc03uj\x16\
+\xa6\x9c\\\xcbv\x8d\xbe\xd2t\xa8Z\x89~\xa0$\x84\x91I\x82\xf37m %\x1f\xab\xaf\
+H\xc6\x95\x06\xfd\xad\xe8\x15`<\x8ej\xd1\x10B\xe5X\x8cU\n\xc2azz\xd2\xc6\xa6\
+K\xe3\xb1\x97\xa9\xae\xa0P\xa2bl\x82T3\x18%!\x88\x06M\xe3\x1dG\\\x97\xebQ\tG\
+:8;\xd2<\x0c\t\x99\x8c\x18\xb3/l\x8f\x1f\x81\xbb\xb9y\xecy\xdf\xde\x87K\t\
+\x8c\x82\xb7BL\xd8\xf0\xf7\xaan\x801|\x03-\x00V\x80\xfd4!\x10\xb3\xacA\x022J\
+\xb8\x1f\xa3ALC\xb0\x8b\xc4M\xc5q\xf0\xd4\x0b\xa7\xca\xe67\x9d\xfa\xc5\x10\
+\xa2\x8e\xea\xb4\xe3\x96\x87|\xbe\xaf\xab\x99<\xd6\x8b\xde;\x82\x10J0\xf9\
+\x8d\xa3\x00\xd4\x8b\xa3\x05\x8b\xe0gl\nA\x92b\x93\xad\xb1ZNw\xc4\'\xf9\x16\
+\xb5\xc8\x952.q\xf5\xa7%X\x8bJ\x936,\xec\xbf\xc6\xcc\xbe\x96\xdd\xbb<U\x14a2\
+\x1f\xb7N\xcc\xb9\xf7\x81\xbe\x97c\xb8\xa0\x82\xc6\x1b\xcb)o\xe7\xa3\xc8\xc6\
+\x0f"\xe9\xe7U=9\x0f\x0e/\x8f!\x9e/\x1a_\x1e\xc6#\x03?\x90\x11\xc3jy@TY<\x0f\
+\xa7\x03\xf1\x817\x1dd\x1d#+\x05\xf6rHh\xcb\xdeFAZ\xc2A\xaf\xd6\xdfr\x99\xc2\
+\xee\x07*"\x90#\x7f(Tp\xf0\xf8\x97\x9fGk\xc6?\xed\x9f3@7t\xa3\x1b4\xcd0\xa5\
+\xbd\t=\xcd\x89\x97\xa6\xb6>m\xbb\xe6\x9e\x98\xa0\xba\x86o\x80\xbf\x13\xc3\
+\x1d\xd9\xd3|\xed\xbb{\xb7\xaer\xb5\xc2\xf7\x9a"*"\xc2Q\x89\x86\xde\xaf\xc8 \
+$\xc8}\xc9O\x82\x05>O\x81\x8d4\xc2L\xbe\xde\x90\x0e\xd7\x0b9a|\xff3\x0e\xe1\
+\x880\xc6\x00S\xe9Y\xd6\xee\xd4)\x84!\x83xZ\xdcD\xbe\xdcc\x0c-u\x1c(N\xe5c\
+\x98b\xa6]\xac\x0e\x12\x19\xfa\xda\xde\xc6V|\xe93\x9a\x9b\r\x96FFqd~\xa6D[.s\
+\r\'\x11\x95\xd0\xddF\xc5\xfc\xf0\xf3\xc9\x87\x1e\x13\x9b\xff\xce\xf1A\xf0\
+\xba!\x15+p\xf7\x0bG\x89\x02Q+\xd99\xe3\xab\xcd\xe7\x87\x03\x8b\x9e\xcb3\x9f\
+D\x84\x9fn\x7f\xe6:Pg"g\x0fp\x83\tEF\xc8\x9f.)\x03\xc6\xbcSw;^\xbc\x8c^\xd6\
+\xd8\x92\x1e\xaf\x97\x8b\xb2\x85\xeezz\xd1[\xd6\xd7\xb0N\x1e{J\x97\xdfXN\xbc\
+\\N\xd5mv:\xfbI\'\xed\x13]\ni\x1e\xe0\xc6\xb8{B\x909\r\xbco\xecQP8\xb6\x88^m\
+\x1e\xb5\x1d4O\xda;\xc9?\x9e\x19\xe6\x08>U\xf7\xa1\x83\x9cN\xe7:6\x03\xb7\
+\xa7\xfa\xc9\x16\x00\x1c%\x1d\x02\xd5B\x07\xd2g%\x10\x14\xb2y1\x06\x1b\xdc\
+\x89(\x85\x89:"\xc0\xb4L\xb3=u\x8d\xd7-b\xbe\x9b\x1f\x84\xfa\x8b^n\x87\x0e\
+\xd7\x12\x1d[\xcf\x9e\x8f$\xcf\xee\\A\xcd^,\xa9\x82\x97\xe3o\x0e\\Q\xa4\xc6\
+\x843\x91"\xac\x84\xbf&\xe9\x14^%=\x15?\x08-\xdfp\x07\x15r\xd2 \xedN\xd5\xad\
+\xd7tu\x91\xa5\xbf\x0b\x86\x17\xa7T\xc7\xbb=\xd3\xf9\x1eg\xfb\xe5f#0\x0e\xfe\
+\x07\xf0\xdc\xb3\xd9Kb[\xc8\x15\xdc\xbe\xaa\xc2\xd1\xaa\r`9hN\\8\x17\x1f\xc9\
+O$\x9e\x87#\xdd\x97\xc6\x97q\xd3aE\x99\xa0\xe3\xd3\xa4\'\xec\xaa\x1e\x7fy\
+\xc6\xda\x9d)\xd7\xcb\x93\xb6\xb2\xdc\xee\xe6\xdb"9\xddL\xb7a\xd2\x87*\x97c\
+\xca\x97\xaf\x93\xf2\xf6\x15 #\x80\xf7\x1a\x84\xd4\x14\xb3y;\xa0\xb7\x82k\
+\xee\x07\xe3\x8a\x81\x07\x07-\x1bk\x7f\xab\xaa\xb0\x9b\xd6k\x7f\x83\x1e\xcf\
+\x84\xd2V9\x92\x8f[\xcd\x9b\\&\xed\xbb\xbb}\xf9\x1c\xda\x10\xfa\xeeJ\x9e\x1c\
+\x1fm\xff\x96\xce\xce\xb3\x90\xdcer\x13\x01h|\xc5A\xb6\xc0\xf6_K\xdc\x01]\t\
+\xc72\xf7U\xd0\x10s\xcb\xda\xdd\x03\xe5\xb1\xce\x8b\xa3\x03\x82\x96\t\x95\
+\xb1\xb3\xa6\xc4\t%\xba\xde\x7f\xb7;\xf6v \xa9\x08\xceO:\xa1 \xcc\x94\x99\
+\xf0\x9e"\x8a{\t\xc5\x83E!\x9fEM\x08v\xab\xdb\x9f\xa2\xc3\x89I\xba\xf0\x91(\
+\x83\xfeJ\x1eX\xd2\xe1\x14&~\xd9kP\xef!\x0b\x16\x1f\xf5p\x8c\t]\xf3\xf56^:\
+\xe4\xaf9\xd1\x13\xa7\xd5\xfbBN=\x10"\xbd\xf0\xb4gtV\x92\xac\xa5\x9b\xdc\xf3\
+\xef1\x8b\xdf\xe5j\xe3;65\x08a\xff%\xf5E\xbd3\xc3&\x1a1\xc7%\x01M\xe1\xb1\
+\xe1k\xe5!\xdb\xed\xc3\xd1\xd9e`\xcc\xa6J+&\x048\xadA\xe6\xb1\xe3^\xef\x98M\
+\xb9$\xd6\xe3D\xd2^\xd3\xe8^`\xd2!\n\x87\x89\xaf\xc6`y\x13h^\xe1\x06\x19\xf7\
+i\xac|\xdc\x12+*\xa8\x0cB)\xf8\xe2*\x0c\x1e\xc2\x01-\xe3}\xf7BN\n\x1e\xe4\
+\xc8\x92\x1d`e\xca\xfej\x8a,\x8c"e\x9bV\xe96\xfc\nj\x8d\x7f6YG\xc0,Vp\xb84\
+\x1ad\xec\xfc\x05)\x8a\x0c\xb3\xc8\x860y\x1ad\x1c\x14\x0c\xc2\x93A\xd1#f\xa7\
+\xd1y\x8f\x9c\x80\x93\xf0\x83\xc9\xc6H\x02ci\x04\x936U\x1c)$\x18i\x04\'\x91\
+\r1\xb2\x97\xc8\xaf\x12\x19\t\x9428\x84Q\xfd\xb7\xc8\xb6@\x0b\xf6\x1fw\xa7\
+\xeb_"\xe4\x01\xb2\x9a\x8eh\x82*\xed!\x10\xb1(_\xec\xb8LwsoJoc\xaa\xbe\xc9\
+\xa5\xca\xc5\x04Y=4j\xe0\x071(\x84\x90\xaf\xff7\x0e\xa8\xc8\xa4\x84\xdbN\xe9\
+\x9a\x8fe~\xb0YM/\x99\x89y\x89\x1a\x9f\x98y^\n\xf3\'\xa8\xf2PcR\x89\x9d\x98:\
+\xe8\xb54q\xc68\xf8\x97\xb7\xd1\xe029\x98J\xaa\xd1-zD\xc9\x04\x05?\xbedF\x07\
+\x9f\x11\xceM\xacd\x84\xe9-"1\xac\xa7\xed|\xbe\xd5\x9d\x10\xc36D\x9c?\xe0\
+\xe9#lw\x88\x91VK\xbe.\xb5\xaf7\x8diz\xee`|\xb1\xb7\xdc?\x12\xc3Md\xc8__{P\
+\xac\xee\xd9\xbe\xf0\x88\xc0=S\xaaHd\x14\xc9\xa6{\xa1\xf4\x19\xcc|\x0fJ\xd2a\
+4\xc0~EEP\xe3b"\x1b\x83`\x94g\x16\n6r\x96\xfdEo3\t\x06\x85J\r\x16\x0eB\x17\t\
+uf\x18X\xe2\xce\x03\xe2\xb9\x90\x8e\xfe\x0e\xaa\xae\xbb\x1d\xab9g6\x1c:\x80\
+\x1b\x85e\x87\xcd"\x9a1{Q\x06uQ\x89\x83\x90\x9a\xa9\tdO\x8a\xea\x0bw<\x8a\
+\xbe.*\x1a+\x97\x11\xae\xa2o\x89|\xce\xb3\xc2\xfa\x9e\x93:\xfd\x17\xbc\xc8\
+\xb2q\xcc\xac\xfe\xdd\xebOl\x8f\xff\xc3uM\x1c\xd3I\xdb\xec\xc5\xfd\xab\x8e\
+\xe7\x99\x80\xa7\xec \xe6"\xb4U\xf1\xab\xce\x9d\xca2\xab\xb6i\x07\xcf\x8b\
+\x98b5\xb7\xb3\xd6Z\xe8x\xac\x80\xa0""\x08\x14Y=\xdb\xc6*\xce\xe4\xc0O\x18\
+\xb4L,<=\xda\xcd\xa9\xfaA<*\x08\x93D\x8a\x13M\xcfd\t<B\xa4\x8aN\xbf\x9cV&A$\
+\xaf\x17}O\xd6\xcb\xfd,k*\x86@\xb3:\xa8i\xf1\xc3"\xc3\r9\xcb\xcd\xb8\xdbg[<\
+\x98\xd0\xab\xec\x9b\t\x81do\x0c\xca\x06\t\x1d\x15\xd5y\x0e\xd5\x90\x05B\xb0\
+E\xb0QA\x90p\x83p\xad\xb2\xf0Om\xaa%z\xaa)&\x1c;|\x1c+BT.tTN\x0c$2d\xa4\x0c\
+\xe4o\xb1`\xf08\xb8O\xe2\xff\xf0r\xc6~\x89\x06U\xa887\xa9\xc1\xbd\x08#!M\x16\
+?r\x06\xc2k\x0bS\xde\xf1\xdf\x81:\xd5\xb2\x05B\x1b^\x04,J\x8c\xcf?Fd\xa8\x7f\
+\xa4\xb2\xdc18\xddL\x1bq]\xf6p!\x9bw\xe5\xdez\xc9\xf2(_\xc8\\?\xbb\x98k\xb5\
+\xd3\x1dQ\xe3i\x14\x1e\x12\xe0I\x0b\xffD\xa9V\xc9\xe5\xb0\x15z^\xd3\xfaq\xb7\
+\xe1\xfa\xc63\x0b\xbd\x92\x89\xb4\x8cl\xbc\xbdT\xef\x01!\xc8X\xefz\xf8x\\\
+\xff\xa5\x0b\x1b}>B-\xc7\xf5\xb3\xebJ\xe5\xc7P\x13\xfc\xd6\xbfVi\x0eL\x8b\
+\x01a\xc6RIj\xcf\xcd&\xe5\x04\xd3\xb2)3\xa1\x0e\xf8Z\xda\x9c\'g\xdc\xc5*eN<\
+\x95\xf42<\xe4)\xe6\xda\x8b7\x89\x89\xb8b\x85\xb97Jl\x99\xe3\xedz\xd5\xe6\
+\xf8\x02||\xbf\x89\xc9HI?\x06\x12\xc4J\x90\x98\xd63\x1d\\\xdd\x07\x07#\xc8N\
+\xbfy\xf3&\xcc\x98\x87\x1c\xcbg\x14\x8d\xc1\x8a\xa1\x96\xda*\xad\xe8\xe8\xc6\
+\x87@\x1c\x01\xa4\x9d\x02\x0b\xecX\xa7@\x0f=\xb4\x14O[t\x07\x19\x99\x99\x99\
+\x91@\x87v/\x96\x97\x97\xfd\x04\r\x9f?&C\nU\xca\x80Ez6s\xa5h\xea\x97\x99B]/\
+\xe9\xf0"\xfb\xfa\xbe\xfadB\xccZi\xbeE\x81\x19\xbfIL\x90\x85\x87\x85\xd1\xd2\
+\xd1\x8d\xbf\xc3\n/\xd0\xd9s\xc1\xe0%\x8d]x3{9{v\xab9[jjjjP\x93Z\n\xbd\xf1y\
+\x9f\x88\x1a\xaa*E\x83w\xe1\xd3\x85o\x12\xc0h\x11\x03f\x0c\x95\xa8\x9f\x86\
+\xba\xb8\xbb\xbb\xc7\xc6\xc7\xbf\xcf//\x8f\xca?l\xa7\x88\x1e\xc4\x8b\x1c\x98\
+\x9aJ(\xb6\xceS\x1a9\xbe\x192a\xd3Nqm\xd3cO\x97+RI\x94\xa2I\x97\x03>\x91\xfe\
+\xf8l\xdf\x9f\x98\x98\xe8\xc5O\x11?\xdc\xbf\xcdJ-x\xbf\x97\xec\xd0\xe2\xeb\
+\xe7\xe7ge5rv\x9byKq\x87hH)"FQZ^>\n\xcd\xcfeI\x95UW\xe7\x98=d\xfe\x00;:\xca\
+\xc8\xceF\xae\xab\xfcX\x9bou\x1e\xb0\xdb\xdb\x0b\x92\x9f\xdf\xb9h\xdd<\xc3F\
+\x02\x07\xf4\xf4\xd4\xd4L\x1e\xdf8\xac6W\xd5\xd4(.\xde\x04\x07\x07\xeb\xb1\
+\x1c\xb8\xb4Q\'J\xf9\xfa>\x00Q\x8dB\xd1\x91\xc0\x03\xff9\xf1\xd8\xb5\xac>\
+\x05\xf4l\x9e\xa5\x9a{\x9b\x99\r\xec^\xb0\x0e\xa1\xa0\xa0P\'FT\x1fC\xcc\xea\
+\xfd\xb6\xc3S\xa2\xe9\xff\x9bmZ;i\xac\xdc\xb9d\xa0\xa6k\xf8\x99\xdf\x8b\x9e\
+\xdf\xf7\x85<$$$].lr\xaa\xb8\xb8XM]\x9e6\xb9\x9e\xe9U\xc8WP\xed*<\x86\xadc\
+\x1d\x0b\xb4\xed\xd4\xda\xd3#\x84\x83\x9av\x90\x021K\xda\xcf\x05myt\xa8\x96\
+\xe1\x7f\xdf\xcf\xef\xc4\xf7ae\x94\xceO\x0e-\xca\xce\xce\xe6\'\xf8\xf4\xfd\
+\xa0\xd6a\x9d\x8fb\xf6QC\xd8\xa8h\xea.\xd99^\xe8\x99\xc7\x9a\xeb\xdfR\xf5L\
+\xbe]\x06\xddC\xfd\xb7\xfc\xa0^\x98[R\x1bq\xf4\xe0\xcf\x81\x0f:\xf1\x1f~\xed\
+]ycHK\xdd\xa4\x07^\xbe\x9a\xcd\x15\xf8\xe4R_S\xf3)]N\xe5?\x18\x0c6L\xe1;F\
+\xde\xb7{\x813\x8f@\x95(u}{\x1bMR\xd0\xfb;\xdf\xd8nO\x83\xda\x9e\x06\xe7\x16\
+H\x01\x15\xce\x14\'s\xee\xe4\xf0\xae\xb3\xfe\xe2g\xf9\xdd\x0bI\t\t)K\x17\x94\
+\xa3v\xa0\x87v.xpi)\xe2\xc3E\xf2\xfc\x04k\x8e\xf9\x8af8c]\x7f\x84\xd4Cu\xe2h\
+\xb3b\xea\x84\xa9~\x96\x97W\x1f\xb7WONL$\x19q2 \x07\x01mu\xb5*LE5\xa6\x16\
+\xfcc\x90\x80\x97$u\xdc"[\x81\xce\xfd\xd4\x0c(4\xad\xb5E\x9aL\x91\x15d\x00\
+\xe66\xc3(\xf6\x9d\xc6z\xb6X\xbeL-\xea:s\xf1x\xdc\xa2\x01\xe6\xa6\x93T\x98\
+\x93\x93S]\xcd"#\xce\xc8\xd2\xa6\x17\xb7\xe3D\x90\n1si#\x07\xb5\xea\xb1|TS\
+\x9f\xb7\xe3\x92\xfd\r\xcc\x88\x18\xf5;\xe9\xa4\xe1\xddw\xefy)\xe2;\xf4XH\
+\x998\xe5W[\x1c)\xcd\xa5\x86j\xb5\x06q\xb5\xbeV\xee-\x1f\xae\xe8\xf7\x04\x98\
+\xa8\xad\xd6\x1b.\x87\x08\x82\xc1\xa9\xd1?\xae\xb9\xaa\xc7\x13|\xeeT\xa7%\
+\x07\xc8UL+\x196i\xdb\xccp\xf7\x13\xc2\xe2M\xe6\xd9\xf5.\\\x1bL\xf8\xfc\xe7\
+\x9d\xcc\x97\x97\x96\xb0\xb5\xa7E\xcb\xf3ES\xbc\xc5K\xf2\'\x98\x84\xf2l\xa4\
+\xad }Y)\x8aJ\xe3\xd3G\xa9=\xa4\xe5\xf7Z\x06\xa1\xc9>\x04\xc8\r\x95Q\xf3\xc9\
+b[\x02;2\xbc\x84\x06\xe6\xa3N\xde\x07\xb2\xb6%\xe5o\xbb\xf99Z%\xccF+V\x84\
+\xc8\xe2F\xb3\x89\xf4\x90\x13\x05\x83Z\xa9\xbb_4\x0f\xdf\xabs\x92\xa6j\x9a\
+\x0c\xee"\xec\xbb\xf2\xcf_6\x1e\x0e\x99\x7f\xd7\x92\xc7U\xe7OW\xba\xb07U\x16\
+\xad\x8d"\x0c\x87\xba\xd5\t#:#\x0b\x94\x93&\xc1\xa1\xe8\xc2\x0ez7\x95\xd8\
+\xea\xd1\xaa\xa7\xa8\x0b\xc2\x9f\xdd!\x1a:}\xc5\x15\xaa1\xd0\xf4w\xe0\x9b\
+\x9f\xc6\xed\x17.\xee\x83\xecJ\xbe\xe7\xc5\x13\xca\x89Q)\xaa\x95\x04o?{i\xec\
+_\xa1\x11\xcco\xd5\x1f(\x13\xf6\x8d\xd1\x11\xda\x98\x05\x88\xeb\xce\xe7\x9f~\
+\x9cHa\xc6&\xd0U\xfc\xf9\xce\xea\xfcTH\xc00OQ}C\xdc\xc2\xd2\xb2\x7f\xbb\x96\
+\x17\xe9\xf7_\xc2\xef\xd9&U)\xde\xf0\xfc\xe3\xd4\xaag\xb9#\xd82a\x93^G\xc4\
+\xa8\x08m\xf2r\x9d\xe9\xab\xb6\xa4\x1f\xa9\x83r\xc3\xfe\xcc\xd8&h\x07\xe8y}\
+\xe8\xcc?d\xba\x9e\xff\x1e\x1f/T\xcdr\x02\xef\xad3$J66\xbe\x00\x85\xca&\xd9\
+\x12\x1e\x18\x1c\x14\xd7N9.\x13\xa1\xc1{\xb7\x89\x1b\xa0\x11\x87T`*\x1c,\x0c\
+\x02\xa9\xa9\xc7\x0e\xefF\xe5\x9b5h_\xea\xc7\r\x9a,\xdbi(\xe3\xab\xab\xf3\
+\xc2\xbd\xc3\x801\x9em$2\xf02vq\xa9W\x18eN\x86\xb8\xb9\xbb\xff\x08W\xbd\xd4b\
+\xe2\x12\x14\xecv?^x~\x0e0\x90\xa1N\\\xaa3\r/:\x8d\xfd\x96Oo1\xc9\x1e\xcd\
+\x0fJ\x18\xb7\x006P}\\PP@\xf0\x81\xc8\x8a\xa6\xd69bxWQ\xe8\x89\xf3\xa5\xccz\
+\x05@\x00\x01\xb20ef\xf5XM~`#h\xa1"\x04\x9b)\x9a_g\x1b\xe5hM\'\xf8\x13\xa5\
+\xac2\xc2"\x0cj<\x17\xab\xf5#\xd2 f#\xb6H\xb8\xafPP\xe8\xbb\x87w\xf3\x12\xac\
+8Ic%p\xa7\x1dZT\xa5X\x89\xfd\xee\xa1\xa9\xde\xb80\xb7\x8cQQ6\xdb\xc5\x88\xf0\
+s\x942\x94\xdc\xc9\xc5\xe5\xe5t\x85\x12oT\x02\xb5\xba\x1fc\xd3c\xc8\xda\xd4\
+\x9a\xb6\xc6\xfd\xac\xfc\x14\x8a\xd4\x89\xa5\xcc*\xf4\xf8\xe5\x0bw7\xdf\xa9\
+\x95\x84H\xdb\x8aI\xd1C\xc3\xc2\xc3\xeb\n\xcb\xcc\xb4\xf7\xff\x16\x0e\xb9E|J\
+]\x8c\xd8ri\x1bY\xac\xa3`\x03\x07\x83<\xa9\xf3\xc6\x08\xc4\xf3B\x1c\x819\x8b\
+\x9b\x8e\x8d\x8b\xf3\xe1\xa7\xa0\x7f\xcb\x12/F\xedf\x12\xa7\x92\x9e\x1d\x0f\
+\xbb\xa3\xf8\xee\xad Y0\xed\'\x18\x1f\xe1\x864\xc9\xe4\x11K\x80\xdbP\xa4T\
+\xc7\xd7\xd5\xd5\x05@q\xb0pP\xdc\xbe\xc9\xcd\x07\xbd$\x12\x96\x9c\xc5\x1f\
+\x99\xddZ\xd6\xa3\xdf\xb9\x15\xda\x1a\x91\xae\xfcM\xdc\xb9|\x9b\xce\xe8\x81@\
+\x86$\xd4\xd5\xcbz2J\xec\xf7N2\x15J\x80!O\x82\x98\xf1\x90+\x94\xcc\xb6\xe9M\
+\xec^\x00\xc8 I\x8d\xdb\xb3I\x89\x89\xd2\xb0|b\x1a/ \x81DLJ*C\x9b\x9c\xa7T\n\
+\xa0\ta\xf42\xfc\x0f@ yy\xef.w4\xd5\x9bzz\x02]\\f\xcen\xc3\xfb\xb7\x81\xa9D\
+\xd98?g}k\x00\xd4\xb7\xbc<\x89\x8c\xe8;\xdf+\xcf\x91\xfc\xab\x8a\x08\x1enn\
+\xc95\xd3\x98D\xc6\xc8\xe5\xfb%\xa8K\xe2\x953\x13!\x06\x12x\xb5\xd9\xfe\xca\
+\x8b?\xab"\xa2\x94\xfe\xf7\xd6\x96\x91\xa2\xe2\xe4\xdc\x1c\xbew\xfaa\xbbBR||\
+\xfc\x1b\xa8\x84\x84\x04\xbe\xf8\xbb\xe5\x88\xba\xbd{\xb4\xa27W\xaa\xaa\xaa\
+\x00h\xc6\x98\xe5pLg\xea\x89n\xef\x94\xad\x0c\x0c 5\xe5\x95\x97744$\'\'/\xdb\
+q\xa5\xcbu\x9f\xff\xc2\xa0\x88\xa7\xa3\x88?p\xe1\xed\xd9\x14\xe2\xe4\x14\x0b\
+\x83.-i\xd1&\x93PP\xd0Z:\\\x82\xb1\xc3\xfb\xe7j\xb1\xc2\xdf\'C\xa4\x92\xcc\
+\x88%\xd8\xd3\xaf\x1f\x9e\x06\xe0&-\xf4\xde\x01W\xd8\x19\x93\x07\x00\xeaO\
+\x1eD\x0f\x9a\x04\xf4\xe4\xc6kl;\xf1P\xc4\xab\x8a\x97;\xeb\xfe4\xe0\x94\x81\
+\xd0\xe2E\xcagri\x83_\xf7Oq\x8e\xc3\xdc.q\x80\x9c\xd5\xd6\xe2`\x85\xb7]\x14\
+\xd0\x03}\xe9\xf5\x17V\x024\x0ff\x7f\x84D\xc1\x9c\xd0\x9a\x037uRh\xba\x14\
+\xcdH\x0cp\x8f\xd9\xd9\xd9\x08b\xf4P\x93:\xe2\x1b\xe2\xcb\xa3\xa5F\xeb7$\xfa\
+\x04U\x9f\xd8=\xdb\xf2\xac\xe2\x92\xa5h:\rj\x06)\nR\xdeE7\x96\x94\x0c\x9b\
+\xb8\xb8"\xb6\x96\x94XZYE\xb0\xefy\x14L\x1e\xc8\xd2\xe0E\x8a}+7b\x89\xab\x7f\
+\xfb\xf6-\xf4\x0f3Q\xc8\xe5\x1e\xbb\x17\x7f\xce\xf4\xb4\xb2\xd8Z\xb7\xff\x97\
+\x0f\xe9YY\x85\x7f\n\xd9X\xf0\xd1\x13\xbb\x84?\xa8\x84\xc5\x0b\xfe-\x9d<\x10\
+\x1d\xd0\xef\xf0\x00\xe6REyB0\x17\xa0\x02\x97\xb2Rc\xe9\xda*\xdd\x96\x14\xb2\
+.\xf2Ns\x91<%Vb\xce\x16\xc5\xc1(T&\xb2\xd7\xb3\xb1,\xa9\xb6\x0e\x0eq\x1cY\
+\x14R\x99\xceU\xbf\n\xf2 9y\x06\x97w\xbb\xf1\x8a\xf2\xf2\xd3\xe6\xaa\x8d\xf6\
+#\x8e\x1d\xec\x92\\\x98!\'\xa2\xfa\x8bD\x84\x84\xf4\t\x8d\xaa\x9fM\xd3\x8e \
+\xc4\xd1b\xd4\x9c\xf9\xef\xf3\xcd\xbf\xe6wu\xad3\xd1P\xaf\xc0=\xe4g\x81\xf2\
+\xfd\xdav\xfa\xf8\x83\xfa\x06\\\xac\xae$v\xed\xd3\xf5\xf0\x07\xee\xfb|\x7f\
+\x004V\xb7\xffCkrc>}a\xfa\xf8\xafL\xfb\x11\x08D\x9a\x05d\xf8\xb5n\x842~\x18\
+\x16\x87\x97C\x93\x0c9YmQTW\x0f\x08\xc6\x99\xe4\xc7A\x9d\xfa\xa6\x1a\x18\x18\
+\xc8\x9e>\xe7\x9e\xbd\xd0\xa2R\xf6=\x9f\x9e\x869\xd6G\x89\xbd\xad\xad\x8b\
+\x95\x9d}\xc7\x89g\xcd\xc1sp\x81#\xe4\x87_\xfd=c\xb476\xf8\xecu\xac\xd9\xfbu\
+\x03\xc4\xe28\x82\xf8a\xeb\xe9$\xc9Yw`F\xb2s\xea\x1a\x1dN\x9dxVVW\xfd\x05)\
+\xd7\x11\xd0\x88|\t2\xe57\xe9r\xfd\xd8)\xfem\xd2\xbca\x19\x90?\xac\xf2\x9acC\
+?x\xdf\x99\x13D\x0f\xde!{\xc4\xb0\x9d\xda\xc4D\x00\xb4\xdf\xd3\xb3y|s\t]\xf0\
+\x7f\xf8s\xff\xf4\xdc\xd6\xd6\x96\x93C\xf1\x8f\x8f\x81\xb1;CRJ\x02\xf0\xce\
+\xe7\x06H\x96e\xc3\xf2\x9f?\x1e\x01\x8e\xd9\xd5\xc7k\xd2\xed~\x8c\xd8\xa0\
+\x85J\xedd\x85\x12\xe6\xd7\x11D\x8ap3\xed\xb8\xe1\xdd\xc6\xd6\xa8\x08\xb1w\
+\xf1\xc3\xbb7>\xd6C\xb6\x13\x80\x96\xb2\x04\xd0\x90\xf6\x03G\xd2\x82\x11\xdc\
+\xe3\x0b\tV\x0c\x9b\xfc\xec\xe1Gy\xd7\xaf1u@\xb1\x14\x80\x97\x9c\x9c\x9a\x9d\
+@\xb4\x9a_9DRN]}\xb4Bi\xc2,\xd8gjD=K\x8b\xa9\xd9v\x91e\xe6]{\xb5d\x81rx\x7f\
+\x1e\xbd\x8c\xa3\xe3\'wAu\xf5u\x07O\xe0\x1d(!\x10@F\xa0\xfb\xdcR\x19\xe4\xf5\
+\xf1\xc5\xd9*\x04\x99\x96\xf3\xdb\xabUS/\xbd\xf8\xf1\x93\x1f\xe8\x0f\xae\xbc\
+\x87\x96.v\x87#\xb1\xe7\xc4\xc7r\xc6\xa7\xad\x00\x82\xad\xab\xabs\xe2\x91\
+\xdby\x8d\x89Y8k\xad\xbfxu\xdfY^\x0e\xec2\x8b\xc2\xd2\xd2\x12\x97\x8d46\xac\
+\x7f\x1b\xd8\xfb\xcc\x9f\x186j!\xcd\x06P-\x10\x00\x9e\xe2,\xa7\xceM\x1e\x8e\
+\xfc,=;D\x0e<\xc5\x01);\xfe\xbbP\xa5\x0b\xe0\x0b\xb0 g\xec\xe4B\xa9\x070\xbd\
+\xbe\n\x8cQ\x97\xd7\xb3\x8a\xaa\x8b\xd8\xa1"_<N~F7[\x8d$\x9b\xa7W\xc6\xf4\
+\x87c#\x06M\x1e\x14\xec<\x06\x08\xc9\xc3\x7f6\xd6\x88\x82\xf7\\x/\x1f\xb9\
+\xb0\xe3\xe6\xc4\x80\xc6)\xf0\xa6\x05Mu~\xdfP1\xb7\xce\xb0yF7\xcbaba\xc1z\
+\xfdzZGX077+\x8b\x86\x17\x96\x17\xfct\x14\x12"q\xca=3\x9c\x99\x91\xe1\x03\
+\xe0\xf5[\xacW=\xba\xb8\xb8%\xc5\xc5\x11\xfcs\xb2V\x18\xa3\x07W\xd0+\xefS\
+\x18\x06\xa8\xcf(\xe1/\x92\xb6\x15\xa0\xb5\xf4\xf5\xf5\xdb\xab+\xcae,\xf0\
+\xaa\xd6\xa6\xc65\x01\xec\xd5\xa2\x1b\xadg\x1am\x8c\x03t\xb4\xbb~\x89\x00g}6\
+\x8e\xf3\xbf\xb1PM1W\xe9q\xdb&\xa0\xfess\xe3\x8c\x10A\x16\x95\x9f\xb0\x87?L.\
+B\x82\x07/s\xabY\x94\r\x18\xc3O\x16\xb9\x1b\xa2-\xda\xb1\xedD\xba,-~Rl\'\x8c\
+\xb8\xf5-\xc7\x92\x8e 2\xfck\'\r\x06%J\rl;\xd9-\xd7\x87\xe6\x9b7h\x97\xb33\
+\x0b*k\xcf%q\x9a\xa5\x15\xb9\xb4\xe9\x01\xf0\x95\x0ca\x1d*\x9d\xb6R\xf8\xba\
+\xb6\n\x8c\xf4\xa6#\xcd6,\xcc\xaa\xde\xaba\x99)u\x96\xfa\xc7r\xba\xee\x96K\
+\x1d\xfb\xc6P\xe3\xd5\xd7h\x9c\xb5\xbbwB\xfe\xdd\x17\xfa\xb8\xe9r\x16&&\xdf\
+\xdcP\xdd\x7f\x92\xb6\xd0a0!Zh\xbcQ\xaa\xfcn\xc0\xccy\xb2\xff\x02\xed\xeb\
+\x84\x02~\xc1\xfa\xa3\xd6k\xe02\x07\x07\x00\x1d\xa6\'\xeb\xea\xd8\xd4\xd55=.\
+z\xfd\x06\x1eW@0\x0f\x9f\xfd7\x0f?\xf0\x0fC\xfe\xfb\x01\x93\xe6\x8c\xdc\xa1\
+\xfc\x92.\xe7\xd3e \x9dd\xc3\xc87\xa4\xc5IZ\xc4PZ}\xacP\xe2:\xb8xQ\xcd\xf6\
+\x11\x0c\x1a_\xdd6\x11\x1a\xfaAx\x14\xf2\x8d\xa0V\rB\x98o\x8f\x92\x81k\x9d\
+\x912\n\xb5Q\xe4*u\x05\x04^Y\x19\x83\xfe"\x81x\x1d\xf97mm\xbd\xfe\xfd\xb5\
+\xcfzM\x16\xfd\xf3\xa6\x05\xdeQZp{\x01^=\x12XJ\x88Er\xc9v\xeb>\xe3\x92]kyX\
+\xca\xfd\xb1\xc6\xdaH9\x1b\'\xa7\xee\xa4\x15\x97w5\xd2\x7f\x18\xf3\xeaC\xbf7\
+\xb7\xee\x9e\xe1\x0bg\x86\x10\xe4\x8c7\x9a\xba\xdc\x19\x81\x0b\xbb\xb6\xbe\
+\x109\rt\xb1A/~\x85a\xf3L\xb1\x8f\xc1\xb7\x9fI\'\x87\xa2\x9e\xeawt\xee\xcdo\
+\x8cF\x83\'WBg\xc2n\x08\xcc#\x96\xa5\x7f\xfb\xc5M\xeaU\x8b\x83\xa2\x9d\x03 \
+\x07.\xa8\x13\x86\xabV\xacd\xaf\xfbKGR\xa28\x12H\xf7\x98\x02\xab\x97\xd6\x19\
+-\xc4KD\x7f_\xd5\xa44{\xc0>\x0b\x97H\x8f2\x0cs\xa3\xfc\xc6\x0e\xd5 W\xfd\xdd\
+_\x81F\x85\xb9kK\x06\xd8\xc5\xfbi\xfb\xb8(\x8f2\xedX-\xbeR\xe82^\x03\x8a\xf7\
+\xc8\xe7e\x15\x9f\xa6S\x03\xb6N\xae+\xe6U\xe7\x14w\x86QcOY\xff\n\xde\x8c\xef\
+\x92\x15\xa5F\xa1\x0e\x9d\xcd\xc6\x8cg\xad\xa3\x88\xf7\x99\xbf\xeb\xd7\x16\
+\x16\x08Q\x91,,,(\xfc\xce\xfb,s\xfd=,,\xf6\x0f\xa7\x0b\x0c\xef\x7fk\x9aiw\
+\x03\x02\xea\xd5\xabW\x96\x16\x16\\vK\xb5\xae\x07\x13\xa9\x06>\xd6\x01\x01\
+\xcf\x91t9\t\xe6\xde7G\xf3\xc1\xc2F/\xcfOd\\v?\xd5\xd6\xd7\xd7\xab\xaa\xaa\
+\xda\xab\xcf\x96\x1b,\x03\x02\x02\xec\xecN\xb6\xc3)5\xd4o\x96-s\x91\x83..\
+\x9c\xb5Stuu\xb9\x1d7\x1e\xd7\\\'\xb3\x05|\xb9\xc8\xb1&\'\'===\x03\xe1\xb6\
+\x86\xbeGe%~\x82\x9b\x86\x8f\x83\x86\'\x1b]\x1d55\x8e\xc0\xaa\x80f\x00\x16\
+\xf6\x02\xber\xd3\x7f\xb9\xa9\t|\x02~8m\xa8MAA\x91b\xdeVP`z\xb5?n\xbbX\x1d;h\
+2G\x05H$\xe0\xafe\xbb\x94\xa8\x82\x824\x80\x83\xca\xcb\x03\x1eV7\xf7%[HL#\
+\xc5~\x9d\xdd\x06>\x1d\x1b\xbe\xdcO\x06\x9e\xba\xf1\xa9\x96\xcd=>\xfa\xf1\
+\x90\x17\xc3\x95\x14t\xd4\xd5s\xbd\xf7I\xf8z\xa9\xa4\x93\xaa\x8f\xbfx\x9e\
+\xd2\x1c\xe0\x19$\xaewx\xe6~\xf1t03\xdb\xeb\rF\xbc\xf7\x0b\xb8=]\x17\x15\x15\
+\x8d\xe7\xbf\x8c\xb8\xc3\xf9\xef\x92\x18\x04"\xb0\xf9\xf7\xbf\x00\xd1\x02\
+\x94\xf8F\xbb\xe7\x87\x05\xf1Ige\x93\x07\xefs\x17\xad8e\x0c\xaa\x04s\xe3Y\n\
+\xb7kL\xea\xd2\x8a\xad\xc3\x8c\xf9\xff\x1e\x164\xe2\xfc2L,a\xa2&\r\x03,d\xb3\
+\xdd\xf2\xb7\x08\xc0\x84\x91\xa7\xbe\xee\xa7\xba\xd5.\x99\x05D\xe0\x1f\xf8j\
+\x1b\x06S\xd9w\x18\xcc\xdaE\x1e\x90:{\x87\xe4\xf1\xc3\xb3\xb0k\xe0sH \x11\
+\xae\xa4\xa4$$\x9a\x9b\x18s\xc6\x1c\xbcG\xc1& \x00\xf7\xe8\x00~K\x84\x93::X\
+\xe3\x9b\xb7\x83/\xce\x80\x8f\x1eQj\xad\xc3\x97]\\?\x03sc%.LshY\xf5c\xc3NY\
+\x8b\xf0\xf5\xf19,)\xb6\x06\x92\xb0\x13,\x1f\x18\xb6~2*\x1b\xfb\x06=TJ\xc1\
+\xf9x\xec\x15Blg\xdf\xc5\xc50%\xf7Cg\xd2\x15V\xec1PL?\xbf\xac\xb5O\x87\x04\
+\x9e-d\xe4\xe4kM\xb6D\xeb\xd4_\x17\xc7\x80\xe0\xbf\x93\xe3.Vj\x8bv#\xc3\xdc\
+\xda+*\xa2S\xe4p\x12\xc1\x1f\xa4\xd9_\xa6M8\x19p\xc4\x95\xc9\xb1$\x01\xebT2\
+\x0b\x80\xce1s\xd7\x11\x1c\xfe\xe1eH\x8a\xc6\xe5\xf1\xce\xb9\xdd&H\x86\xa6\
+\xec\xc4\xa3\xa3\xbc\xfc\x9fu\xe4N\x1d\xfd\xcc\xc5e\xd1\xb2\n\xe3$\xf7f\x9d2\
+\xf6\xbc\x1c+L\x03\xb2\xeb\xd0\x92\xb9)\x0b**\x08|\xd4y\xfb{\x13\x88\xd5\x14\
+!\xbe\x7f0\xe8\xa3g;\xba^\xb2\xd5\x1e|\x08H\xab\x9fA\x8b\xdd\xf2\xce\xed\xc3\
+\xf0\xd0\x90\x14"(h\xf0\xad\x87\x9d]\x8c\xa7d\xcff\xee(\xf4\xf0\x07\x00\xcej\
+J\x19\xef\xa1W\xf7\xd3\xd3\xd3ZL\x12\x1b\xa2klQVT\xa3\xf5\xd0\xb5fs\x1dbL\
+\x14\x08>\x12\xc8\xd5\x85\x14\x03Gy\xd1\xbd\x9d.\x8c\x9f\xaco\xee\x0c\x12\
+\x1eq Ag:>\xd1\x84\x14\xde\xcf\xc6\xce.\xa2 \xad\x96\xad\xf6q\xd0\x1f\xec\
+\xad\xb1\xff\xdf\x1a\xda\x12\x93\xdc`[~z\xf0/\xb9\xae\xfd\xe9-u\xf2\x9eME\
+\xac\xb2\xbd\xab\x95\xcd\xcd\xc9\x07\x9c.0(`6\x91\x97\xe0\x9e0S\xc0\x9c\xd2\
+\xf7\xa4\x0e\xd0\x98\x9eY\x00:\xf5l\xd6\xd0G\xad\xc2\x8b\x1d\xaa\xca\xcb\xf3\
+\xe9\xa3\xf2\x01.P\xd1\xf9\x9d\xf5ps\x99\xc5\xc0\x0b\xfaO$\x0f\xc0 \xf4\x92\
+\x9e\xc0\xac\xc9\x03\x88!\x17a\xf4\xbd_7\xc0/\xb6\x19\xcd\xb5\xb5\xbd2\x9a\
+\x9a\xf4QI\n%$\xa6\x85\xfa\x88\xc1\xa0\'l\x10\xc0Kk\x8f\x9c\xf2\x80"\x05h\
+\xa1\x93\x10\xf0\x12\xffs\x0e\x80\x15\x8e\x88[F\xf8\x9d\xc0\xbb\x94&Yz\xa6BL\
+S]\xdd\xd7\xd7\x97@|{\x03\xb2\xffru\xcfO\xf9\x15\xa9\xda\x9bs\xe9\xb9l\xff\
+\xea\xb7\xa17?\x85N;}\xa0\xb94A\xa4\x18\xca\xabW\xd6X\xb4\xd2I\xb30\x1aw\xd2\
+\xf8\xe1\xb7X\xc3\xbf~\x89\x04\x06\xf6T\x1f\x0f\x10\x9e\x82G\xaf\xf6\x04;\
+\xea\xb4bN\t\x7f~\x8b\x18\xdb\x13\x90\xabn1\xa8I}\t6\x03\xf3\xee\xcep\xf4\
+\x98\xf5\x1e\\\xe1\xcaB\xf6h=-/\xa1\x1f\x96G\x90s|\x852_\x16\xf8\x18\x0e\x80\
+\x94\xdfB\xa7o<\x00\x1d\\Xm\x9dE\x1d\x7f\x04\xd8d=\x16\xc9\xf4\x04d\x8cb2z\
+\xe4j{\xbdX\xa1\x1c\xe3\x8d\xc0\x0e3\xce\xab\xc9\xcbF\xaf\x0b\xe9\xc5K\x9ay\
+\x97je\x9f&\xd4\xfd\xf3\xcb\x97c\xc7`\x9bzu\xc6:\x9d\x11\xf6\xdf\x04^\x81y\
+\x17\xdc\xcesX5\r\xbe\x9f\xcb\x0f\xd4M\xed\xb2\xb7\xf7[4\xbf\xae\x0cY|8\xef\
+\x16}\x7f\x10\xdb\xc4\xbd\x907C(T\xf5M\xe7r\xbb\xab\xc7\x84\xc1\xeb"\x9d\xa2\
+\xb2\x91\xd7\x81e\xa5\x1e\xe7\xdbg\xcfU\xdb\xef\xba\xf0\xefr\xe2\xa1\x1al\
+\xe1\xa4[?\xca\xfd\xcf\x85\xb7V\x86~\xcc\xc4\xa6r\xf6o\x14\xc0[b\xf2\x8f\x19\
+\x8a\x14:\xda=\xbe6\xed\x1a\xa6\x04w\xc5|D\xed\x0ctF~\x99\xf1\x12H\x16(\x15\
+\x0b\xe2\xfbb\x15\x9b\xff\x89q\x02\xeaqa\xee\xd4a\xc2\x90\xca\xb9\xf2\xc9\
+\xd6\n\xa7\x15\xc5\xe9\xc7\x9f\xef\xc7\'\x8a\xeex3&\xf6\x06\x84\x1dG\xf1~\
+\x10\xe3\x1d\x17\xcb\xa4\xb3\xec^\xf0"\x97\xc6\xf3\x7f\xc3P\x9b_\xd0\x9bi(C/\
+\x9fki\x97\x9c\xfa\x97Be0\x86\x1e\xa2\x967\x17:**Nu\x13\x06*2\xd2F\xcf\xff\
+\xf3\xd9\xe1\xff\xcf\x97o\xb1/ED|\x0e\xd0\xc0\xf4\x16\xca7a>\xaa\xd41\xd5\
+\xfd)\x11\xb2\x88S\xaf\xcc\xdey\xebgs\xa0\xa4\xf8\x9c\x1a\xd3\x10\x83\x9az24\
+\xcf\xde\x86\xbe\xde\xc1\xc2\x95D\x1eSw\x99k\x81\xeb\x8d\xe4\xda\x18\xa5\xad\
+\x1d@m\x12\xabQ\xb1\x80\x9e:\xcf\x07\x85j\xbc\x89g\xd3x\xfe\xad\xd2d\xb3\x9c\
+\xd2\x02\x81\xca\xd8V\x7f\x96\xfe%:\xf6M\x9d)\x82Z\xedM\x13\xff\xcbJ~\xca\
+\x81\xea\x05\xd9\xfc\x1d\x9f\xdeV\x14\xda\xb1\xf5\xcc\x8a\xf3\xc7\x80\xca({\
+\x93\x00si\x13\xd8<A\x85j\x9d\xb1\xed\\W\x18\x7f\x93\xf2\x8fX\xe8\xfa\xaf\
+\xc7Yj\xab\xc1=\x82\xcc\x19\xbc\x91ck\xac\x96\xc6\x8c\xa2.\x80\xc6\xc3\xabS\
+\x1e\x99?\xe8\xa0W\xd1{\xc7\x9e\xa9u\xc4YX\xdb\xfa\x91\xc9\x9a\xc3\xfa\xa8-\
+\xbd\x8f\xe7\xdf\xc7\x17\xbde\xfa\xcc|6>\xefeE\xf1\xd6K\xea^\xb3t!i\xbe]\xa9\
+\xed?\xa5\xb2\xbc\xdf\xc9\x99/5<\n\xea\xc7R)\xc6\xbf\x1e\x9buJ>\x12\xb5vb\
+\xa8\x8b}\xfe\xa6&\xa6\xce\xa5#\xf6\xa9FZ\\\x8b\xb4h\xa7\xc2\xa6\x90\x9dH\
+\xbf:\x80\xf9\xbabZ\x92y\x1b\xd8\xda\xaeql\x8b\x12\xec\x8b:\xb9m\xde\x89\xe8\
+\xc2\x82&`\x142X>NV\x99|-/?j\x9f@dt\x19\xca\x8a\xe2\x8e\x86\x8e^\x8e\x00\x12\
+/s<\x85\x0e\xe1\xcd\xd3\x98}\xe6\xb8E2\xc4\x0c0e\xf1v\x84e($,\xbc\x95z*\xc6\
+\x1as^\xd0$\xb5=\xad\xc1\x96\xe8\xe8\xe8\xf2r\r9uy\x14\x8aq\xd6\x18\x9bD[\
+\x8eYB\xacp\xdbN\ruw@=l\xf5\x85tx\x9ey\xf0e\x03\x10\x05\x02\xb4#?\xc5\xa2\
+\xbb@\xb6\x02%\x8f\xd3V\xdc\x97S+{{\xe4 -\xa6?\xbf0>\x8d\xd4\xafMO\x7fE\x06\
+\xdc\xf4\xf0\xaes\xea\xa8y\xcb\xaat\xb1\x16\x13\xd7\xe7\xcf?\xc6-\x08\xc4\
+\x11\x82\x00P\x8c\x8f\x8b\xa3\x10\xf4\xd3R\xc6\xd7_\xbc=Yv\xdd\xea\r\xbe\xbf\
+\xbf\x07,\xce2\xdc#\xbc?Q\x8e\x96\x8e\x96\x16\xa0<O\xcf\xdb\xa3\xf9\nw\xfd\
+\xcd6\xd7\x83\xbe\x90W\xfa,D\x04\xbf\x19\xb53\x93\xe7upX\xcc\xb7\xd4O\xe8S\
+\xfe\x83F->\x06\xb61\xf4\x89\xee\\\xdc\xbd\x8f\xe7\xc7\x03\xc2\xa57sMr1(+?\
+\xba\xba\xe7rX;ti\x1b\xdb\xe6\x0f\x10\x17WS\xf8H\x914y\xd0\xf3\x08\xfdD\xf9\
+\xc5cl\xf7b\x8e\xe2\x81\x81\xe0&\xa1\xbb\xbb[\x89\xfez\xe4}<(\xa8\xfa\x18\
+\xd8\xe0\x82\xbb\x00a4B\xd0,\x8cI\xf3\xfd\rf\xf1\xbe\xe6\xb2\x9d\xc7p\xdc[\
+\x98\x9b\xcf\xeeP,?`\x89\xcf\xe3\xd4\xaa\xea\xa1\xe0L\xd7j\xabC\xe5\x11UE\
+\x81&@@\tS)\xbd\x9f\x84@ \x079\xbe,\xc9\x10\xfa(\x11w\xce\x16\xb3{/\xcd\x8ez\
+q\\\xdb\xde\xb3[\xb2\xb0`\xc0\xcd\xb1\'JQ\x99p\x92\xbeF\t\xe1\xffVl\r\xd4!\
+\x192\xa5\xd8\xb2ZE\xff\xfb\xd7/p\xe0\xe8{\xc7\x95F4\\\xc0\xe3\xea\xb1,^_s\
+\x9a\xdb\x12\x0f\xf5\xf6\x82pW\x0f\xae\xac3\x86\\\xb1PB\x92\x93\x93\xff\xf9y\
+\xc0\xe4cc#\x81\xf9I\xcd\xf1b%L\xeb\x88\x05R\xfb\xc9\r(J&Z\x00\xbdmd\xf4\x93\
+\x9fTZ\xa5\xee\xd0\xe6Rk\xe3R\xb1Wt,\xd6(@]^-~X4\xb7\xf8\x1d+\xe6{r,\xc7\xcd\
+\xee\xae\x8c^\xc2D?A\xca2\xcf\x8dW(((\xa5\x88V\x19\\||>\x90\x9d\xc3\xed\x9bE\
+\xc3\x92jC\x03B\xe3\xc5j\xfdz&\xc0\xdeQ\xe0a\xa20\x0b\x04\x0b\x1d\xb7\x1bt\
+\xb4\xcav\xcb\xd3\xe0\xcd\x99\xda\x8a\xe9ji\x81@W\xb7\xe5w\xb16D6C\x1f\x96\
+\x9666&\x18S\xc6-\xdc\xf5\xcf`\xb0\xba\x01`\xaf\x00\xbf\xe4\xc0D]\xb0\xc8\
+\xc2p\xd1C\x99.CE\xde)(\xeb/\x02\xaa\xe7u\xff\xb9O\xd7\xa3\xeb\xe5\x08\xa0\
+\x0e\xca\x8f.\xbc\x0e;$\n\x94e9;\xf1\x0bRb/\x17\x1a\xcd\x14\xde+?A5\xcc:Bc\
+\xf7\xf9+\x8a\x9a\xf6T\x13c\xe7w\x0cU\x14\xfe\x9a\xed\xd7h\x90\xa9\xf94M\xff\
+\x0b\xb2\x87\t\xc1\xe6zK\x8c\x9a\x8d\x97\xd7\xbaeu\x16\xc6\x19\xfa\x1d\x03\
+\xa2\xea\xcd\x9f\xe3sY\x89\x9b#EC\x9f\xbcR\x06\xeaM\n\xbc\x9f\xd2\x16\xd2\
+\xb1\x81\xc9\x07mo\x1b\xc7\x0f\xbf\x89\x14\xeb4p\xb4\xb4<\xb8\xbb\xf324\xcc\
+\x15oj\xd7c\x91\x9f\xad_\xae\x1c\x85\x8a\x0c$&\xe2\xf6l\n\xb1\xb3\x03]\xd4f\
+\x107h\xb2\xbb\xbb\x1b)\x06\x06\x81\x83\x83\xf2\xe9\xb7\x9d\xb4R\xa0\x07\xb9\
+\x81\x1bg\xb7\x07.\xbc\xf2\xb3c\xcd\x8407`\xbd\xf8a\x80\xce\xd3;@?\xe9\xa3>\
+\xe8pe\xbe%!\xb9\xf0\xe2\x0f\x16\xa6J\x97\x03\x07\x95\x97\x03\x12\x88_\xe4\
+\xdf\xe9s2\xe0\x94\xa9q]\x8dx\xc8)\xde\xbe\xf5\xbf\x84\xd0\x01\x9agfqQ\xd2\
+\x1a\x01h\x0eDa\x1er\r\xfcP\x0b\xacka\xde\xd2\xf2r\xeb\x95n\xff\x87o}[\xbd\
+\xbd\xc2,\xa9\xb2E*\xff\xdb/^x\xe9\xa5\x98\x7f\xcc\x8e\xa1\xafY$\x8b\x14\x03\
+t(\xd3\xe8w!\xea?\xb7\xb7\x80\xc0\xb0_\xe5*\x99\x85T1\x89\x99\xc5\xc0\xda)P\
+\x91\xacHiG]\x98\x08U\xf1?$C\xc6&&\x86v/\xb4\x98\x8a\xff\x89\x96BQ\t\t\t7}\
+\xee\x16\t\xbe\xec\x89\x83\xab6\xbd\t\xc0\xf9\x93\xc7\x19\xb1\x12\xa3"\xd1{\
+\xb3\x12\x8fA]\xbe3\x8f\xcb\x0f\r\x0f\x1b\x06\xdcC76\x0clZ\x9cx\xf8\xb9\xb8\
+\x08\xd1C\xc1\xc1 h\n\xc4\x0c\xf8J\x9f%\xf5\xdf3\x8at\xf0gy2J\xca\x12\x144$p\
+{\xb5e\x83\xb6\xbazG{;\xdf\x97/0W\xd5\xb6m\'\x1e\x1cT7ww\x04\xc0(\x97\xb4\
+\x9f\xab\xf5\x19999%\xcc\xa8(F\x88Q\xcf\xfd\xba\xf7\x13$\x8d\xfd\x18%B\xb0-\
+\x8f\x04\xf2\xe8\x10\xf8g\xfe6\xec\xb8F\xc5\xc1 \xfd\xc5\xf1\xdcO\xe4X%*e\
+\xffN\xdaU\x00\xf1\xcbB\x14?\xcc\x91>&\x8a-\xb0\xe7\xc6gZ\x97\xb6y\xe6\xd9\
+\xa6G3\x7fr\xc2Db\x05\xe4\x17PO6\xf0\x1b\x9f\xb2\xee\xffy(u}{{U<\x94\xc4\x1b\
+U\x94\xee\xc1\x9eNNB\x82\x16*\x02\x8c.\xe0BIcg\x16\x0b\x1f!Y\xcd~\xb6\xa5\
+\x11\xb2\x97\xee..\x00\xc2\xcc\xf0\xe0c\x853\xc6e\xf8\xf1\x14\xb6d\x14\xbb{x\
+d\x88N\x99%\xbd\xcb\x07t\x7fr\xc8\x86\x82\xa2\xa2+\xdcf\x08\xd0\xeb\xde.x\
+\xa2\x8b7\x97\xb8\x11C\x97b\xd4_\xff\xceV\xd2\xb3\xa7c\xbe.\x02\x9450%\xfbce\
+\xfd\xb8\xad|\xa4\xc4\xc4R4x\xd3Vn\xc7\x0bUj\xf4h\xbdp\xc2(i\xf1\xf3\xed7\
+\xb8!}T\x0cp\xc0D\xfcJ0)I\x0b\xe8\xd9\xccz\xe9\xed\xed\x05\xe6\x9bZ\xc5\x90M\
+\xd6|\x82\x03\x1f=\xd1;\x0b\xf8i$R:\x9aB\tG2D\x89a\xbfx\xc2\x8c}m\xe7g~\x97\
+\xc1\xa7\xcf#{{4-\xec>>>30\xce\xc9q\xa8K\xe2\xdf|\xe7X\x9d\x90n\x17 9\x12\
+\xac\xa2\xca\xf82#U\x85\xf2\x99\\\x89E6@Y\xffx\xf1\xfb\x1d\xf5\xc9.r\x8e\xc3\
+=\xc8\xc2\xfab\xdf\x92\xc6\x8d\x13\xdcz\xf1\xe3\x03\xf5\xb9*\xd9s-\x9a\x9eV>\
+b\xceS\x8a\xe0\x0b\xba\xbfR\xee\xbf\xd4(jW}\x06j\x90\x0cq\xf2\x1d\xa7& x\x1b\
++\xf1Y\xbe _\x80"\xbe\xfa\x18\x11\x11\x11\xea\xfa\nk\x88X3in\xf6[\x1fU\xbao\
+\'\xf5\xdc\xd6\xbc\x0b\xeb.\xdeCl\xc4\xe1\xed\xc3,\xcc\xed\xdf\xe3\xb2]\xa0E\
+\xef\xfd(F\x17B\xdd\xf4K\x94\x13\xe2\x8d?u\x9e\xff\xea\xed\r2\xb7\x8e{O\x8c\
+\x19\x17\x1bk\xceNB\xea\xf8\xef`\x86\x96\xe7\xd3w\nB"\xa2\xfb\xad\x0c\x88YXx\
+8\x80>L\xb64\xfc?\xf2\xe9\xeb\xb4\x98pP\xd3\xe6CG\xa1\xf9}h\xb8\xd8\x94\x82v\
+\xb8\xfdP\x80fXH\xb7\xa4Cg"\xd2F\xa1V]Q\xc7\x9c\xf4\xe3=\xe6\xda\xc0\x1e\xa1\
+\x9f\xa3\x06M\xf4J\x12A\xf5L\xabp\x8f\x9a\xc5/\xdc\xdc\xa9\x10\xb3\xb1\xfb\
+\xe4\xc8\xaf\xd9\xab*\xb6\xb2\xab*\x1a\x065\x8b\xc77+p\x0fuusm \x9d\x80\xfa\
+\x1dH0O\xcf\x88X\x98/\xc5\x9f\x11\xe2\x0flC\x029\xf1\x90\xb7W\xfbv\x19\xa8\
+\xec\xc2\xe1\xf0N\xed,\x05\xba\xcf\x11\xc6\xc4\x85\xf5CCCc\x974\xc9\x90?\x7f\
+<~\x0f;\xb4\xd4h7\xd0&J)y6\xce\x89(\xe3W\x87\'C\n\xfb\xb7\x7f\xaa\xd1H|\xfc\
+\xb1S_\xa5\xdd\x10&(\xe0\xaf\xa4\xaeNb\x8a\xdfVR\x12\x9e1:\xc3\x18\x16\x16\
+\x06\x0c\x02\xe41\xcf\x9c\xc6\xe6^\xed#\xf4\xc6\x07pU\x9e\x98=\x9b\x93\x04\
+\xad\x9bg\xb9YYY\x99\x99W\x0fO49\xda\x1f\xd9\xd7\xf4XR\x99F\x05\x04\x04\xe6\
+\x8e\xfd\x04\ri\xf0\xb6}Z;\x0f2Gw\xaa\xaa\xacW\xd6\xd6\xc8\x05\x10\x83V\xe1\
+\\\x9a\x85(,}\x12\xbf\xe7\xb6\x9d\xfe\xeb7VW\x7fxx\x80!\xa6\x15A\xa0.\xf5L_\
+\xb2\x158\xa4\x81\xee\x181kB\x95\xbf\x81\x1d\x1f\x03\xd0\xec\x05 \x8c\xbf %\
+\xc8\xcd\xc3\x83\x8f\x1cKA\xdf\x01QNQQ\xf5?+N>\x1e\x1e\xc03!\x04}\x02gr\x14f\
+T\x94\xcb\xa4\x10\x10)\xcer\x02\x81UY\xb9\xce{1\xb4W\xab\xbe\xfa\xe0\n\xe4\
+\xcda\xb9\xde\xdc\xc6&\n\x93\xee\xef\x91\xde\xb3\x15g;\x001\xf5t\xe3m\xd0+ov\
+\xc7\xc7\x00\xa1\xd7$\x9d\x11\xbb\xc3\xf1\xaa\xcf\x80E72\n\xf2*\xb2\xd6\xb9_\
+\x00S\xcb\xcc\x84~2!L#\xcd\xfd\xfb\xdat\xcc\xa1\xe5\x13\x0bK\x06\xf2\x94{6\
+\x9bD\x11\xe6\xeb\xd7\xca\xfe\x15\xcc\xefT\x16\xbf\x9b/\xff\x03\xd3]\x86\xfa\
+Q\xe8o\x8b\x063\xe7\xf4~\xe6\xdc\x9c\xc2\xa6\x81m2D\xb3\xa5\xd9R\xc72\xac\
+\x1aZ\xf3_71\xd5\xc75=\xcf\xed\xda\x16\x14\xe3\x8f\xaevvL\xe9rC\xbb\xce\xa0\
+\xffyZ\xe8\xdb\xb5\x01\xf4;2\x18A\x07\x9d+\x95\x00\x15\x89:\x11\xd54\xa6r\n\
+\x18\xcdF\x06\xc3\xfd&\xce\xd4u\x07\x00#\xe6\x8e\xef\xb8?\xbe\xf0\xed\xd7\
+\x1fj\'\x15\x86\x86\x86^>\xebQl~\x93\xade#\x8dU\xf1\xd1_|\xf8\xfb\xf7\xc4\
+\xc3\x97\x93\xf4-\x05\xc5>\xbc\xa3\xb5\x15 VZ:\xba\xb299\xda\xfe/\xd4\xe3\
+\xd8\xcc\xe0\x8bp\xfb\xd5f\x92\xb0\xaf\x1f#\xd8\x08\x01\xfa\xcc\xc3\x89=\xc4\
+g\xe7\xe4\xe4\x14\x14T\x98\xa5#\xad\x03Q\x91c\xc5\xc7\xc4\xc4\xc4\xc7g\x92%\
+\xd2H\x17\xa9\xc0\xdc2\x8a\x19\x1f\xe1\x000\x8doZINt~\x1a\x16be\x88\x02\x0cT\
+\xd7W\xf5\xf9\xcf\xb6\x0b\tM$\x91b\x11\xabz\'o\xbfx\xfc;@\x00\x1a\xd2\x0eI~\
+\x16$<\xdb\x98\x97\xf7NP\xeed\xb5e\x92\x9f\xf5\xe6\xe6F\xf9\x19\x940y\xa0\
+\xae\x1e+F\xdd\xf9z\xdb\xa4\\IU4R_o\x05\xb0\x973\xfa\xed\xd5\xb6\x19\x7f\xf6\
+\x92\x15\x05\x9f\xaeg\xf7\x8f\x90\xc0\xc10\x18lyY\x9b\x1e.,\x934\x90.\x81\
+\xddI\xc8\x89]\xb2\x06LX\x9d\x1d\xad(\xa9\x1d\xa0\\\xe6\x10P3\xb9\xec\xf3\
+\x131dD\x89\xb5\xcf:\x05`\xc162\x04\x00I\xf9\xfb\xfb\xe3\xa7\xb3\x15[\x93"g\
+\xf8\x16\x1a\x13w"K\xb5)\xe7\x15\xcez\x8d211\x9d\xffU\xf1\x9d\xd1=J\x01\x18\
+\xf0\xbb\x10%Fi\xdeONk7\xbe\xec\xae\xae\xae\xce\x10\xef.\x83\x1cc\xc1\\\xba\
+\xfe\x1c\xd9__\xbb\xbea\xd7\xa8}f\xc1St\xc0*L,\xaa\xa9\x89g\x93\xb8p\xd7\x9f\
+\x84\xba\xf8A\xf3\x87h%\xab\x9e$\xc4\xf8=O\xd7\x1e\x1f\x1fON\xec$%\xc3\x17\
+\x8f\xcdc\xaa\xf4;\xfet?\x9c>L\xc7\x07\x14\x1e\xf3\xdd\xdc\xd0\xe5x}\x93\x12\
+\x8f\xfe\x00\xea}e\x018qD{~\x8a\xb3\xd3\xd3\x05\xf7\xae\x8e\x8e\xcc\xc2\xb6\
+\x97\xe7\x87/||^c\x0b\x0b\x1a:2\xbc^\xe7[\xd3\x05\x92\xaaes\xfb\xe3\x99\x1d\
+\x1d\x1do"\x07\x80u==\xb9\x97\xedz\x9eoj\xca\xb0\x9d\x03f4n\x00\xd1\xa8\xab\
+\xab\x0b@H\xe51\x9f\xb9\xac\xbaS\x9b\xde+,\xf2\xb4\xf4t\xd8\xe9)\x80\x03\xc0\
+\xbe\x1be\xff=\x10\xa6\x8c1q\xd8\xe8\xbcO\xb7\xccI\xcf\xcc\xcc\xcc\xcd\r\xbc\
+\xdb\x0e\xcf\xf5\xdeO\x8f\x8c\x8c\x8c%\xe3\xf6\xf0\xf4lin>j\x17\xe4\xe0\x10\
+\xf5\x89\x94c8R\x01H\xf8\x156EVz\xfa\xbf\x98z\xceC?M\xe9\x8c\x06#\xbe\xa2\
+\x08\xb83\x8e\xda\x9c\x9f\xff0\xcaN\xfd<M\xf94=\x91\xfde\x16v]g:\xfc\xef\x1b\
+\x9d\xe8\x95\x0eOG\xda\xe4\x7f\xf7>\xb8\x9ac\x8a\x90\xf2>\xdd\xec\t\x84\xaf4\
+]]]UUU\xd1\x84\xac\xc2s\xd2\xd2\xd2rs5\xf1\xcd\x9d\x9d\xa7\x0f\xae\x80\x06\
+\x80\xafw\xa4z*\x89+Sx\xef\xe1\xaaH\xbe}\xfbv\xd10P\x00w\x06\xeeQ\xa2\xc2\
+\x10\xde\xff\x15.Q\x89\x8a\x04\xae\xd3\xb2\x0b\x08\xd8\x80{\x1c\\\xdd7XN\xce\
+\x90b2\x8f\xbc\xe5\xf7J\x90!\'\'_k\xb0\xc4\xfe\x8ei\x01\xba\xdb!\xea!\x8e\
+\x1e\x9c\xc8\x11\xe8\xb0\x9c\xcc\xa9\xab\xabC|\x85\x95\xf5\xc53\x0e\xaf\xda\
+\xa0+#\xf07\xe0\x0cF\xa13\xb0\xeb\xf3s\xd6\xcf\x9f\x8a\x808\xcf\x0e\x0f\x0f\
+\x81\xb8#\xe8\x80B\xd3d2\x8d\x02i\xfb\x19\x0e\x94\xc7\xc4\xc4DD]\xddFz\xe3\
+\x0c\x16\xd4\xf7\xf7\xe6X\xb3\xc6\xe05\x08\x08\x8b\x8c\xcbn+\xd7\xf1`"\xbb\
+\xa1\xa1\xe1\xfa^j+\xfb\x8b\xa7\neA\xcd\x9f\xa6\x1a\xff\xd8\xeb\xda\xc5c\xf8\
+\xe1\xa1\xb0\x98\xab\xbe\xc7\xc9\x12\x9b\x90\x90"\xe2\x9b2\x1cA\x18\xd5\x97\
+\xc3\xde`D\xe0\x06\xfd\xfd\xfd\x9cQ\xb8e\x1eg\x1b\x80\xfe\x05\x8aJDDT\xf6\
+\x05R/W\xd5\x12\xf0\xe4S~\x03\x8c\x91\xbaY@@\x00\xb0z&\xb7c\xb5\x047\xa8\xf4\
+vI\xe1\xfe\xdc\xda\x1bC]\x01{^A\xeb\xfd\xcb\xe8\xeb2\r\xa5lp\x9a\xe9\xfe\xd5\
+=\xb3n\xf3\xde\x15\x00\xb5\xe2\x1c\xbeW\x95\xad\ns\tUaR7R\xb6\\\x0f\xc7c%\
+\x14\xc6G\x90\x85,\xf4\xf2\x93<\xc3z\t\xf7\x00\x86\xd1\x93\xe2\t\x94\xca\x94\
+\xbc"n\x8d\x93\xac\xd41.\x8a\xf8\x8eu\x87#\xcc\xf9\xfa\xf6J\x8b\xc6\x9f\x06\
+\x94q\x97(\xd0\x17\xd1"\x12d5!4m\xe6@\xd7\x9cy\xc1\xbcA\x96\xfa1\xad\xb1\x1a\
+&\x93\xef\x18\x86\xcf\xc9\x07\xac\xbc\xbc\xbc\x9f?\x0f\xa6\x8a\x8e\x87\xb1)\
+\xaa\x180\xc8Jy\x0b\x95\xd27\x7f\xf0\x1e\xf4h6sH\xdfP\x1b;JRc\xaai\x82\xe6\r\
+i\xf6\xd7\xbf\xa1#8V\xa9z\xaa\x80:B\x86\xb3\x02c\x8f\xa6\x107\xdb\xaa\xd2\
+\xde)\x9a\x94\x99\x0e\xe21\xf3f\x8a\x92c\xbd\xa2L\xad0W\xaf\xeb_,\xbbb\xd9\
+\x95\xb2\xf5FH\x99.\nc\xaf\xac\x1a`\xe1\xd5\xd1\x8c\xf1\xd3\xb6)\x9cp\xfa\
+\xb6yV"/\x02\xda\xd2\x8dK\xcd\x84\x98\x0f\x1a6\xfb\x94\xf5\xb0\xd9\xeeub\x16\
+\x98\x92 \x06\xbeF \xcd\x81\xfb\xa6\x0e\x90k\xc5\xa3i\xbc7~\xfa\xa1#HVc<%\
+\x821\x8e3\xf6\xd0\x0c\x06k\xe2\x7f\x8b\x15\x9f\x13z\xae\xda\xbacv\xb2\x17\
+\xad\xadc?2\xf4\xc5\xc1\xb1\xc2G";\x16\x89\xbd\x11@\x08\x96\xf8Z{\xc1~n@X\
+\x83\x8c\xc4\x82\xd67\xe4\xf3\x8e\x98\xde\xa9\xe8\x9d\x83S\'h\x94\xb6\xfcC\
+\xe7\x8a\xe2\xbfK\x0e\x87F\x83j\xd5\x90\xab\xe9\xff\xb7\x8b\xff\xa5B\xf0\x7f\
+\x7f\x0c\xf0\x18hhJ,\xe3\xacr\x9c\x05\x02^\xd2\xe2\nb\xd5"F\xdf\xfe\x17]\xf7\
+x\x8e' )
+
+def getBitmap():
+ return BitmapFromImage(getImage())
+
+def getImage():
+ stream = cStringIO.StringIO(getData())
+ return ImageFromStream(stream)
+
+def getIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getBitmap())
+ return icon
+
diff --git a/jet_tools/JetCreator/midifile.py b/jet_tools/JetCreator/midifile.py
new file mode 100755
index 0000000..63449c0
--- /dev/null
+++ b/jet_tools/JetCreator/midifile.py
@@ -0,0 +1,1579 @@
+"""
+ File:
+ midifile.py
+
+ Contents and purpose:
+ Utilities used throughout JetCreator
+
+ Copyright (c) 2008 Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+import struct
+import copy
+import array
+
+# JET events
+JET_EVENT_MARKER = 102
+JET_MARKER_LOOP_END = 0
+JET_EVENT_TRIGGER_CLIP = 103
+
+# header definitions
+SMF_HEADER_FMT = '>4slHHH'
+SMF_RIFF_TAG = 'MThd'
+
+SMF_TRACK_HEADER_FMT = '>4sl'
+SMF_TRACK_RIFF_TAG = 'MTrk'
+
+# defaults
+DEFAULT_PPQN = 120
+DEFAULT_BEATS_PER_MEASURE = 4
+DEFAULT_TIME_FORMAT = '%03d:%02d:%03d'
+
+# force note-offs to end of list
+MAX_SEQ_NUM = 0x7fffffff
+
+# MIDI messages
+NOTE_OFF = 0x80
+NOTE_ON = 0x90
+POLY_KEY_PRESSURE = 0xa0
+CONTROL_CHANGE = 0xb0
+PROGRAM_CHANGE = 0xc0
+CHANNEL_PRESSURE = 0xd0
+PITCH_BEND = 0xe0
+
+# System common messages
+SYSEX = 0xf0
+MIDI_TIME_CODE = 0xf1
+SONG_POSITION_POINTER = 0xf2
+SONG_SELECT = 0xf3
+RESERVED_F4 = 0xf4
+RESERVED_F5 = 0xf5
+TUNE_REQUEST = 0xf6
+END_SYSEX = 0xf7
+
+# System real-time messages
+TIMING_CLOCK = 0xf8
+RESERVED_F9 = 0xf9
+START = 0xfa
+CONTINUE = 0xfb
+STOP = 0xfc
+RESERVED_FD = 0xfd
+ACTIVE_SENSING = 0xfe
+SYSTEM_RESET = 0xff
+
+ONE_BYTE_MESSAGES = (
+ TUNE_REQUEST,
+ TIMING_CLOCK,
+ RESERVED_F9,
+ START,
+ CONTINUE,
+ STOP,
+ RESERVED_FD,
+ ACTIVE_SENSING,
+ SYSTEM_RESET)
+
+THREE_BYTE_MESSAGES = (
+ NOTE_OFF,
+ NOTE_ON,
+ POLY_KEY_PRESSURE,
+ CONTROL_CHANGE,
+ PITCH_BEND)
+
+MIDI_MESSAGES = (
+ NOTE_OFF,
+ NOTE_ON,
+ POLY_KEY_PRESSURE,
+ CONTROL_CHANGE,
+ CHANNEL_PRESSURE,
+ PITCH_BEND,
+ SYSEX)
+
+# Meta-events
+META_EVENT = 0xff
+META_EVENT_SEQUENCE_NUMBER = 0x00
+META_EVENT_TEXT_EVENT = 0x01
+META_EVENT_COPYRIGHT_NOTICE = 0x02
+META_EVENT_SEQUENCE_TRACK_NAME = 0x03
+META_EVENT_INSTRUMENT_NAME = 0x04
+META_EVENT_LYRIC = 0x05
+META_EVENT_MARKER = 0x06
+META_EVENT_CUE_POINT = 0x07
+META_EVENT_MIDI_CHANNEL_PREFIX = 0x20
+META_EVENT_END_OF_TRACK = 0x2f
+META_EVENT_SET_TEMPO = 0x51
+META_EVENT_SMPTE_OFFSET = 0x54
+META_EVENT_TIME_SIGNATURE = 0x58
+META_EVENT_KEY_SIGNATURE = 0x59
+META_EVENT_SEQUENCER_SPECIFIC = 0x7f
+
+# recurring error messages
+MSG_NOT_SMF_FILE = 'Not an SMF file - aborting parse!'
+MSG_INVALID_TRACK_HEADER = 'Track header is invalid'
+MSG_TYPE_MISMATCH = 'msg_type does not match event type'
+
+LARGE_TICK_WARNING = 1000
+
+# default control values
+CTRL_BANK_SELECT_MSB = 0
+CTRL_MOD_WHEEL = 1
+CTRL_RPN_DATA_MSB = 6
+CTRL_VOLUME = 7
+CTRL_PAN = 10
+CTRL_EXPRESSION = 11
+CTRL_BANK_SELECT_LSB = 32
+CTRL_RPN_DATA_LSB = 38
+CTRL_SUSTAIN = 64
+CTRL_RPN_LSB = 100
+CTRL_RPN_MSB = 101
+CTRL_RESET_CONTROLLERS = 121
+
+RPN_PITCH_BEND_SENSITIVITY = 0
+RPN_FINE_TUNING = 1
+RPN_COARSE_TUNING = 2
+
+MONITOR_CONTROLLERS = (
+ CTRL_BANK_SELECT_MSB,
+ CTRL_MOD_WHEEL,
+ CTRL_RPN_DATA_MSB,
+ CTRL_VOLUME,
+ CTRL_PAN,
+ CTRL_EXPRESSION,
+ CTRL_BANK_SELECT_LSB,
+ CTRL_RPN_DATA_LSB,
+ CTRL_SUSTAIN,
+ CTRL_RPN_LSB,
+ CTRL_RPN_MSB)
+
+MONITOR_RPNS = (
+ RPN_PITCH_BEND_SENSITIVITY,
+ RPN_FINE_TUNING,
+ RPN_COARSE_TUNING)
+
+RPN_PITCH_BEND_SENSITIVITY = 0
+RPN_FINE_TUNING = 1
+RPN_COARSE_TUNING = 2
+
+DEFAULT_CONTROLLER_VALUES = {
+ CTRL_BANK_SELECT_MSB : 121,
+ CTRL_MOD_WHEEL : 0,
+ CTRL_RPN_DATA_MSB : 0,
+ CTRL_VOLUME : 100,
+ CTRL_PAN : 64,
+ CTRL_EXPRESSION : 127,
+ CTRL_RPN_DATA_LSB : 0,
+ CTRL_BANK_SELECT_LSB : 0,
+ CTRL_SUSTAIN : 0,
+ CTRL_RPN_LSB : 0x7f,
+ CTRL_RPN_MSB : 0x7f}
+
+DEFAULT_RPN_VALUES = {
+ RPN_PITCH_BEND_SENSITIVITY : 0x100,
+ RPN_FINE_TUNING : 0,
+ RPN_COARSE_TUNING : 1}
+
+# initialize logger
+midi_file_logger = logging.getLogger('MIDI_file')
+midi_file_logger.setLevel(logging.NOTSET)
+
+
+class trackGrid(object):
+ def __init__ (self, track, channel, name, empty):
+ self.track = track
+ self.channel = channel
+ self.name = name
+ self.empty = empty
+ def __str__ (self):
+ return "['%s', '%s', '%s']" % (self.track, self.channel, self.name)
+
+
+#---------------------------------------------------------------
+# MIDIFileException
+#---------------------------------------------------------------
+class MIDIFileException (Exception):
+ def __init__ (self, stream, msg):
+ stream.error_loc = stream.tell()
+ self.stream = stream
+ self.msg = msg
+ def __str__ (self):
+ return '[%d]: %s' % (self.stream.error_loc, self.msg)
+
+#---------------------------------------------------------------
+# TimeBase
+#---------------------------------------------------------------
+class TimeBase (object):
+ def __init__ (self, ppqn=DEFAULT_PPQN, beats_per_measure=DEFAULT_BEATS_PER_MEASURE):
+ self.ppqn = ppqn
+ self.beats_per_measure = beats_per_measure
+
+ def ConvertToTicks (self, measures, beats, ticks):
+ total_beats = beats + (measures * self.beats_per_measure)
+ total_ticks = ticks + (total_beats * self.ppqn)
+ return total_ticks
+
+ def ConvertTicksToMBT (self, ticks):
+ beats = ticks / self.ppqn
+ ticks -= beats * self.ppqn
+ measures = beats / self.beats_per_measure
+ beats -= measures * self.beats_per_measure
+ return (measures, beats, ticks)
+
+ def ConvertTicksToStr (self, ticks, format=DEFAULT_TIME_FORMAT):
+ measures, beats, ticks = self.ConvertTicksToMBT(ticks)
+ return format % (measures, beats, ticks)
+
+ def ConvertStrTimeToTuple(self, s):
+ try:
+ measures, beats, ticks = s.split(':',3)
+ return (int(measures), int(beats), int(ticks))
+ except:
+ return (0,0,0)
+
+ def ConvertStrTimeToTicks(self, s):
+ measures, beats, ticks = self.ConvertStrTimeToTuple(s)
+ return self.ConvertToTicks(measures, beats, ticks)
+
+ def MbtDifference(self, mbt1, mbt2):
+ t1 = self.ConvertToTicks(mbt1[0], mbt1[1], mbt1[2])
+ t2 = self.ConvertToTicks(mbt2[0], mbt2[1], mbt2[2])
+ return abs(t1-t2)
+
+
+#---------------------------------------------------------------
+# Helper functions
+#---------------------------------------------------------------
+def ReadByte (stream):
+ try:
+ return ord(stream.read(1))
+ except TypeError:
+ stream.error_loc = stream.tell()
+ raise MIDIFileException(stream, 'Unexpected EOF')
+
+def ReadBytes (stream, length):
+ bytes = []
+ for i in range(length):
+ bytes.append(ReadByte(stream))
+ return bytes
+
+def ReadVarLenQty (stream):
+ value = 0
+ while 1:
+ byte = ReadByte(stream)
+ value = (value << 7) + (byte & 0x7f)
+ if byte & 0x80 == 0:
+ return value
+
+def WriteByte (stream, value):
+ stream.write(chr(value))
+
+def WriteBytes (stream, bytes):
+ for byte in bytes:
+ WriteByte(stream, byte)
+
+def WriteVarLenQty (stream, value):
+ bytes = [value & 0x7f]
+ value = value >> 7
+ while value > 0:
+ bytes.append((value & 0x7f) | 0x80)
+ value = value >> 7
+ bytes.reverse()
+ WriteBytes(stream, bytes)
+
+#---------------------------------------------------------------
+# EventFilter
+#---------------------------------------------------------------
+class EventFilter (object):
+ pass
+
+class EventTypeFilter (object):
+ def __init__ (self, events, exclude=True):
+ self.events = events
+ self.exclude = exclude
+ def Check (self, event):
+ if event.msg_type in self.events:
+ return not self.exclude
+ return self.exclude
+
+class NoteFilter (EventFilter):
+ def __init__ (self, notes, exclude=True):
+ self.notes = notes
+ self.exclude = exclude
+ def Check (self, event):
+ if event.msg_type in (NOTE_ON, NOTE_OFF):
+ if event.note in self.notes:
+ return not self.exclude
+ return self.exclude
+
+class ChannelFilter (EventFilter):
+ def __init__ (self, channel, exclude=True):
+ self.channel = channel
+ self.exclude = exclude
+ def Check (self, event):
+ if event.msg_type in (NOTE_ON, NOTE_OFF, POLY_KEY_PRESSURE, CONTROL_CHANGE, CHANNEL_PRESSURE, PITCH_BEND):
+ if event.channel in self.channel:
+ return not self.exclude
+ return self.exclude
+
+#---------------------------------------------------------------
+# MIDIEvent
+#---------------------------------------------------------------
+class MIDIEvent (object):
+ """Factory for creating MIDI events from a stream."""
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ if msg_type == SYSEX:
+ return SysExEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif msg_type == END_SYSEX:
+ return SysExContEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif msg_type == META_EVENT:
+ return MetaEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ else:
+ high_nibble = msg_type & 0xf0
+ if high_nibble == NOTE_OFF:
+ return NoteOffEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == NOTE_ON:
+ return NoteOnEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == POLY_KEY_PRESSURE:
+ return PolyKeyPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == CONTROL_CHANGE:
+ return ControlChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == PROGRAM_CHANGE:
+ return ProgramChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == CHANNEL_PRESSURE:
+ return ChannelPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ elif high_nibble == PITCH_BEND:
+ return PitchBendEvent.ReadFromStream(stream, seq, ticks, msg_type)
+ else:
+ stream.Warning('Ignoring unexpected message type 0x%02x' % msg_type)
+ def WriteTicks (self, stream, track):
+ WriteVarLenQty(stream, self.ticks - track.ticks)
+ track.ticks = self.ticks
+ def WriteRunningStatus (self, stream, track, filters, msg, data1, data2=None):
+ if not self.CheckFilters(filters):
+ return
+ self.WriteTicks(stream, track)
+ status = msg + self.channel
+ if track.running_status != status:
+ WriteByte(stream, status)
+ track.running_status = status
+ WriteByte(stream, data1)
+ if data2 is not None:
+ WriteByte(stream, data2)
+ def CheckFilters (self, filters):
+ if filters is None or not len(filters):
+ return True
+
+ # never filter meta-events
+ if (self.msg_type == META_EVENT) and (self.meta_type == META_EVENT_END_OF_TRACK):
+ return True
+
+ # check all filters
+ for f in filters:
+ if not f.Check(self):
+ return False
+ return True
+
+ def TimeEventStr (self, timebase):
+ return '[%s]: %s' % (timebase.ConvertTicksToStr(self.ticks), self.__str__())
+
+#---------------------------------------------------------------
+# NoteOffEvent
+#---------------------------------------------------------------
+class NoteOffEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, note, velocity):
+ self.name = 'NoteOff'
+ self.msg_type = NOTE_OFF
+ self.seq = seq
+ self.ticks = ticks
+ self.channel = channel
+ self.note = note
+ self.velocity = velocity
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ ticks = ticks
+ channel = msg_type & 0x0f
+ note = ReadByte(stream)
+ velocity = ReadByte(stream)
+ if msg_type & 0xf0 != NOTE_OFF:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return NoteOffEvent(ticks, seq, channel, note, velocity)
+ def WriteToStream (self, stream, track, filters=None):
+ # special case for note-off using zero velocity
+ if self.velocity > 0:
+ self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
+ if track.running_status == (NOTE_OFF + self.channel):
+ self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
+ else:
+ self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, 0)
+ def __str__ (self):
+ return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
+
+#---------------------------------------------------------------
+# NoteOnEvent
+#---------------------------------------------------------------
+class NoteOnEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, note, velocity, note_length, note_off_velocity):
+ self.name = 'NoteOn'
+ self.msg_type = NOTE_ON
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.note = note
+ self.velocity = velocity
+ self.note_length = note_length
+ self.note_off_velocity = note_off_velocity
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ note = ReadByte(stream)
+ velocity = ReadByte(stream)
+ if msg_type & 0xf0 != NOTE_ON:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ if velocity == 0:
+ return NoteOffEvent(ticks, seq, channel, note, velocity)
+ return NoteOnEvent(ticks, seq, channel, note, velocity, None, None)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
+ def __str__ (self):
+ if self.note_length is not None:
+ return '%s: ch=%d n=%d v=%d l=%d' % (self.name, self.channel, self.note, self.velocity, self.note_length)
+ else:
+ return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
+
+#---------------------------------------------------------------
+# PolyKeyPressureEvent
+#---------------------------------------------------------------
+class PolyKeyPressureEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, note, value):
+ self.name = 'PolyKeyPressure'
+ self.msg_type = POLY_KEY_PRESSURE
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.note = note
+ self.value = value
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ note = ReadByte(stream)
+ value = ReadByte(stream)
+ if msg_type & 0xf0 != POLY_KEY_PRESSURE:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return PolyKeyPressureEvent(ticks, seq, channel, note, value)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, POLY_KEY_PRESSURE, self.note, self.value)
+ def __str__ (self):
+ return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.value)
+
+#---------------------------------------------------------------
+# ControlChangeEvent
+#---------------------------------------------------------------
+class ControlChangeEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, controller, value):
+ self.name = 'ControlChange'
+ self.msg_type = CONTROL_CHANGE
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.controller = controller
+ self.value = value
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ controller = ReadByte(stream)
+ value = ReadByte(stream)
+ if msg_type & 0xf0 != CONTROL_CHANGE:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ if controller >= 120:
+ return ChannelModeEvent(ticks, seq, channel, controller, value)
+ return ControlChangeEvent(ticks, seq, channel, controller, value)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
+ def __str__ (self):
+ return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
+
+#---------------------------------------------------------------
+# ChannelModeEvent
+#---------------------------------------------------------------
+class ChannelModeEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, controller, value):
+ self.name = 'ChannelMode'
+ self.msg_type = CONTROL_CHANGE
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.controller = controller
+ self.value = value
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ controller = ReadByte(stream)
+ value = ReadByte(stream)
+ if msg_type & 0xf0 != CONTROL_CHANGE:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ if controller < 120:
+ return ControlChangeEvent(ticks, seq, channel, controller, value)
+ return ChannelModeEvent(ticks, seq, channel, value)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
+ def __str__ (self):
+ return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
+
+#---------------------------------------------------------------
+# ProgramChangeEvent
+#---------------------------------------------------------------
+class ProgramChangeEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, program):
+ self.name = 'ProgramChange'
+ self.msg_type = PROGRAM_CHANGE
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.program = program
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ program = ReadByte(stream)
+ if msg_type & 0xf0 != PROGRAM_CHANGE:
+ stream.seek(-1,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return ProgramChangeEvent(ticks, seq, channel, program)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, PROGRAM_CHANGE, self.program)
+ def __str__ (self):
+ return '%s: ch=%d p=%d' % (self.name, self.channel, self.program)
+
+#---------------------------------------------------------------
+# ChannelPressureEvent
+#---------------------------------------------------------------
+class ChannelPressureEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, value):
+ self.name = 'ChannelPressure'
+ self.msg_type = CHANNEL_PRESSURE
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.value = value
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ value = ReadByte(stream)
+ if msg_type & 0xf0 != CHANNEL_PRESSURE:
+ stream.seek(-1,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return ChannelPressureEvent(ticks, seq, channel, value)
+ def WriteToStream (self, stream, track, filters=None):
+ self.WriteRunningStatus(stream, track, filters, CHANNEL_PRESSURE, self.value)
+ def __str__ (self):
+ return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
+
+#---------------------------------------------------------------
+# PitchBendEvent
+#---------------------------------------------------------------
+class PitchBendEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, channel, value):
+ self.name = 'PitchBend'
+ self.msg_type = PITCH_BEND
+ self.ticks = ticks
+ self.seq = seq
+ self.channel = channel
+ self.value = value
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ channel = msg_type & 0x0f
+ value = (ReadByte(stream) << 7) + ReadByte(stream) - 0x2000
+ if msg_type & 0xf0 != PITCH_BEND:
+ stream.seek(-2,1)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return PitchBendEvent(ticks, seq, channel, value)
+ def WriteToStream (self, stream, track, filters=None):
+ value = self.value + 0x2000
+ if value < 0:
+ value = 0
+ if value > 0x3fff:
+ value = 0x3fff
+ self.WriteRunningStatus(stream, track, filters, PITCH_BEND, value >> 7, value & 0x7f)
+ def __str__ (self):
+ return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
+
+#---------------------------------------------------------------
+# SysExEvent
+#---------------------------------------------------------------
+class SysExEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, msg):
+ self.name = 'SysEx'
+ self.msg_type = SYSEX
+ self.ticks = ticks
+ self.seq = seq
+ self.length = len(msg)
+ self.msg = msg
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ pos = stream.tell()
+ length = ReadVarLenQty(stream)
+ msg = ReadBytes(stream, length)
+ if msg_type != SYSEX:
+ stream.seek(pos,0)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return SysExEvent(ticks, seq, msg)
+ def WriteToStream (self, stream, track, filters=None):
+ if not self.CheckFilters(filters):
+ return
+ self.WriteTicks(stream, track)
+ WriteByte(stream, SYSEX)
+ WriteVarLenQty(stream, self.length)
+ WriteBytes(stream, self.msg)
+ track.running_status = None
+ def __str__ (self):
+ fmt_str = '%s: f0' + ' %02x'*self.length
+ return fmt_str % ((self.name,) + tuple(self.msg))
+
+#---------------------------------------------------------------
+# SysExContEvent
+#---------------------------------------------------------------
+class SysExContEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, msg):
+ self.name = 'SysEx+'
+ self.msg_type = END_SYSEX
+ self.ticks = ticks
+ self.seq = seq
+ self.length = len(msg)
+ self.msg = msg
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ pos = stream.tell()
+ length = ReadVarLenQty(stream)
+ msg = ReadBytes(stream, length)
+ if msg_type != END_SYSEX:
+ stream.seek(pos,0)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ return SysExContEvent(ticks, seq, msg)
+ def WriteToStream (self, stream, track, filters=None):
+ if not self.CheckFilters(filters):
+ return
+ self.WriteTicks(stream, track)
+ WriteByte(stream, END_SYSEX)
+ WriteVarLenQty(stream, self.length)
+ WriteBytes(stream, self.msg)
+ track.running_status = None
+ def __str__ (self):
+ fmt_str = '%s:' + ' %02x'*self.length
+ return fmt_str % ((self.name,) + tuple(self.msg))
+
+#---------------------------------------------------------------
+# MetaEvent
+#---------------------------------------------------------------
+class MetaEvent (MIDIEvent):
+ def __init__ (self, ticks, seq, meta_type, msg):
+ self.name = 'MetaEvent'
+ self.msg_type = META_EVENT
+ self.ticks = ticks
+ self.seq = seq
+ self.meta_type = meta_type
+ self.length = len(msg)
+ self.msg = msg
+ @staticmethod
+ def ReadFromStream (stream, seq, ticks, msg_type):
+ pos = stream.tell()
+ meta_type = ReadByte(stream)
+ length = ReadVarLenQty(stream)
+ msg = ReadBytes(stream, length)
+ if msg_type != META_EVENT:
+ stream.seek(pos,0)
+ raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
+ obj = MetaEvent(ticks, seq, meta_type, msg)
+ return obj
+ def WriteToStream (self, stream, track, filters=None):
+ if not self.CheckFilters(filters):
+ return
+ self.WriteTicks(stream, track)
+ WriteByte(stream, META_EVENT)
+ WriteByte(stream, self.meta_type)
+ WriteVarLenQty(stream, self.length)
+ WriteBytes(stream, self.msg)
+ track.running_status = None
+ def __str__ (self):
+ fmt_str = '%s: %02x' + ' %02x'*self.length
+ return fmt_str % ((self.name, self.meta_type) + tuple(self.msg))
+
+#---------------------------------------------------------------
+# MIDIControllers
+#---------------------------------------------------------------
+class MIDIControllers (object):
+ def __init__ (self):
+ self.controllers = []
+ self.rpns = []
+ for channel in range(16):
+ self.controllers.append({})
+ self.controllers[channel] = copy.deepcopy(DEFAULT_CONTROLLER_VALUES)
+ self.rpns.append({})
+ self.rpns[channel] = copy.deepcopy(DEFAULT_RPN_VALUES)
+ self.pitchbend = [0] * 16
+ self.program = [-1] * 16
+ self.pressure = [0] * 16
+
+ def __str__ (self):
+ output = []
+ for channel in range(16):
+ output.append('channel=%d' % channel)
+ output.append(' program=%d' % self.program[channel])
+ output.append(' pressure=%d' % self.pressure[channel])
+
+ output.append(' controllers')
+ for controller in self.controllers[channel].keys():
+ output.append(' %03d: %03d' % (controller, self.controllers[channel][controller]))
+
+ output.append(' rpns')
+ for rpn in self.rpns[channel].keys():
+ output.append(' %05d: %05d>' % (controller, self.rpns[channel][rpn]))
+ return '\n'.join(output)
+
+
+ def Event (self, event):
+ """Process an event and save any changes in controller values"""
+ # process control changes
+ if event.msg_type == CONTROL_CHANGE:
+ self.ControlChange(event)
+ elif event.msg_type == CHANNEL_PRESSURE:
+ self.PressureChange(event)
+ elif event.msg_type == PROGRAM_CHANGE:
+ self.ProgramChange(event)
+ elif event.msg_type == PITCH_BEND:
+ self.PitchBendChange(event)
+
+ def PitchBendChange (self, event):
+ """Monitor pitch bend change."""
+ self.pitchbend[event.channel] = event.value
+
+ def ProgramChange (self, event):
+ """Monitor program change."""
+ self.program[event.channel] = event.program
+
+ def ControlChange (self, event):
+ """Monitor control change."""
+ controller = event.controller
+ if controller in MONITOR_CONTROLLERS:
+ channel = event.channel
+ self.controllers[channel][controller] = event.value
+ if (controller == CTRL_RPN_DATA_MSB) or (controller == CTRL_RPN_DATA_LSB):
+ rpn = (self.controllers[channel][CTRL_RPN_MSB] << 7) + self.controllers[channel][CTRL_RPN_LSB]
+ if rpn in MONITOR_RPNS:
+ value = (self.controllers[channel][CTRL_RPN_DATA_MSB] << 7) + self.controllers[channel][CTRL_RPN_DATA_LSB]
+ self.rpns[channel][rpn] = value
+
+ # reset controllers
+ elif event.controller == CTRL_RESET_CONTROLLERS:
+ self.ResetControllers[event.channel]
+
+ def PressureChange (self, event):
+ """Monitor pressure change."""
+ self.pressure[event.channel] = event.value
+
+ def ResetControllers (self, channel):
+ """Reset controllers to default."""
+ self.controllers[channel] = DEFAULT_CONTROLLER_VALUES
+ self.rpns[channel] = DEFAULT_RPN_VALUES
+ self.pressure[channel] = 0
+
+ def GenerateEventList (self, ticks, ref_values=None):
+ """Generate an event list based on controller differences."""
+ events = EventList()
+
+ # if no reference values, based on default values
+ if ref_values is None:
+ ref_values = MIDIControllers()
+
+ # iterate through 16 MIDI channels
+ for channel in range(16):
+
+ # generate RPN changes
+ for rpn in self.rpns[channel].keys():
+ value = self.rpns[channel][rpn]
+ if value != ref_values.rpns[channel][rpn]:
+ events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_MSB, rpn >> 7))
+ events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_LSB, rpn & 0x7f))
+ events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_MSB, value >> 7))
+ events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_LSB, value & 0x7f))
+
+ # generate controller changes
+ for controller in self.controllers[channel].keys():
+ if self.controllers[channel][controller] != ref_values.controllers[channel][controller]:
+ events.append(ControlChangeEvent(ticks, -1, channel, controller, self.controllers[channel][controller]))
+
+ # generate pressure changes
+ if self.pressure[channel] != ref_values.pressure[channel]:
+ events.append(ChannelPressureEvent(ticks, -1, channel, self.pressure[channel]))
+
+ # generate program changes
+ if self.program[channel] != ref_values.program[channel]:
+ if self.program[channel] in range(128):
+ events.append(ProgramChangeEvent(ticks, -1, channel, self.program[channel]))
+
+ # generate pitch bend changes
+ if self.pitchbend[channel] != ref_values.pitchbend[channel]:
+ if self.pitchbend[channel] in range(-8192,8191):
+ events.append(PitchBendEvent(ticks, -1, channel, self.pitchbend[channel]))
+
+ return events
+
+#---------------------------------------------------------------
+# EventList
+#---------------------------------------------------------------
+class EventList (list):
+ def __init__ (self):
+ list.__init__(self)
+
+ def FixNoteLengths (self):
+ midi_file_logger.debug('Fix note lengths')
+
+ # search for note-on's in event list
+ for index in range(len(self)):
+ event = self[index]
+ if event.msg_type == NOTE_ON:
+ note_off_ticks = event.ticks + event.note_length
+
+ # check for note-on occuring before end of current note
+ for i in range(index + 1, len(self)):
+ event_to_check = self[i]
+ if event_to_check.ticks >= note_off_ticks:
+ break
+
+ # adjust note length
+ if (event_to_check.msg_type == NOTE_ON) and (event_to_check.note == event.note):
+ midi_file_logger.debug('Adjusting note length @ %d' % event.ticks)
+ event.note_length = event_to_check.ticks - event.ticks
+ break
+
+ def ChaseControllers (self, end_seq, start_seq = 0, values = None):
+ midi_file_logger.debug('ChaseControllers from %d to %d' % (start_seq, end_seq))
+
+ # initialize controller values
+ if values is None:
+ values = MIDIControllers()
+
+ # chase controllers in track
+ for i in range(start_seq, min(end_seq, len(self))):
+ values.Event(self[i])
+
+ # return new values
+ return values
+
+ def SelectEvents (self, start, end):
+ midi_file_logger.debug('SelectEvents: %d to %d' % (start, end))
+ selected = EventList()
+ for event in self:
+ if event.ticks >= start:
+ if event.ticks >= end:
+ break
+ midi_file_logger.debug('SelectEvent: %s' % event.__str__())
+ selected.append(event)
+ return selected
+
+ def MergeEvents (self, events):
+ # copy events and sort them by ticks/sequence#
+ self.extend(events)
+ self.SortEvents()
+
+ def InsertEvents (self, events, seq):
+ self[seq:seq] = events
+ self.RenumberSeq()
+
+ def DeleteEvents (self, start_index, end_index, move_meta_events=None):
+ # default parameters
+ if start_index is None:
+ start_index = 0
+ if end_index is None:
+ end_index = len(self)
+
+ #print("\n")
+ #for evt in self[start_index:end_index]:
+ # print("%d %s" % (evt.ticks, evt))
+
+ # delete events
+ delete_count = 0
+ move_count = 0
+ for event in self[start_index:end_index]:
+ #Bth; Added this so we always get clip end events; clips that ended on last measure wouldn't end on repeat
+ if (event.msg_type == CONTROL_CHANGE) and \
+ (event.controller == JET_EVENT_TRIGGER_CLIP) and \
+ ((event.value & 0x40) != 0x40):
+ pass
+ else:
+ if (move_meta_events is None) or (event.msg_type != META_EVENT):
+ self.remove(event)
+ delete_count += 1
+
+ # move meta-events
+ else:
+ event.ticks = move_meta_events
+ move_count += 1
+
+ midi_file_logger.debug('DeleteEvents: deleted %d events in range(%s:%s)' % (delete_count, start_index, end_index))
+ midi_file_logger.debug('DeleteEvents: moved %d events in range(%s:%s)' % (move_count, start_index, end_index))
+
+
+ def SeekEvent (self, pos):
+ for i in range(len(self)):
+ if self[i].ticks >= pos:
+ return i
+ return None
+
+ def RenumberSeq (self):
+ seq = 0
+ for event in self:
+ event.seq = seq
+ seq += 1
+
+ def SortEvents (self):
+ self.sort(self.EventSorter)
+ self.RenumberSeq()
+
+ @staticmethod
+ def EventSorter (x, y):
+ if x.ticks == y.ticks:
+ return cmp(x.seq, y.seq)
+ else:
+ return cmp(x.ticks, y.ticks)
+
+ def DumpEvents (self, output, timebase):
+ if output is not None:
+ for event in self:
+ output.write('%s\n' % event.TimeEventStr(timebase))
+ else:
+ for event in self:
+ midi_file_logger.debug(event.TimeEventStr(timebase))
+
+#---------------------------------------------------------------
+# MIDITrack
+#---------------------------------------------------------------
+class MIDITrack (object):
+ """The MIDITrack class implements methods for reading, parsing,
+ modifying, and writing tracks in Standard MIDI Files (SMF).
+
+ """
+ def __init__ (self):
+ self.length = 0
+ self.events = EventList()
+ self.end_of_track = None
+ self.channel = None
+ self.name = None
+
+ def ReadFromStream (self, stream, offset, file_size):
+ self.stream = stream
+ ticks = 0
+ seq = 0
+ running_status = None
+ tick_warning_level = stream.timebase.ppqn * LARGE_TICK_WARNING
+
+ # read the track header - verify it's an SMF track
+ stream.seek(offset)
+ bytes = stream.read(struct.calcsize(SMF_TRACK_HEADER_FMT))
+ riff_tag, track_len = struct.unpack(SMF_TRACK_HEADER_FMT, bytes)
+ midi_file_logger.debug('SMF track header\n Tag: %s\n TrackLen: %d' % (riff_tag, track_len))
+ if (riff_tag != SMF_TRACK_RIFF_TAG):
+ raise MIDIFileException(stream, MSG_INVALID_TRACK_HEADER)
+ self.start = stream.tell()
+
+ # check for valid track length
+ if (self.start + track_len) > file_size:
+ stream.Warning('Ignoring illegal track length - %d exceeds length of file' % track_len)
+ track_len = None
+
+ # read the entire track
+ note_on_list = []
+ while 1:
+
+ # save current position
+ pos = stream.tell()
+
+ # check for end of track
+ if track_len is not None:
+ if (pos - self.start) >= track_len:
+ break
+
+ # are we past end of track?
+ if self.end_of_track:
+ stream.Warning('Ignoring data encountered beyond end-of-track meta-event')
+ break;
+
+ # read delta timestamp
+ delta = ReadVarLenQty(stream)
+ if ticks > tick_warning_level:
+ stream.Warning('Tick value is excessive - possibly corrupt data?')
+ ticks += delta
+
+ # get the event type and process it
+ msg_type = ReadByte(stream)
+
+ # if data byte, check for running status
+ if msg_type & 0x80 == 0:
+
+ # use running status
+ msg_type = running_status
+
+ # back up so event can process data
+ stream.seek(-1,1)
+
+ # if no running status, we have a problem
+ if not running_status:
+ stream.Warning('Ignoring data byte received with no running status')
+
+ # create event type from stream
+ event = MIDIEvent.ReadFromStream(stream, seq, ticks, msg_type)
+
+ if self.channel == None:
+ try:
+ self.channel = event.channel
+ except AttributeError:
+ pass
+
+ # track note-ons
+ if event.msg_type == NOTE_ON:
+
+ """
+ Experimental code to clean up overlapping notes
+ Clean up now occurs during write process
+
+ for note_on in note_on_list:
+ if (event.channel == note_on.channel) and (event.note == note_on.note):
+ stream.Warning('Duplicate note-on\'s encountered without intervening note-off')
+ stream.Warning(' [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))
+ note_on.note_length = event.ticks - note_on.ticks - 1
+ if note_on.note_length <= 0:
+ stream.Warning('Eliminating duplicate note-on')
+ event.ticks = note_on.ticks
+ self.events.remove(note_on)
+ """
+
+ note_on_list.append(event)
+
+ # process note-offs
+ if event.msg_type == NOTE_OFF:
+ for note_on in note_on_list[:]:
+ if (event.channel == note_on.channel) and (event.note == note_on.note):
+ note_on.note_length = event.ticks - note_on.ticks
+ note_on.note_off_velocity = event.velocity
+ note_on_list.remove(note_on)
+ break
+ #else:
+ # stream.Warning('Note-off encountered without corresponding note-on')
+ # stream.Warning(' [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))
+
+ # check for end of track
+ elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_END_OF_TRACK:
+ self.end_of_track = event.ticks
+
+ # BTH; get track name
+ elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_SEQUENCE_TRACK_NAME:
+ self.name = array.array('B', event.msg).tostring()
+
+ # append event to event list
+ else:
+ self.events.append(event)
+ seq += 1
+
+ # save position for port-mortem
+ stream.last_good_event = pos
+
+ # update running statusc_str(
+ if msg_type < 0xf0:
+ running_status = msg_type
+ elif (msg_type < 0xf8) or (msg_type == 0xff):
+ running_status = None
+
+ # check for stuck notes
+ #if len(note_on_list):
+ # stream.Warning('Note-ons encountered without corresponding note-offs')
+
+ # check for missing end-of-track meta-event
+ if self.end_of_track is None:
+ self.last_tick = self.events[-1].ticks
+ stream.Warning('End of track encountered with no end-of-track meta-event')
+
+ # if track length was bad, correct it
+ if track_len is None:
+ track_len = stream.tell() - offset - 8
+
+ return track_len
+
+ def Write (self, stream, filters=None):
+ # save current file position so we can write header
+ header_loc = stream.tell()
+ stream.seek(header_loc + struct.calcsize(SMF_TRACK_HEADER_FMT))
+
+ # save a copy of the event list so we can restore it
+ save_events = copy.copy(self.events)
+
+ # create note-off events
+ index = 0
+ while 1:
+ if index >= len(self.events):
+ break
+
+ # if note-on event, create a note-off event
+ event = self.events[index]
+ index += 1
+ if event.msg_type == NOTE_ON:
+ note_off = NoteOffEvent(event.ticks + event.note_length, index, event.channel, event.note, event.note_off_velocity)
+
+ # insert note-off in list
+ for i in range(index, len(self.events)):
+ if self.events[i].ticks >= note_off.ticks:
+ self.events.insert(i, note_off)
+ break
+ else:
+ self.events.append(note_off)
+
+ # renumber list
+ self.events.RenumberSeq()
+
+ # write the events
+ self.running_status = None
+ self.ticks = 0
+ for event in self.events:
+
+ # write event
+ event.WriteToStream(stream, self, filters)
+
+ # restore original list (without note-off events)
+ self.events = save_events
+
+ # write the end-of-track meta-event
+ MetaEvent(self.end_of_track, 0, META_EVENT_END_OF_TRACK,[]).WriteToStream(stream, self, None)
+
+ # write track header
+ end_of_track = stream.tell()
+ track_len = end_of_track - header_loc - struct.calcsize(SMF_TRACK_HEADER_FMT)
+ stream.seek(header_loc)
+ bytes = struct.pack(SMF_TRACK_HEADER_FMT, SMF_TRACK_RIFF_TAG, track_len)
+ stream.write(bytes)
+ stream.seek(end_of_track)
+
+ def Trim (self, start, end, slide=True, chase_controllers=True, delete_meta_events=False, quantize=0):
+ controllers = None
+
+ if quantize:
+ # quantize events just before start
+ for event in self.events.SelectEvents(start - quantize, start):
+ midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), start))
+ event.ticks = start
+
+ # quantize events just before end
+ for event in self.events.SelectEvents(end - quantize, end):
+ midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), end))
+ event.ticks = end
+
+ # trim start
+ if start:
+
+ # find first event inside trim
+ start_event = self.events.SeekEvent(start)
+ if start_event is not None:
+
+ # chase controllers to cut point
+ if chase_controllers:
+ controllers = self.events.ChaseControllers(self.events[start_event].seq)
+ controller_events = controllers.GenerateEventList(0)
+ midi_file_logger.debug('Trim: insert new controller events at %d:' % start)
+ controller_events.DumpEvents(None, self.stream.timebase)
+ self.events.InsertEvents(controller_events, start_event)
+
+ # delete events
+ midi_file_logger.debug('Trim: deleting events up to event %d' % start_event)
+ if delete_meta_events:
+ self.events.DeleteEvents(None, start_event, None)
+ else:
+ self.events.DeleteEvents(None, start_event, start)
+
+ # delete everything except metadata
+ else:
+ self.events.DeleteEvents(None, None, start)
+
+ # trim end
+ end_event = self.events.SeekEvent(end)
+ if end_event is not None:
+ midi_file_logger.debug('Trim: trimming section starting at event %d' % end_event)
+ self.events.DeleteEvents(end_event, None)
+
+ # trim any notes that extend past the end
+ for event in self.events:
+ if event.msg_type == NOTE_ON:
+ if (event.ticks + event.note_length) > end:
+ midi_file_logger.debug('Trim: trimming note that extends past end %s' % event.TimeEventStr(self.stream.timebase))
+ event.note_length = end - event.ticks
+ if event.note_length <= 0:
+ raise 'Error in note length - note should have been deleted'
+
+ midi_file_logger.debug('Trim: initial end-of-track: %d' % self.end_of_track)
+ self.end_of_track = min(self.end_of_track, end)
+
+ # slide events to start of track to fill hole
+ if slide and start:
+ midi_file_logger.debug('Trim: sliding events: %d' % start)
+ for event in self.events:
+ if event.ticks > start:
+ event.ticks -= start
+ else:
+ event.ticks = 0
+ self.end_of_track = max(0, self.end_of_track - start)
+ midi_file_logger.debug('Trim: new end-of-track: %d' % self.end_of_track)
+
+ self.events.RenumberSeq()
+ self.events.FixNoteLengths()
+
+ def DumpEvents (self, output):
+ self.events.DumpEvents(output, self.stream.timebase)
+ if output is not None:
+ output.write('[%s]: end-of-track\n' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
+ else:
+ midi_file_logger.debug('[%s]: end-of-track' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
+
+
+#---------------------------------------------------------------
+# MIDIFile
+#---------------------------------------------------------------
+class MIDIFile (file):
+ """The MIDIFile class implements methods for reading, parsing,
+ modifying, and writing Standard MIDI Files (SMF).
+
+ """
+ def __init__ (self, name, mode):
+ file.__init__(self, name, mode)
+ self.timebase = TimeBase()
+
+ def ReadFromStream (self, start_offset=0, file_size=None):
+ """Parse the MIDI file creating a list of properties, tracks,
+ and events based on the contents of the file.
+
+ """
+
+ # determine file size - without using os.stat
+ if file_size == None:
+ self.start_offset = start_offset
+ self.seek(0,2)
+ file_size = self.tell() - self.start_offset
+ self.seek(start_offset,0)
+ else:
+ file_size = file_size
+
+ # for error recovery
+ self.last_good_event = None
+ self.error_loc = None
+
+ # read the file header - verify it's an SMF file
+ bytes = self.read(struct.calcsize(SMF_HEADER_FMT))
+ riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn = struct.unpack(SMF_HEADER_FMT, bytes)
+ midi_file_logger.debug('SMF header\n Tag: %s\n HeaderLen: %d\n Format: %d\n NumTracks: %d\n PPQN: %d\n' % \
+ (riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn))
+
+ # sanity check on header
+ if (riff_tag != SMF_RIFF_TAG) or (self.format not in range(2)):
+ raise MIDIFileException(self, MSG_NOT_SMF_FILE)
+
+ # check for odd header size
+ if self.hdr_len + 8 != struct.calcsize(SMF_HEADER_FMT):
+ self.Warning('SMF file has unusual header size: %d bytes' % self.hdr_len)
+
+ # read each of the tracks
+ offset = start_offset + self.hdr_len + 8
+ self.tracks = []
+ self.end_of_file = 0
+ for i in range(self.num_tracks):
+ #print("Track: %d" % i)
+
+ # parse the track
+ track = MIDITrack()
+ length = track.ReadFromStream(self, offset, file_size)
+ track.trackNum = i
+
+ self.tracks.append(track)
+
+ # calculate offset to next track
+ offset += length + 8
+
+ # determine time of last event
+ self.end_of_file = max(self.end_of_file, track.end_of_track)
+
+ # if start_offset is zero, the final offset should match the file length
+ if (offset - start_offset) != file_size:
+ self.Warning('SMF file size is incorrect - should be %d, was %d' % (file_size, offset))
+
+ def Save (self, offset=0, filters=None):
+ """Save this file back to disk with modifications."""
+ if (not 'w' in self.mode) and (not '+' in self.mode):
+ raise MIDIFileException(self, 'Cannot write to file in read-only mode')
+ self.Write(self, offset, filters)
+
+ def SaveAs (self, filename, offset=0, filters=None):
+ """Save MIDI data to new file."""
+ output_file = MIDIFile(filename, 'wb')
+ self.Write(output_file, offset, filters)
+ output_file.close()
+
+ def Write (self, output_file, offset=0, filters=None):
+ """This function does the actual work of writing the file."""
+ # write the file header
+ output_file.seek(offset)
+ bytes = struct.pack(SMF_HEADER_FMT, SMF_RIFF_TAG, struct.calcsize(SMF_HEADER_FMT) - 8, self.format, self.num_tracks, self.timebase.ppqn)
+ output_file.write(bytes)
+
+ # write out the tracks
+ for track in self.tracks:
+ track.Write(output_file, filters)
+
+ # flush the data to disk
+ output_file.flush()
+
+ def ConvertToType0 (self):
+ """Convert a file to type 0."""
+ if self.format == 0:
+ midi_file_logger.warning('File is already type 0 - ignoring request to convert')
+ return
+
+ # convert to type 0
+ for track in self.tracks[1:]:
+ self.tracks[0].MergeEvents(track.events)
+ self.tracks = self.tracks[:1]
+ self.num_tracks = 1
+ self.format = 0
+
+ def DeleteEmptyTracks (self):
+ """Delete any tracks that do not contain MIDI messages"""
+ track_num = 0
+ for track in self.tracks[:]:
+ for event in self.tracks.events:
+ if event.msg_type in MIDI_MESSAGES:
+ break;
+ else:
+ midi_file_logger.debug('Deleting track %d' % track_num)
+ self.tracks.remove(track)
+ track_num += 1
+
+ def ConvertToTicks (self, measures, beats, ticks):
+ return self.timebase.ConvertToTicks(measures, beats, ticks)
+
+ def Trim (self, start, end, quantize=0, chase_controllers=True):
+ track_num = 0
+ for track in self.tracks:
+ midi_file_logger.debug('Trimming track %d' % track_num)
+ track.Trim(start, end, quantize=quantize, chase_controllers=chase_controllers)
+ track_num += 1
+
+ def DumpTracks (self, output=None):
+ track_num = 0
+ for track in self.tracks:
+ if output is None:
+ midi_file_logger.debug('*** Track %d ***' % track_num)
+ else:
+ output.write('*** Track %d ***' % track_num)
+ track.DumpEvents(output)
+ track_num += 1
+
+ def Warning (self, msg):
+ midi_file_logger.warning('[%d]: %s' % (self.tell(), msg))
+
+ def Error (self, msg):
+ midi_file_logger.error('[%d]: %s' % (self.tell(), msg))
+
+ def DumpError (self):
+ if self.last_good_event:
+ midi_file_logger.error('Dumping from last good event:')
+ pos = self.last_good_event - 16
+ length = self.error_loc - pos + 16
+ elif self.error_loc:
+ midi_file_logger.error('Dumping from 16 bytes prior to error:')
+ pos = self.error_loc
+ length = 32
+ else:
+ midi_file_logger.error('No dump information available')
+ return
+
+ self.seek(pos, 0)
+ for i in range(length):
+ if i % 16 == 0:
+ if i:
+ midi_file_logger.error(' '.join(debug_out))
+ debug_out = ['%08x:' % (pos + i)]
+ byte = self.read(1)
+ if len(byte) == 0:
+ break;
+ debug_out.append('%02x' % ord(byte))
+ if i % 16 > 0:
+ midi_file_logger.error(' '.join(debug_out))
+
+def GetMidiInfo(midiFile):
+ """Bth; Get MIDI info"""
+
+ class midiData(object):
+ def __init__ (self):
+ self.err = 1
+ self.endMbt = "0:0:0"
+ self.totalTicks = 0
+ self.maxTracks = 0
+ self.maxMeasures = 0
+ self.maxBeats = 0
+ self.maxTicks = 0
+ self.totalTicks = 0
+ self.timebase = None
+ self.ppqn = 0
+ self.beats_per_measure = 0
+ self.trackList = []
+
+ md = midiData()
+
+ try:
+ m = MIDIFile(midiFile, 'rb')
+ m.ReadFromStream()
+
+ for track in m.tracks:
+ if track.channel is not None:
+ empty = False
+ trk = track.channel + 1
+ else:
+ empty = True
+ trk = ''
+ md.trackList.append(trackGrid(track.trackNum, trk, track.name, empty))
+
+ md.endMbt = m.timebase.ConvertTicksToMBT(m.end_of_file)
+ md.endMbtStr = "%d:%d:%d" % (md.endMbt[0], md.endMbt[1], md.endMbt[2])
+ md.maxMeasures = md.endMbt[0]
+ md.maxBeats = 4
+ md.maxTicks = m.timebase.ppqn
+ md.maxTracks = m.num_tracks
+ md.totalTicks = m.end_of_file
+ md.timebase = m.timebase
+ md.ppqn = m.timebase.ppqn
+ md.beats_per_measure = m.timebase.beats_per_measure
+
+ #add above if more added
+ md.err = 0
+
+ m.close()
+ except:
+ raise
+ pass
+
+ return md
+
+
+
+
+#---------------------------------------------------------------
+# main
+#---------------------------------------------------------------
+if __name__ == '__main__':
+ sys = __import__('sys')
+ os = __import__('os')
+
+ # initialize root logger
+ root_logger = logging.getLogger('')
+ root_logger.setLevel(logging.NOTSET)
+
+ # initialize console handler
+ console_handler = logging.StreamHandler()
+ console_handler.setFormatter(logging.Formatter('%(message)s'))
+ console_handler.setLevel(logging.DEBUG)
+ root_logger.addHandler(console_handler)
+
+ files = []
+ dirs = []
+ last_arg = None
+ sysex_filter = False
+ drum_filter = False
+ convert = False
+
+ # process args
+ for arg in sys.argv[1:]:
+
+ # previous argument implies this argument
+ if last_arg is not None:
+ if last_arg == '-DIR':
+ dirs.append(arg)
+ last_arg = None
+
+ # check for switch
+ elif arg[0] == '-':
+ if arg == '-DIR':
+ last_arg = arg
+ elif arg == '-SYSEX':
+ sysex_filter = True
+ elif arg == '-DRUMS':
+ drum_filter = True
+ elif arg == '-CONVERT':
+ convert = True
+ else:
+ midi_file_logger.error('Bad option %s' % arg)
+
+ # must be a filename
+ else:
+ files.append(arg)
+
+ # setup filters
+ filters = []
+ if sysex_filter:
+ filters.append(EventTypeFilter((SYSEX,)))
+ if drum_filter:
+ filters.append(ChannelFilter((9,),False))
+
+
+ # process dirs
+ for d in dirs:
+ for root, dir_list, file_list in os.walk(d):
+ for f in file_list:
+ if f.endswith('.mid'):
+ files.append(os.path.join(root, f))
+
+ # process files
+ bad_files = []
+ for f in files:
+ midi_file_logger.info('Processing file %s' % f)
+ midiFile = MIDIFile(f, 'rb')
+ try:
+ midiFile.ReadFromStream()
+
+ #midiFile.DumpTracks()
+ #print('[%s]: end-of-track\n' % midiFile.timebase.ConvertTicksToStr(midiFile.end_of_file))
+
+ # convert to type 0
+ if convert and (midiFile.format == 1):
+ midiFile.Convert(0)
+ converted = True
+ else:
+ converted = False
+
+ # write processed file
+ if converted or len(filters):
+ midiFile.SaveAs(f[:-4] + '-mod.mid', filters)
+
+ except MIDIFileException, X:
+ bad_files.append(f)
+ midi_file_logger.error('Error in file %s' % f)
+ midi_file_logger.error(X)
+ midiFile.DumpError()
+ midiFile.close()
+
+ # dump problem files
+ if len(bad_files):
+ midi_file_logger.info('The following file(s) had errors:')
+ for f in bad_files:
+ midi_file_logger.info(f)
+ else:
+ midi_file_logger.info('All files read successfully')
+