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