1
# GNU Solfege - free ear training software
2
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2007, 2008 Tom Cato Amundsen
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program. If not, see <http://www.gnu.org/licenses/>.
28
stemlen = linespacing * 3.5
35
accidental_widths = { -2: 12, -1: 7, 0: 5, 1: 8, 2: 8}
37
'G': -38 + 3 * linespacing,
38
'F': -8 + 3 * linespacing,
39
'C': -15 + 3 * linespacing,
42
dimentions = {20: dim20}
44
accidental_y_offset = {const.ACCIDENTAL__2: -7,
45
const.ACCIDENTAL__1: -7,
46
const.ACCIDENTAL_0: -5,
47
const.ACCIDENTAL_1: -6,
48
const.ACCIDENTAL_2: -2}
51
def __init__(self, timepos, fontsize):
53
self.m_timepos = timepos
56
Get the width of all engravers that does not implement their
57
own get_width function.
64
class ClefEngraver(Engraver):
65
def __init__(self, timepos, fontsize, clef):
66
Engraver.__init__(self, timepos, fontsize)
68
def set_xpos(self, dict):
69
self.m_xpos = dict[self.m_timepos].m_clef
72
def engrave(self, widget, gc, staff_yoffset):
73
widget.window.draw_pixbuf(gc,
74
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta%i-clefs-%s.xpm' \
75
% (self.m_fontsize, self.m_clef.get_symbol())),
77
dimentions[self.m_fontsize].clef_yoffset[self.m_clef.get_symbol()] - self.m_clef.get_stafflinepos() * dimentions[self.m_fontsize].linespacing\
80
class TimeSignatureEngraver(Engraver):
81
def __init__(self, timepos, fontsize, timesig):
82
Engraver.__init__(self, timepos, fontsize)
83
self.m_timesig = timesig
84
def set_xpos(self, dict):
85
self.m_xpos = dict[self.m_timepos].m_timesignature
86
def engrave(self, widget, gc, staff_yoffset):
88
for idx in range(len(str(self.m_timesig.m_num))):
89
n = str(self.m_timesig.m_num)[idx]
90
widget.window.draw_pixbuf(gc,
91
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta%i-number-%s.xpm' \
92
% (self.m_fontsize, n)),
94
self.m_xpos+x, staff_yoffset-12)
97
for idx in range(len(str(self.m_timesig.m_den))):
98
n = str(self.m_timesig.m_den)[idx]
99
widget.window.draw_pixbuf(gc,
100
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta%i-number-%s.xpm' %\
101
(self.m_fontsize, n)),
103
self.m_xpos+x, staff_yoffset+3)
106
return '(TimeSignatureEngraver:%i/%i, xpos:%s)' % (self.m_timesig.m_den,
107
self.m_timesig.m_num, self.m_xpos)
110
class TieEngraver(Engraver):
111
def __init__(self, fontsize, pos1, pos2, shift1, shift2, ylinepos):
112
Engraver.__init__(self, None, fontsize)
115
self.m_shift1 = shift1
116
self.m_shift2 = shift2
117
self.m_ylinepos = ylinepos
118
def set_xpos(self, dict):
119
dim = dimentions[self.m_fontsize]
120
self.m_xpos = dict[self.m_pos1].m_music
121
self.m_xpos2 = dict[self.m_pos2].m_music
122
self.m_xpos += 3 + (self.m_shift1 + 1) * dim.xshift
123
self.m_xpos2 += - 3 + self.m_shift2 * dim.xshift
124
def engrave(self, widget, gc, staff_yoffset):
125
dim = dimentions[self.m_fontsize]
126
w = self.m_xpos2 - self.m_xpos
128
widget.window.draw_arc(gc, False,
129
self.m_xpos, staff_yoffset + dim.linespacing*(self.m_ylinepos-1)/2 + 3,
130
w, h, 64 * 180, 64*180)
133
class AccidentalsEngraver(Engraver):
134
def __init__(self, timepos, fontsize, accs):
135
Engraver.__init__(self, timepos, fontsize)
137
def f(v, w=dimentions[fontsize].accidental_widths):
142
self.m_usize = reduce(operator.__add__, map(f, accs.values())) + len(accs)
143
def set_xpos(self, dict, tpd):
144
self.m_xpos = dict[self.m_timepos].m_accidentals + tpd[self.m_timepos].m_accidentals - self.get_width()
147
def engrave(self, widget, gc, staff_yoffset):
149
for y in self.m_accs:
150
for acc in self.m_accs[y]:
151
widget.window.draw_pixbuf(gc,
152
gtk.gdk.pixbuf_new_from_file(
153
fetadir+'/feta%i-accidentals-%i.xpm' % \
154
(self.m_fontsize, acc)),
157
int(accidental_y_offset[acc] + staff_yoffset
158
+ dimentions[self.m_fontsize].linespacing*y/2
159
+ accidental_y_offset[acc]))
160
x += dimentions[self.m_fontsize].accidental_widths[acc] + 1
163
class KeySignatureEngraver(Engraver):
164
def __init__(self, timepos, fontsize, old_key, key, clef):
165
Engraver.__init__(self, timepos, fontsize)
166
self.m_old_key = old_key
169
self.m_old_accidentals = mpdutils.key_to_accidentals(old_key)
170
self.m_accidentals = mpdutils.key_to_accidentals(key)
171
def set_xpos(self, dict):
172
self.m_xpos = dict[self.m_timepos].m_keysignature
174
#FIXME this value depends on the fontsize, and on what kind of
175
#accidental we are drawing. A sharp is wider that a flat.
176
return len(self.m_accidentals) * 10 + len(self.m_old_accidentals) * 6 + 8
177
def engrave(self, widget, gc, staff_yoffset):
181
for acc in self.m_old_accidentals:
182
if acc in self.m_accidentals:
184
ypos = self.m_clef.an_to_ylinepos(acc)
185
dolist.append((0, x, ypos))
186
#FIXME see FIXME msg in .get_width
189
for acc in self.m_accidentals:
190
if acc.endswith('eses'):
192
elif acc.endswith('es'):
194
elif acc.endswith('isis'):
198
ypos = self.m_clef.an_to_ylinepos(acc)
199
dolist.append((ktype, x, ypos))
200
#FIXME see FIXME msg in .get_width
202
for ktype, x, ypos in dolist:
203
widget.window.draw_pixbuf(gc,
204
gtk.gdk.pixbuf_new_from_file(
205
fetadir+'/feta%i-accidentals-%i.xpm' \
206
% (self.m_fontsize, ktype)),
209
int(accidental_y_offset[ktype] + staff_yoffset
210
+ dimentions[self.m_fontsize].linespacing*ypos/2
211
+ accidental_y_offset[ktype]))
214
class NoteheadEngraver(Engraver):
215
def __init__(self, timepos, fontsize, shift, ypos, head, numdots, midi_int, mgroup):
217
m_ypos == 0 is the middle line on the staff.
218
Negative value is up, positive is down.
219
The value counts notesteps, not pixels!
220
FIXME, right now it also draws dots, but they should be an own class
221
because the dots has to be handled special with noteheads are xshifted.
223
Engraver.__init__(self, timepos, fontsize)
226
self.m_numdots = numdots
228
self.m_midi_int = midi_int
229
self.m_mgroup = mgroup
230
def set_xpos(self, dict):
231
self.m_xpos = dict[self.m_timepos].m_music
232
def engrave(self, widget, gc, staff_yoffset):
233
dim = dimentions[self.m_fontsize]
234
# Have to adjust holenotes a little to the left to be on the middle
235
# of the ledger line.
240
widget.window.draw_pixbuf(gc,
241
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta%i-noteheads-%i.xpm' \
242
% (self.m_fontsize, self.m_head)),
244
self.m_xpos + self.m_shift * dim.xshift + xx,
245
int(staff_yoffset + dim.linespacing*self.m_ypos/2 - 4))
246
for n in range(self.m_numdots):
247
widget.window.draw_pixbuf(gc,
248
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta20-dots-dot.xpm'),
250
int(self.m_xpos+dim.xshift*(self.m_shift+1.5+n/2.0)),
251
-3 + staff_yoffset + dim.linespacing*self.m_ypos/2)
253
return "(NoteheadEngraver: xpos:%i, ypos:%i, head:%i)" % (self.m_xpos,
257
class BarlineEngraver(Engraver):
258
def __init__(self, timepos, fontsize, barline_type):
259
Engraver.__init__(self, timepos, fontsize)
260
self.m_type = barline_type
261
def set_xpos(self, dict):
262
self.m_xpos = dict[self.m_timepos].m_barline
265
def engrave(self, widget, gc, staff_yoffset):
266
dim = dimentions[self.m_fontsize]
267
widget.window.draw_line(gc, self.m_xpos, staff_yoffset - dim.linespacing*2,
268
self.m_xpos, staff_yoffset + dim.linespacing*2)
270
class TupletEngraver(Engraver):
271
def __init__(self, fontsize, n):
272
Engraver.__init__(self, None, fontsize)
274
self.m_den, self.m_direction = n
275
def set_xpos(self, dict):
277
def add_stem(self, stem):
278
self.m_stems.append(stem)
280
dim = dimentions[self.m_fontsize]
283
for s in self.m_stems:
284
if s.m_mgroup.m_stemdir == const.UP:
285
top.append(min(s.m_yposes)-7)
286
bottom.append(max(s.m_yposes)+1)
288
top.append(min(s.m_yposes))
289
bottom.append(max(s.m_yposes)+5)
290
self.m_t = min(top) - 2
291
self.m_b = max(bottom) + 2
292
self.m_xpos1 = self.m_stems[0].m_xpos
293
self.m_xpos2 = self.m_stems[-1].m_xpos + dim.xshift
294
def engrave(self, widget, gc, staff_yoffset):
295
dim = dimentions[self.m_fontsize]
296
m = self.m_xpos1 + (self.m_xpos2 - self.m_xpos1)/2
299
if self.m_direction == const.UP or self.m_direction == const.BOTH:
300
y = min(staff_yoffset - dim.linespacing * 3,
301
staff_yoffset + self.m_t * dim.linespacing / 2)
303
else: # == const.DOWN
304
y = max(staff_yoffset + dim.linespacing * 5,
305
staff_yoffset + self.m_b * dim.linespacing /2)
307
widget.window.draw_line(gc, self.m_xpos1, y, x1, y)
308
widget.window.draw_line(gc, x2, y, self.m_xpos2, y)
309
widget.window.draw_line(gc, self.m_xpos1, y, self.m_xpos1, y+5*d)
310
widget.window.draw_line(gc, self.m_xpos2, y, self.m_xpos2, y+5*d)
311
cc = widget.create_pango_context()
312
ll = pango.Layout(cc)
313
ll.set_text(str(self.m_den))
314
ll.set_font_description(pango.FontDescription("Sans 10"))
315
sx, sy = [i / pango.SCALE for i in ll.get_size()]
316
widget.window.draw_layout(gc, m - sx / 2, y - sy / 2, ll)
318
class BeamEngraver(Engraver):
319
def __init__(self, fontsize):
320
Engraver.__init__(self, None, fontsize)
322
def add_stem(self, stem_engraver):
323
self.m_stems.append(stem_engraver)
324
def set_xpos(self, dict):
327
self.decide_beam_stemdir()
328
self.set_stemlens(self.find_lowhigh_ypos())
329
def set_stemlens(self, lh):
331
for e in self.m_stems:
332
if self.m_stemdir == const.UP:
333
e.m_beamed_stem_top = l - 6
335
assert self.m_stemdir == const.DOWN
336
e.m_beamed_stem_top = h + 6
337
def find_lowhigh_ypos(self):
339
Find the lowest and highest notehead in the beam.
343
for se in self.m_stems:
344
for ylinepos in se.m_yposes:
350
def decide_beam_stemdir(self):
352
Decide the direction for the stems in this beam, and set
353
the stemdir for all stems.
355
v = {const.UP: 0, const.DOWN: 0}
356
for e in self.m_stems:
357
v[e.m_mgroup.m_stemdir] = v[e.m_mgroup.m_stemdir] + 1
359
if v[const.UP] > v[const.DOWN]:
363
for e in self.m_stems:
364
e.m_mgroup.m_stemdir = stemdir
365
self.m_stemdir = stemdir
366
for e in self.m_stems:
367
e.m_mgroup.m_stemdir = stemdir
368
def engrave(self, widget, gc, staff_yoffset):
369
dim = dimentions[self.m_fontsize]
371
if self.m_stemdir == const.UP:
375
x1 = self.m_stems[0].m_xpos
376
x2 = self.m_stems[-1].m_xpos
377
if self.m_stems[0].m_beamed_stem_top % 2 == 1:
378
for x in range(len(self.m_stems)):
379
self.m_stems[x].m_beamed_stem_top =\
380
self.m_stems[x].m_beamed_stem_top - d
381
y1 = self.m_stems[0].m_beamed_stem_top * dim.linespacing/2 + staff_yoffset
384
for y in range(beamw):
385
widget.window.draw_line(gc, x1, y1 + y*d, x2, y1 + y*d)
386
for stem in self.m_stems:
388
def short_beam(stem, xdir, beamnum, d=d, widget=widget, beamw=beamw, gc=gc, beaml=beaml, y1=y1):
389
for y in range(beamw):
390
widget.window.draw_line(gc, stem.m_xpos, y1 + y + beamnum*d*beamw*2,
391
stem.m_xpos + xdir*beaml, y1 + y + beamnum*d*beamw*2)
392
for nl, yc in ((16, 0), (32, 1), (64, 2)):
393
for i in range(len(self.m_stems)-1):
394
if self.m_stems[i].m_mgroup.m_duration.m_nh >= nl \
395
and self.m_stems[i+1].m_mgroup.m_duration.m_nh >= nl:
396
for y in range(beamw):
397
widget.window.draw_line(gc,
398
self.m_stems[i].m_xpos, d*beamw*yc*2 + y1 + y + d*beamw*2,
399
self.m_stems[i+1].m_xpos, d*beamw*yc*2 + y1 + y + d*beamw*2)
400
self.m_stems[i]._beam_done = nl
401
self.m_stems[i+1]._beam_done = nl
402
if self.m_stems[i].m_mgroup.m_duration.m_nh >= nl \
403
and self.m_stems[i+1].m_mgroup.m_duration.m_nh <= nl/2 \
404
and self.m_stems[i]._beam_done < nl:
408
if self.m_stems[i-1].m_mgroup.m_duration.m_nh < \
409
self.m_stems[i+1].m_mgroup.m_duration.m_nh:
413
short_beam(self.m_stems[i], stemdir, yc+1, d)
414
self.m_stems[i]._beam_done = nl
415
if self.m_stems[-1].m_mgroup.m_duration.m_nh >= nl \
416
and self.m_stems[-1]._beam_done <= nl/2:
417
short_beam(self.m_stems[-1], -1, yc+1, d)
420
class StemEngraver(Engraver):
422
Every notehead belong to a stem, even if the stem is invisible.
424
def __init__(self, timepos, fontsize, yposes, mgroup, is_beamed):
425
Engraver.__init__(self, timepos, fontsize)
426
self.m_mgroup = mgroup
427
self.m_yposes = yposes
428
self.m_is_beamed = is_beamed
429
if len(self.m_yposes) == 1:
432
x = self.m_yposes[0] + self.m_yposes[-1]
433
if self.m_mgroup.m_stemdir == const.UP \
434
or (self.m_mgroup.m_stemdir == const.BOTH and x >= 0):
435
self.m_mgroup.m_stemdir = const.UP
437
self.m_mgroup.m_stemdir = const.DOWN
438
self.m_stemlen = dimentions[self.m_fontsize].stemlen
439
def set_xpos(self, dict):
440
self.m_xpos = dict[self.m_timepos].m_music
442
if self.m_mgroup.m_stempos == 1 or self.m_mgroup.m_stemdir == const.UP:
443
self.m_xpos = self.m_xpos + dimentions[self.m_fontsize].xshift
444
def engrave(self, widget, gc, staff_yoffset):
445
dim = dimentions[self.m_fontsize]
447
if not self.m_is_beamed and self.m_mgroup.m_duration.m_nh > 4:
448
if self.m_mgroup.m_stemdir == const.UP:
449
widget.window.draw_pixbuf(gc,
450
gtk.gdk.pixbuf_new_from_file(
451
fetadir+'/feta%i-flags-%s.xpm' % \
452
(self.m_fontsize, self.get_flag(const.UP))),
455
int(staff_yoffset - self.m_stemlen + self.m_yposes[0] * dim.linespacing/2))
457
widget.window.draw_pixbuf(gc,
458
gtk.gdk.pixbuf_new_from_file(
459
fetadir+'/feta%i-flags-%s.xpm' \
460
% (self.m_fontsize, self.get_flag(const.DOWN))),
463
int(staff_yoffset + 4 + self.m_yposes[-1] * dim.linespacing/2))
465
if self.m_mgroup.m_stemdir == const.DOWN:
467
yroot = self.m_beamed_stem_top
469
yroot = self.m_yposes[-1] + 6
470
ytop = self.m_yposes[0]
473
yroot = self.m_beamed_stem_top
475
yroot = self.m_yposes[0] - 6
476
ytop = self.m_yposes[-1]
477
widget.window.draw_line(gc,
479
ytop * dim.linespacing/2 + staff_yoffset,
481
yroot * dim.linespacing/2 + staff_yoffset)
482
def get_flag(self, stemdir):
483
assert self.m_mgroup.m_duration.m_nh > 4
484
return {8:{const.UP: const.FLAG_8UP,
485
const.DOWN: const.FLAG_8DOWN},
486
16: {const.UP: const.FLAG_16UP,
487
const.DOWN: const.FLAG_16DOWN},
488
32: {const.UP: const.FLAG_32UP,
489
const.DOWN: const.FLAG_32DOWN},
490
64: {const.UP: const.FLAG_64UP,
491
const.DOWN: const.FLAG_64DOWN}} \
492
[self.m_mgroup.m_duration.m_nh][stemdir]
494
return "(StemEngraver)"
497
class LedgerLineEngraver(Engraver):
498
def __init__(self, timepos, fontsize, up, down):
500
up: number of ledger lines above staff
501
down: number of ledger lines below staff
503
Engraver.__init__(self, timepos, fontsize)
506
def set_xpos(self, dict):
507
self.m_xpos = dict[self.m_timepos].m_music
508
def engrave(self, widget, gc, staff_yoffset):
509
dim = dimentions[self.m_fontsize]
511
for y in range(self.m_up):
512
widget.window.draw_line(gc,
513
self.m_xpos+dim.ledger_left, (-y-3) * dim.linespacing + staff_yoffset,
514
self.m_xpos+dim.ledger_right, (-y-3) * dim.linespacing + staff_yoffset)
516
for y in range(self.m_down):
517
widget.window.draw_line(gc,
518
self.m_xpos+dim.ledger_left, (y+3) * dim.linespacing + staff_yoffset,
519
self.m_xpos+dim.ledger_right, (y+3) * dim.linespacing + staff_yoffset)
521
return "(LedgerLineEngraver xpos:%i, updown%i%i" % (
522
self.m_xpos, self.m_up, self.m_down)
525
class RestEngraver(Engraver):
526
def __init__(self, timepos, fontsize, ypos, dur):
527
Engraver.__init__(self, timepos, fontsize)
529
self.m_dots = dur.m_dots
530
self.m_type = {1: 0, 2: 1, 4: 2, 8: 3, 16: 4, 32: 5, 64: 6}[dur.m_nh]
531
def set_xpos(self, dict):
532
self.m_xpos = dict[self.m_timepos].m_music
536
def engrave(self, widget, gc, staff_yoffset):
537
dim = dimentions[self.m_fontsize]
539
my = dim.linespacing/2
542
widget.window.draw_pixbuf(gc,
543
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta%i-rests-%i.xpm' \
544
% (self.m_fontsize, self.m_type)),
547
int(staff_yoffset - my + dim.linespacing*self.m_ypos/2 - 4))
548
for n in range(self.m_dots):
549
widget.window.draw_pixbuf(gc,
550
gtk.gdk.pixbuf_new_from_file(fetadir+'/feta20-dots-dot.xpm'),
552
int(self.m_xpos+dim.xshift*(1.5+n/2.0)),
553
-3 + staff_yoffset + dim.linespacing*self.m_ypos/2)
555
return "(RestEngraver)"