mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-23 03:34:00 +00:00
479 lines
14 KiB
Plaintext
479 lines
14 KiB
Plaintext
|
#!/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("<Button-1>", self.selectFileClick)
|
||
|
lgv.bind("<Button-1>", self.selectGrooveClick)
|
||
|
lgv.bind("<Double-Button-1>", 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()
|
||
|
|
||
|
|
||
|
|