#!/usr/bin/env python # Simple groove browser for MMA from Tkinter import * import tkMessageBox import os import subprocess import sys import pickle import platform # Pointer the mma executable. Normally you'll have 'mma' in your # path, so leave it as the default. If not, set a complete path. MMA = 'mma' #MMA = "c:python25\python mma.py" # Some tk defaults for color/font listbx_Bcolor = "white" # listbox colors listbx_Fcolor = "medium blue" # Set the font and size to use. Examples: # "Times 12 Normal" # "Helvetica 12 Italic" # or just set it to "" to use the default system font #gbl_font = "Georgia 16 bold" #gbl_font = "Helvetica 12 italic" #gbl_font = "System 16 bold" gbl_font = '' # Find the groove libraries. This code is the same as that used # in mma.py to find the root MMA directory. platform = platform.system() if platform == 'Windows': dirlist = ( sys.path[0], "c:/mma", "c:/program files/mma", ".") midiPlayer = [''] # must be a list! sub_shell = True else: dirlist = ( sys.path[0], "/usr/local/share/mma", "/usr/share/mma", '.' ) midiPlayer = ["aplaymidi"] # Must be a list! sub_shell = False for d in dirlist: moddir = os.path.join(d, 'MMA') if os.path.isdir(moddir): if not d in sys.path: sys.path.insert(0, d) MMAdir = d break libPath = os.path.join(MMAdir, 'lib') if not os.path.isdir(libPath): print "The MMA library directory was not found." sys.exit(1) # these are the options passed to mma for playing a groove. # they are modified by the entryboxes at the top of screen opt_tempo = 100 opt_keysig = "C" opt_chords = "I vi ii V7" opt_count = 4 # The name of the database and a storage ptr. Don't change this. class db_Entry: def __init__(self, fd, g): self.fileDesc=fd # description of style self.grooveList=g # dictionary of groove names, descriptions dbName = "browserDB" db = [] ############################################################# # Utility stuff to manage database. def error(m=''): """ Universal error/termination. """ if m: print m sys.exit(1) def update_groove_db(root, dir, textbox=None): """ Update the list of grooves. Use mma to do work. The resulting database is a dict. with the keys being the filenames. Each entry has 2 fields: filedesc - the header for the file glist - a dict of subgrooves. The keys are the groovenames and the data is the groove desc. """ gdict = {} files = os.listdir(os.path.join(root, dir)) for f in files: path = os.path.join(root,dir,f) if os.path.isdir(path): gdict.update(update_groove_db(root, os.path.join(dir, f), textbox)) elif path.endswith('.mma'): fullFname = os.path.join(dir,f) if textbox: textbox.delete(0.0, END) textbox.insert(END,fullFname) textbox.update() else: print "Parsing", fullFname try: pp=subprocess.Popen([MMA, '-Dbo', path], stdout=subprocess.PIPE, shell=sub_shell) output = pp.communicate()[0] except: msg = "Error in reading mma file. Is MMA current?\n" \ "Executable set to '%s'. Is that right?" % MMA if textbox: tkMessageBox.showerror("Database Update", msg) else: print msg sys.exit(1) if pp.returncode: msg = "Error in MMA. Is MMA current?\n" + output if textbox: tkMessageBox.showerror("Database Update", output) else: print msg sys.exit(1) output = output.strip().split("\n") gg={} for i in range(1, len(output), 2): gg[output[i].strip()] = output[i+1].strip() e=db_Entry( output[0].strip(), gg ) gdict[fullFname] = e return gdict def write_db(root, dbName, db, textbox=None): """ Write the data base from memory to a file. """ path = os.path.join(root, dbName) msg = None try: outpath = open(path, 'wb') except: msg = "Error creating groove database file '%s'. " \ "Do you need to be root?" % path if msg: if textbox: tkMessageBox.showwarning("Database Write Error", msg) else: print msg return pickle.dump(db, outpath, pickle.HIGHEST_PROTOCOL ) outpath.close() def read_db(root, dbName): """ Read database. Return structure/list. """ path = os.path.join(root, dbName) try: inpath = open(path, 'rb') except: return None g = pickle.load(inpath) inpath.close() return g ################################################### # All the tk stuff goes here #################################################################### ## These functions create various frames. Maintains consistency ## between different windows (and makes cleaner code??). def makeLabelBox(parent, justify=CENTER, row=0, column=0, text=''): """ Create a label box. """ f = Frame(parent) b = Label(f,justify=justify, text=text) b.grid() f.grid(row=row, column=column, sticky=E+W) f.grid_rowconfigure(0, weight=1) return b def makeMsgBox(parent, justify=LEFT, row=0, column=0, text=''): """ Create a message box. """ b = Message(parent, border=5, relief=SUNKEN, aspect=1000, anchor=W, justify=justify, text=text) b.grid(sticky=E+W, column=column, row=row) return b def makeTextBox(parent, justify=LEFT, row=0, column=0, text=''): """ Create a text-message box. """ f=Frame(parent) ys=Scrollbar(f) b = Text(f, border=5, relief=SUNKEN, wrap=WORD, height=2, width=50) b.grid(column=1,row=0, sticky=N+E+W+S) ys.config(orient=VERTICAL, command=b.yview) ys.grid(column=0,row=0, sticky=N+S) f.grid(row=row, column=column, sticky=E+W+N+S) f.grid_rowconfigure(0, weight=0) f.grid_columnconfigure(1, weight=1) return b def makeButtonBar(parent, row=0, column=0, buttons=(())): """ Create a single line frame with buttons. """ bf=Frame(parent) c=0 for txt, cmd in buttons: Button(bf, text=txt, height=1, command=cmd).grid(column=c, row=0, pady=5) c+=1 bf.grid(row=row, column=column, sticky=W) return bf def makeListBox(parent, width=50, height=20, selectmode=BROWSE, row=0, column=0): """ Create a list box with x and y scrollbars. """ f=Frame(parent) ys=Scrollbar(f) xs=Scrollbar(f) lb=Listbox(f, bg=listbx_Bcolor, fg=listbx_Fcolor, width=width, height=height, yscrollcommand=ys.set, xscrollcommand=xs.set, exportselection=FALSE, selectmode=selectmode ) ys.config(orient=VERTICAL, command=lb.yview) ys.grid(column=0,row=0, sticky=N+S) xs.config(orient=HORIZONTAL, command=lb.xview) xs.grid(column=1, row=1, sticky=E+W) lb.grid(column=1,row=0, sticky=N+E+W+S) f.grid(row=row, column=column, sticky=E+W+N+S) f.grid_rowconfigure(0, weight=1) f.grid_columnconfigure(1, weight=1) return lb def makeEntry(parent, label="Label", text='', column=0, row=0): f=Frame(parent) l=Label(f, anchor=W, width=10, padx=10, pady=10, text=label).grid(column=0, row=0) e=Entry(f, text=text, width=10) e.grid(column=1, row=0, sticky=W) e.delete(0, END) e.insert(END, text) f.grid( column=column, row=row, sticky=W) return e def dohelp(): pass ############################ # Main display screen class Application: def __init__(self): """ Create frames: bf - the menu bar f1, f2 - the options bars lb - the list box with a scroll bar lbdesc - desc for file entries lgv - list box of grooves lgvdesc - desc for groove """ self.selectedFile = '' self.selectedGroove = '' bf = makeButtonBar(root, row=0, column=0, buttons=( ("Quit", self.quitall ), ("Re-read Grooves", self.updatedb), ("Help", dohelp) ) ) self.f1 = Frame(root) self.f2 = Frame(root) self.e_tempo = makeEntry(self.f1, label="Tempo", text=opt_tempo, row=0, column=0) self.e_keysig = makeEntry(self.f1, label="Key Signature", text=opt_keysig, row=0, column=1) self.f1.grid( column=0, row=1, sticky=W) self.e_chords = makeEntry(self.f2, label="Chords", text=opt_chords, row=0, column=0) self.e_count = makeEntry(self.f2, label="Count", text=opt_count, row=0, column=1) self.f2.grid( column=0, row=2, sticky=W) self.lbdesc = makeTextBox(root, row=3, column=0, text="Current file") self.lb=lb = makeListBox(root, height=10, row=4, column=0) self.lgvdesc = makeTextBox(root, row=5, column=0, text="Groovy") self.lgv=lgv = makeListBox(root, height=8, row=6, column=0) # bindings lb.bind("", self.selectFileClick) lgv.bind("", self.selectGrooveClick) lgv.bind("", self.playGroove) # Make the listbox frames expandable root.grid_rowconfigure(2, weight=1) root.grid_rowconfigure(4, weight=1) root.grid_columnconfigure(0, weight=1) lb.focus_force() # make the listbox use keyboard self.updateFileList() # Play the selected groove def playGroove(self,w): opt_tempo = self.e_tempo.get() opt_keysig = self.e_keysig.get() opt_chords = self.e_chords.get() opt_chords = opt_chords.replace(' ', ' ') opt_chords = opt_chords.replace(' ', ',') opt_count = self.e_count.get() try: p=subprocess.Popen([MMA, '-V', 'Tempo=%s' % opt_tempo, 'Keysig=%s' % opt_keysig, 'Chords=%s' % opt_chords, 'Count=%s' % opt_count, self.selectedGroove], stderr=subprocess.PIPE, shell=sub_shell) output = p.communicate()[0] except: tkMessageBox.showerror("MMA Error", "Error calling MMA to process the preview.\n" \ "Check your installation!\n" \ "Executable set to '%s'. Is that right?" % MMA) sys.exit(1) if p.returncode: if not output: msg = "Error ... can't find the MMA interpreter set to %s" % MMA else: msg = output tkMessageBox.showwarning("MMA Error", msg) if not output: sys.exit(1) # Update the selected groove info def selectGrooveRet(self, w): self.selectGroove(self.lgv.get(ACTIVE) ) def selectGrooveClick(self,w): self.lgv.activate(self.lgv.nearest(w.y)) self.selectGroove(self.lgv.get(self.lgv.nearest(w.y))) def selectGroove(self, f): self.selectedGroove=f self.lgvdesc.config(state=NORMAL) self.lgvdesc.delete(0.0, END) self.lgvdesc.insert(END,db[self.selectedFile].grooveList[self.selectedGroove]) self.lgvdesc.config(state=DISABLED) # Update the selected file into def selectFileClick(self, w): self.lb.activate(self.lb.nearest(w.y)) self.selectFile(self.lb.get(self.lb.nearest(w.y))) def selectFileRet(self, w): self.selectFile(self.lb.get(ACTIVE) ) def selectFile(self, f): self.selectedFile = f self.lbdesc.config(state=NORMAL) self.lbdesc.delete(0.0, END) self.lbdesc.insert(END,db[f].fileDesc) self.lbdesc.config(state=DISABLED) self.updateGrooveList() # Display all the files in the file window, Select entry 0 def updateFileList(self): f = sorted(db) self.selectedFile = f[0] self.lb.delete(0,END) for ff in f: self.lb.insert(END, ff) self.selectFile(f[0]) self.updateGrooveList() root.update() # Display all the grooves for the current file, select 0 def updateGrooveList(self): g = sorted(db[self.selectedFile].grooveList) self.selectedGroove=g[0] self.lgv.delete(0,END) for gg in g: self.lgv.insert(END, gg) self.selectGroove(self.selectedGroove) root.update() def updatedb(self): global db self.lb.delete(0,END) self.lbdesc.config(state=NORMAL) self.lbdesc.delete(0.0, END) self.lgv.delete(0,END) self.lgvdesc.config(state=NORMAL) self.lgvdesc.delete(0.0, END) db = update_groove_db(libPath, '', self.lbdesc ) if not db: print "No data read" sys.exit(1) write_db(libPath, dbName, db, self.lbdesc) self.updateFileList() def quitall(self): sys.exit() # Start the tk stuff. db = read_db(libPath, dbName) if not db: db = update_groove_db(libPath, '', None) if not db: print "No data in database" sys.exit(1) write_db(libPath, dbName, db, None) root = Tk() root.title("MMA Groove Browser") root.option_add("*Dialog.msg.wrapLength", "15i") if gbl_font: root.option_add('*font', gbl_font) app=Application() root.mainloop()