1import qutesheet as q 2from Tkinter import * 3import tkFileDialog 4from math import modf, log10, fmod 5import sys, subprocess, shutil, os 6 7image_avail = True 8try: 9 import Image, ImageTk 10except ImportError: 11 image_avail = False 12 13# get rows already sorted 14rows_sorted = q.selection_full_rows_sorted() 15 16if sys.platform == 'win32': 17 lilypond_exec = 'lilypond.exe' 18elif sys.platform == 'darwin': 19 lilypond_exec = '/Applications/LilyPond.app/Contents/Resources/bin/lilypond' 20else: 21 lilypond_exec = '/usr/bin/lilypond' 22 23# --- 24q_options = ["1/4", "1/8", "1/16", "1/32"] 25time_options = ["4/4", "3/4", "6/8", "5/4"] 26num_beats = [4, 3, 6, 5] # number of beats for the available time_options 27note_names_sharp = [ 'c' , 'cis', 'd', 'dis', 'e', 'f', 'fis', 'g', 'gis', 'a', 'ais', 'b' ] 28note_names_flat = [ 'c' , 'des', 'd', 'ees', 'e', 'f', 'ges', 'g', 'aes', 'a', 'bes', 'b' ] 29note_names = [] 30acc_options = ['sharps', 'flats'] 31p4_options = ['MIDI note', 'cps', 'pitch class'] 32 33filename = "temp-lilyfile.ly" 34root = 0 # root is tk root window 35 36template_text = ''' 37\\version "2.12.2" 38\\header{ 39 title = <Title comes here> 40} 41\\score { 42<< 43<Notes Come Here> 44 45>> 46 } 47''' 48 49def quantize(rows): 50 return rows 51 52def split_polyphony(rows): 53 polyphony = [[]] 54 cur_edges = [0] 55 cur_poly = 0 56 for r in rows: 57 if len(r) > 3 and type(r[2]) !=str and r[2] < cur_edges[cur_poly]: 58 for i, e in enumerate(cur_edges): 59 # Try to reuse a previous polyphony line if possible 60 if e > r[2]: 61 cur_poly = i 62 break 63 if r[2] < cur_edges[cur_poly]: 64 new_voice = [] 65 cur_edges.append(0) 66 polyphony.append(new_voice) 67 cur_poly = len(polyphony) - 1 68 if len(r) > 3 and type(r[2]) !=str and r[2] + r[3] > cur_edges[cur_poly]: 69 cur_edges[cur_poly] = r[2] + r[3] 70 polyphony[cur_poly].append(r) 71 return polyphony 72 73 74def produce_score(): 75 global rows_sorted, lilypond_exec, num_beats, template_text, note_names, filename 76 quant = q_var.get() 77 time = time_var.get() 78 beats = num_beats[time_options.index(time_var.get())] 79 title = title_var.get() 80 p4 = p4_var.get() 81 if acc_var.get() == 'sharp': 82 note_names = note_names_sharp 83 else: 84 note_names = note_names_flat 85 notes_by_instr = [] 86 instr_nums = [] 87 out_text = '' 88 for n in range(len(rows_sorted)): 89 if (rows_sorted[n][0] != 'i'): 90 print "Not i event. Skipping line ", n 91 continue 92 i_num = rows_sorted[n][1] 93 if type(i_num) != str: 94 if instr_nums.count(i_num) != 0: 95 index = instr_nums.index(i_num) 96 notes_by_instr[index].append(rows_sorted[n]) 97 else: 98 instr_rows = [rows_sorted[n]] 99 notes_by_instr.append(instr_rows) 100 instr_nums.append(i_num) 101 for n in range(len(notes_by_instr)): 102 staff_name = "instr " + str(instr_nums[n]) 103 out_text += make_ly_text(notes_by_instr[n], staff_name, quant, time, beats, p4) 104 out_text += '' 105 complete_text = template_text 106 complete_text = complete_text.replace('<Notes Come Here>', out_text) 107 complete_text = complete_text.replace('<Title comes here>', '"' + title + '"') 108 f = open(filename, 'w') 109 try: 110 f.write(complete_text) 111 finally: 112 f.close() 113 args = [lilypond_exec, '-fpng', filename] 114 p = subprocess.call(args) 115 print p 116 if p != 0: 117 r = Toplevel(root) 118 r.title("Error running Lilypond!") 119 info_text = "Executable:\n" + lilypond_exec + "\nError!\n" 120 info_text += "This script requires lilypond." 121 l = Label(r, text=info_text) 122 l.grid(row=0) 123 t = Toplevel(root) 124 t.title("Generated Score"); 125 126 save_ly_but = Button(t, text="Save Lilypond", fg="black", command=save_lilypond) 127 save_ly_but.grid(row=0) 128 save_png_but = Button(t, text="Save PNG", fg="black", command=save_png) 129 save_png_but.grid(row=0, column=1) 130 if image_avail: 131 image_name = filename.replace('.ly', '.png') 132 image1 = Image.open(image_name) 133 im = ImageTk.PhotoImage(image1) 134 label_image = Label(t, image=im) 135 label_image.grid(row=1, column=0, columnspan=2) 136 if old_label_image is not None: 137 old_label_image.destroy() 138 old_label_image = label_image 139 else: 140 l = Label(t, text="ImageTk module not available\nInstalling it will allow preview here.") 141 l.grid(row=1, column=0, columnspan=2) 142# print p 143 144 145def make_ly_text(rows, staff_name, quant, time, num_beats, p4): 146 polyphony = split_polyphony(rows) 147 ly_notes_text = '\n\\new Staff {<< \\set Staff.instrumentName = \"' + staff_name + ' \"\n \\time ' + time + '\n' 148 for voice in polyphony: 149 ly_notes_text += '\n{ ' 150 last_note_time = 0 151 clef_changed = False 152 for r in voice: 153 if type(r[3]) == str or type(r[2]) == str or type(r[4]) == str: 154 continue 155 if last_note_time < r[2]: 156 remaining = r[2] - last_note_time 157 while remaining > 0: 158 sil_dur = 1 159 if remaining <= num_beats: 160 if modf(1.0/remaining)[0] != 0: 161 sil_dur = str( int(8 * (1.0/remaining)) ) + "." 162 else: 163 sil_dur = str( int(4 * (1.0/remaining)) ) 164 remaining -= num_beats 165 if voice.index(r) == 0: 166 ly_notes_text += 'r' + str(sil_dur) + ' ' 167 else: 168 ly_notes_text += 's' + str(sil_dur) + ' ' 169 else: 170 ly_notes_text += 's1 ~ ' 171 remaining -= num_beats 172 p_class = 0 173 if p4 == 'pitch class': 174 p_class = r[4] + 0.00005 175 elif p4 == 'MIDI note': 176 p_class = r[4]//12 + 3 + ((r[4]%12 + 0.00005)/ 100.0) # Middle C is octave 8. Must add a small offset to avoid floating point rounding errors... 177 elif p4 == 'cps': 178 p_class = 69 + (12 *log10(r[4]/440.0)/log10(2)) 179 p_class = p_class//12 + 3 + ((p_class%12 + 0.00005)/ 100.0) 180# print p_class 181 note_num, octave = modf(p_class - 7.0) # Middle C is 1st octave here 182# print octave, note_num, p_class, modf(p_class - 7.0) 183 note_text = '' 184 if p_class < 7.05 and not clef_changed: 185 note_text += " \\clef F " 186 clef_changed = True 187 else: 188 if p_class > 8.07 and clef_changed: 189 note_text += " \\clef G " 190 clef_changed = False 191 note_text += note_names[int(note_num *100)] 192 for i in range(int(abs(octave))): 193 if octave > 0: 194 note_text += "'" 195 else: 196 note_text += "," 197 dur = r[3] 198 beat_start = fmod(r[2], num_beats) 199 overshoot = fmod(beat_start + dur, num_beats) 200 last_note_time = r[2] + dur 201 if overshoot > 0 and overshoot != dur: 202 ly_notes_text += note_text + str( int(4 * (1.0/ (num_beats - beat_start))) ) 203 while overshoot > 0: 204 new_part_dur = overshoot if overshoot < num_beats else num_beats 205 ly_notes_text += ' ~ ' + note_text + str( int(4 * (1.0/ (num_beats - new_part_dur))) ) 206 overshoot = overshoot - new_part_dur 207 else: 208 if modf(1.0/dur)[0] != 0: 209 dur_text = str( int(8 * (1.0/dur)) ) + "." 210 else: 211 dur_text = str( int(4 * (1.0/dur)) ) 212 note_text += dur_text 213 ly_notes_text += note_text + ' ' 214 ly_notes_text += '} \\\\' 215 ly_notes_text += '\n>>}' 216 return ly_notes_text 217 218def save_lilypond(): 219 global root, filename 220 options = {} 221# options['initialdir'] = 'C:\\' 222# options['mustexist'] = False 223 options['parent'] = root 224 options['title'] = 'Save lilypond file' 225 new_filename = tkFileDialog.asksaveasfilename(**options) 226 227 if new_filename: 228 print "save_lilypond() ", new_filename 229 shutil.copyfile(filename, new_filename) 230 231def save_png(): 232 global root, filename 233 options = {} 234# options['initialdir'] = 'C:\\' 235# options['mustexist'] = False 236 options['parent'] = root 237 options['title'] = 'Save png file' 238 new_filename = tkFileDialog.asksaveasfilename(**options) 239 240 if new_filename: 241 print "save_png() ", new_filename 242 shutil.copyfile(filename.replace('.ly', '.png'), new_filename) 243 244 245# GUI ------------- 246root = Tk() 247root.title("Produce lilypond Score") 248 249l = Label(root, text="Quantize to:") 250#l.grid(row=0) 251q_var = StringVar(root) 252q_var.set(q_options[1]) # initial value 253q = apply(OptionMenu, (root, q_var) + tuple(q_options)) 254#q.grid(row=0, column=1) 255 256l = Label(root, text="Measure Time:") 257l.grid(row=1) 258time_var = StringVar(root) 259time_var.set(time_options[0]) # initial value 260time = apply(OptionMenu, (root, time_var) + tuple(time_options)) 261time.grid(row=1, column=1) 262 263l = Label(root, text="Accidentals") 264l.grid(row=2) 265acc_var = StringVar(root) 266acc_var.set(acc_options[0]) # initial value 267acc = apply(OptionMenu, (root, acc_var) + tuple(acc_options)) 268acc.grid(row=2, column=1) 269 270l = Label(root, text="p4 is:") 271l.grid(row=3) 272p4_var = StringVar(root) 273p4_var.set(p4_options[0]) # initial value 274p4 = apply(OptionMenu, (root, p4_var) + tuple(p4_options)) 275p4.grid(row=3, column=1) 276 277l = Label(root, text="Title") 278l.grid(row=4) 279title_var = StringVar(root) 280titleentry = Entry(root, textvariable=title_var) 281titleentry.grid(row=4, column=1) 282title_var.set("QuteCsound Score") # initial value 283 284button = Button(root, text="Produce Score", fg="black", command=produce_score) 285button.grid(row=9, column=0, columnspan=2) 286 287root.mainloop() 288 289#remove temp files 290os.remove(filename) 291os.remove(filename.replace('.ly', '.png')) 292os.remove(filename.replace('.ly', '.ps')) 293