From b0fc2bbf6d5f9b64c8bba3b7b5e4e50297c36e00 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Sat, 2 Sep 2000 22:46:50 +0200 Subject: [PATCH] release: 1.3.84 ====== * pmx2ly, PMX to LilyPond conversion. Succesfully converts barsant.pmx. * musedata2ly, a Musedata to LilyPond convertor. Succesfully converts wtk1-fugue2 * bugfixes for dynamics: make sure that dynamic alignments encompass the dynamics they support, and that any columns they attach to aren't prematurely killed. * bugfix: make sure \time gets noticed early enough. 1.3 --- CHANGES | 18 +- Documentation/regression-test.tely | 4 + NEWS | 12 +- VERSION | 2 +- buildscripts/musedata2ly.py | 210 ----- buildscripts/pmx2ly.py | 217 ----- input/bugs/script-collide.ly | 2 + ...{multistanza.ly => lyrics-multi-stanza.ly} | 0 lily/axis-group-engraver.cc | 4 +- lily/directional-element-interface.cc | 2 +- lily/dynamic-engraver.cc | 13 +- lily/include/spanner.hh | 1 + lily/line-group-group-engraver.cc | 3 +- lily/main.cc | 6 +- lily/paper-column.cc | 2 +- lily/score-engraver.cc | 15 +- lily/spanner.cc | 82 +- lily/timing-translator.cc | 36 +- make/out/lilypond.lsm | 8 +- make/out/lilypond.spec | 4 +- scripts/etf2ly.py | 89 +- scripts/musedata2ly.py | 616 ++++++++++++++ scripts/pmx2ly.py | 766 ++++++++++++++++++ 23 files changed, 1591 insertions(+), 521 deletions(-) delete mode 100644 buildscripts/musedata2ly.py delete mode 100644 buildscripts/pmx2ly.py create mode 100644 input/bugs/script-collide.ly rename input/test/{multistanza.ly => lyrics-multi-stanza.ly} (100%) create mode 100644 scripts/musedata2ly.py create mode 100644 scripts/pmx2ly.py diff --git a/CHANGES b/CHANGES index 1e2dd72443..ef5141e5be 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,18 @@ +1.3.84 +====== + +* pmx2ly, PMX to LilyPond conversion. Succesfully converts barsant.pmx. + +* musedata2ly, a Musedata to LilyPond convertor. Succesfully converts +wtk1-fugue2 + +* bugfixes for dynamics: make sure that dynamic alignments encompass +the dynamics they support, and that any columns they attach to aren't +prematurely killed. + +* bugfix: make sure \time gets noticed early enough. + 1.3.83 ====== @@ -33,7 +47,6 @@ * Smobified Translator and Translator_group, junked Translator_group_identifier. - * \pushproperty and \popproperty withing \translator, similar to predefining \property, ie. @@ -330,9 +343,6 @@ have self_scm_) * Glen Prideaux lyric phrasing engraver. See input/test/lyric-phrasing.ly - - - 1.3.73 ====== * Removed \interscoreline after the last line, prevents some diff --git a/Documentation/regression-test.tely b/Documentation/regression-test.tely index c157ffa346..127f991038 100644 --- a/Documentation/regression-test.tely +++ b/Documentation/regression-test.tely @@ -262,6 +262,10 @@ to work. @mudelafile{lyric-combine.ly} +Multiple stanzas + +@mudelafile{lyrics-multi-stanza.ly} + @section Multiple notes Rests should not collide with beams, stems and noteheads. Rests may diff --git a/NEWS b/NEWS index 90f069c6f7..f2da758e45 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -* Finale (.etf) import program. +* Finale (.etf), PMX (.pmx) and musedata import tools. * Point and click functionality using emacs and Xdvi. @@ -18,10 +18,12 @@ internals: LilyPond is smaller, cleaner, more flexible, etc. * Typography: More elegant slurs, aligned dynamics, text crescendos, -* Nice lyrics placement: Automagical phrasing and melisma alignment. +* Better lyrics placement: Automagical phrasing, melisma alignment, + stanza numbering. -* Part combining for orchestral scores and hymns: Automagical combining - and separating of two voices, with Solo/`a2 indications as appropriate. +* Part combining for orchestral scores and hymns: two voices are + combined automatic into a staff automatically, including Solo/`a2 + indications as appropriate. * Chordnames are now configurable in every respect @@ -32,3 +34,5 @@ internals: LilyPond is smaller, cleaner, more flexible, etc. * Finished ouverture Coriolan as full orchestral score example. * AsciiScript [check if broken, decide wether to keep] + +* Translations into Japanese and Russian diff --git a/VERSION b/VERSION index 745ecaa54b..c692feef21 100644 --- a/VERSION +++ b/VERSION @@ -1,7 +1,7 @@ PACKAGE_NAME=LilyPond MAJOR_VERSION=1 MINOR_VERSION=3 -PATCH_LEVEL=83 +PATCH_LEVEL=84 MY_PATCH_LEVEL= # use the above to send patches: MY_PATCH_LEVEL is always empty for a diff --git a/buildscripts/musedata2ly.py b/buildscripts/musedata2ly.py deleted file mode 100644 index 1bbd165db6..0000000000 --- a/buildscripts/musedata2ly.py +++ /dev/null @@ -1,210 +0,0 @@ -#!@PYTHON@ - -# musedata = musedata.stanford.edu -# musedata = COBOL for musicians. -# todo: rewrite this. - -import re -import sys -import string - -f = open (sys.argv[1]) -lines =f.readlines() - -def chomp (x): - return re.sub ('[\r\n \t]+$','', x) - -lines = map (chomp, lines) - -default_header_dict = { - 'tagline' :'automatically converted from Musedata', - 'copyright' : 'all rights reserved -- free for noncommercial use' - } - -# Jezus, wat een ranzig formaat. (2am) -def parse_header (lines): - d = default_header_dict - enter = string.split (lines[3], ' ') - d['enteredby'] = string.join (enter[1:]) - d['enteredon'] = enter[0] - d['opus'] = lines[4] - d['source'] = lines[5] - d['title'] = lines[6] - d['subtitle'] = lines[7] - d['instrument']= lines[8] - d['musedatamisc'] =lines[9] - d['musedatagroups'] =lines[10] - d['musedatagroupnumber']=lines[11] - - return d - -clef_dict = { -04: 'treble', -13 : 'alto', -22: 'bass', - -} - -def get_clef(s): - return '\\clef "%s";\n' % clef_dict [string.atoi (s)] - -def get_mudela_notename (p, ac): - if p > 5: - p = p - 7 - s = chr (p + ord ('c')) - infix = 'i' - if ac < 0: - infix = 'e' - ac = -ac - - while ac: - s = s + infix + 's' - ac = ac - 1 - return s - -def get_key (s): - i = string.atoi (s) - return '' - -def get_timesig (s): - return '\\time %s;\n' % s - - -divisions = 4 -def get_divisions_per_quarter (s): - divisions = string.atoi (s) - return '' - -def get_directive (s): - return '%% %s\n' % s - -def get_transposing (s): - return '' - -def get_num_instruments (s): - return '' - -attr_dict = { - 'C' : get_clef, - 'K' : get_key , - 'T' : get_timesig, - 'Q' : get_divisions_per_quarter, - 'D' : get_directive, - 'X' : get_transposing, - 'I': get_num_instruments, - } - -def parse_musical_attributes (l): - s = '' - l = l[1:] - atts = re.split('[ \t]+', l) - for a in atts: - if not a: - continue - m = re.search ('(.):(.*)', a) - if m == None: - print 'Huh, unknown attr `%s\'' % a - continue - - s = s + attr_dict[m.group(1)](m.group (2)) - return s - - -def get_mudela_pitch (n, a, o): - c = '\'' - if o < 1: - c = ',' - o = 1 - o - - return get_mudela_notename (n,a) + '%s' % c * o - -def dump_header (h, out): - out.write ('\\header {\n') - for tup in h.items (): - out.write ('\t%s = \"%s\";\n' % tup) - out.write ('}\n') - -header_dict = parse_header (lines[0:12]) -dump_header (header_dict, sys.stdout) - - -lines = lines [12:] - - -def parse_line_comment (l): - return re.sub ('@' , '%' , l) - -def parse_note_line (l): - pitch = ((ord (l[0]) -ord('A')) + 5) % 7 - acc = 0 - l= l[1:] - while l[0] == 'f': - l= l[1:] - acc = acc - 1 - while l[0] == '#': - l= l[1:] - acc = acc + 1 - while l[0] in ' \t': - l= l[1:] - - oct = 0 - if l[0] in '0123456789': - oct = string.atoi (l[0]) - 4 - l= l[1:] - - while l[0] in ' \t': - l= l[1:] - - - print get_mudela_pitch (pitch,acc,oct), parse_duration(l[:2]) - l = l[2:] - - - - -def parse_duration (l): - s = '' - while l[0] in '0123456789': - s = s + l[0] - l= l[1:] - print l - num = string.atoi (s) - den = 4 * divisions - - current_dots = 0 - try_dots = [3, 2, 1] - for d in try_dots: - f = 1 << d - multiplier = (2*f-1) - if num % multiplier == 0 and den % f == 0: - num = num / multiplier - den = den / f - current_dots = current_dots + d - - if num <> 1: - sys.stderr.write ('huh. Durations left') - return '%s%s' % (den, '.' * current_dots) - -comment_switch = 0 -for l in lines: - if l[0] == '&': - comment_switch = not comment_switch - if comment_switch: - l= l[1:] - print '%{' - else: - print '%}' - - if comment_switch: - print l - continue - - if 0: - pass - elif l[0] == '$': - print parse_musical_attributes (l) - elif l[0] == '@': - parse_line_comment (l) - - elif l[0] in 'ABCDEFG': - parse_note_line (l) diff --git a/buildscripts/pmx2ly.py b/buildscripts/pmx2ly.py deleted file mode 100644 index 2f9155005e..0000000000 --- a/buildscripts/pmx2ly.py +++ /dev/null @@ -1,217 +0,0 @@ -#!@PYTHON@ - -# (urg! wat een pokkeformaat (pokkenformaat?)) - -import string -import sys -import re - -fn = sys.argv[1] - -ls = open (fn).readlines () -def stripcomment (l): - return re.sub ('[ \t]*%.*$\n', '', l) - -def stripwhite (l): - return re.sub ('[ \n\t]+', ' ', l) - -def stripeols (l): - return re.sub ('^ ', '', re.sub (' $', '', l)) - -ls = map (stripcomment, ls) -ls = map (stripwhite, ls) -ls = map (stripeols, ls) - - -ls = filter (lambda x: x <> '', ls) - -opening = ls[0] -ls = ls[1:] - - -opening = map (string.atoi, re.split ('[\t ]+', opening)) - -(no_staffs, no_instruments, timesig_num,timesig_den, ptimesig_num, - ptimesig_den, pickup_beats,keysig_number) = tuple (opening) - - -opening = ls[0] -ls = ls[1:] - -# ignore this. -# opening = map (string.atoi, re.split ('[\t ]+', opening)) -# (no_pages,no_systems, musicsize, fracindent) = tuple (opening) - -instruments = [] -while len (instruments) < no_instruments: - instruments.append (ls[0]) - ls = ls[1:] - -class Staff: - def __init__ (self): - self.voices = ([],[]) - self.clef = None - self.instrument = 0 -l = ls[0] -ls = ls[1:] - -staffs = map (lambda x: Staff (), range(0, no_staffs)) -staff_idx = 0 - -for s in staffs: - s.clef = l[0] - l = l[1:] - -# dump path -ls = ls[1:] - -# dump more ? -ls = ls[2:] - -actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'} - -def pitch_to_lily_string (tup): - (o,n,a) = tup - - nm = chr((n + 2) % 7 + ord ('a')) - nm = nm + actab[a] - if o > 0: - nm = nm + "'" * o - elif o < 0: - nm = nm + "," * -o - return nm - -class Chord: - def __init__ (self): - self.pitches = [] - self.dots = 0 - self.basic_duration = 0 - - def dump (self): - str = '' - - for p in self.pitches: - if str: - str = str + ' ' - str = str + pitch_to_lily_string (p) - - if len (self.pitches) > 1: - str = '<%s>' % str - elif len (self.pitches) == 0: - str = 'r' - - - sd = '' - if self.basic_duration == 0.5: - sd = '\\breve' - else: - sd = '%d' % self.basic_duration - - str = str + sd + '.' * self.dots - return str - - -input_left = string.join (ls, ' ') - - -input_left = re.sub ('[ \t\n]+', ' ', input_left) - -SPACE=' \t\n' -DIGITS ='0123456789' -basicdur_table = { - 9: 0.5, - 0: 0 , - 2: 2 , - 4: 4 , - 8: 8 , - 1: 16, - 3: 32, - 6: 64 - } - -class Parser: - def __init__ (self): - self.chords = [] - self.forced_duration = None - self.last_octave = 4 - - def parse_note (self, str): - ch = Chord () - - name = None - if str[0] <> 'r': - name = (ord (str[0]) - ord('a') + 5) % 7 - str = str[1:] - - forced_duration = 0 - alteration = 0 - dots = 0 - oct = None - durdigit = None - multibar = 0 - while str[0] in 'dsfmnul0123456789.,': - c = str[0] - str = str[1:] - if c == 'f': - alteration = alteration -1 - elif c == 'n': - alteration = 0 - elif c == 'm': - multibar = 1 - elif c == 's': - alteration = alteration +1 - elif c == 'd': - dots = dots + 1 - elif c in DIGITS and durdigit == None: - durdigit = string.atoi (c) - elif c in DIGITS: - oct = string.atoi (c) - 4 - elif c == '.': - dots = dots+ 1 - forced_duration = 2 - elif c == ',': - forced_duration = 2 - - - if durdigit: - ch.basic_duration = basicdur_table[durdigit] - self.last_basic_duration = ch.basic_duration - else: - ch.basic_duration = self.last_basic_duration - - if name: - if oct: - self.last_octave =oct - else: - oct = self.last_octave - - if name: - ch.pitches.append ((oct, name, alteration)) - - ch.dots = dots - - - if forced_duration: - self.forced_duration = ch.basic_duration / forced_duration - - - self.chords.append (ch) - while str[0] in SPACE: - str = str [1:] - return str - - -parser = Parser() -while input_left: - while input_left[0] in 'abcdefgr': - input_left = parser.parse_note (input_left) - print input_left[0] - - sys.stderr.write ("\nHuh? Unknown directive %s" %input_left[0:1]) - input_left = input_left[1:] - - - -for c in parser.chords: - print c.dump () - diff --git a/input/bugs/script-collide.ly b/input/bugs/script-collide.ly new file mode 100644 index 0000000000..3167a77c85 --- /dev/null +++ b/input/bugs/script-collide.ly @@ -0,0 +1,2 @@ + +\score { \notes { c4-1-2-3 }} diff --git a/input/test/multistanza.ly b/input/test/lyrics-multi-stanza.ly similarity index 100% rename from input/test/multistanza.ly rename to input/test/lyrics-multi-stanza.ly diff --git a/lily/axis-group-engraver.cc b/lily/axis-group-engraver.cc index 57d42f3113..ef031b3229 100644 --- a/lily/axis-group-engraver.cc +++ b/lily/axis-group-engraver.cc @@ -47,7 +47,7 @@ Axis_group_engraver::do_creation_processing () Axis_group_interface::set_axes (staffline_p_, Y_AXIS, Y_AXIS); Score_element * it = unsmob_element (get_property ("currentCommandColumn")); - Pointer_group_interface (it, "bounded-by-me").add_element (staffline_p_); + staffline_p_->set_bound(LEFT,it); announce_element (staffline_p_, 0); @@ -84,7 +84,7 @@ Axis_group_engraver::do_removal_processing () Score_element * it = unsmob_element (get_property ("currentCommandColumn")); - Pointer_group_interface (it, "bounded-by-me").add_element (staffline_p_); + staffline_p_->set_bound(RIGHT,it); typeset_element (staffline_p_); diff --git a/lily/directional-element-interface.cc b/lily/directional-element-interface.cc index 9689529f43..0b08777be9 100644 --- a/lily/directional-element-interface.cc +++ b/lily/directional-element-interface.cc @@ -10,7 +10,7 @@ #include "directional-element-interface.hh" -static SCM Directional_element_interface::direction_sym; +SCM Directional_element_interface::direction_sym; static void init_functions () diff --git a/lily/dynamic-engraver.cc b/lily/dynamic-engraver.cc index 9093c2aa66..0593606bb6 100644 --- a/lily/dynamic-engraver.cc +++ b/lily/dynamic-engraver.cc @@ -186,7 +186,10 @@ Dynamic_engraver::do_process_music () else { assert (!finished_cresc_p_); - cresc_p_->set_bound (RIGHT, unsmob_element (get_property ("currentMusicalColumn"))); + Score_element* cc = unsmob_element (get_property ("currentMusicalColumn")); + + cresc_p_->set_bound (RIGHT, cc); + finished_cresc_p_ = cresc_p_; cresc_p_ = 0; current_cresc_req_ = 0; @@ -233,7 +236,8 @@ Dynamic_engraver::do_process_music () + "Spanner", SCM_UNDEFINED); } - cresc_p_->set_bound (LEFT, unsmob_element (get_property ("currentMusicalColumn"))); + Score_element *cc = unsmob_element (get_property ("currentMusicalColumn")); + cresc_p_->set_bound (LEFT, cc); /* @@ -324,7 +328,7 @@ Dynamic_engraver::typeset_all () if (finished_line_spanner_) { Side_position::add_staff_support (finished_line_spanner_); - +#if 0 if (!finished_line_spanner_->get_bound (LEFT)) { Score_element * cmc @@ -335,7 +339,8 @@ Dynamic_engraver::typeset_all () finished_line_spanner_->set_bound (RIGHT, finished_line_spanner_->get_bound (LEFT)); - +#endif + extend_spanner_over_elements (finished_line_spanner_); typeset_element (finished_line_spanner_); finished_line_spanner_ = 0; } diff --git a/lily/include/spanner.hh b/lily/include/spanner.hh index 165ed99efe..8792301dfc 100644 --- a/lily/include/spanner.hh +++ b/lily/include/spanner.hh @@ -61,6 +61,7 @@ protected: void add_bound_item (Spanner*, Item* n); +void extend_spanner_over_elements (Score_element* span); #endif diff --git a/lily/line-group-group-engraver.cc b/lily/line-group-group-engraver.cc index 53c61f40c3..a03c8b6a06 100644 --- a/lily/line-group-group-engraver.cc +++ b/lily/line-group-group-engraver.cc @@ -37,7 +37,7 @@ Line_group_engraver_group::do_removal_processing() Score_element * it = unsmob_element (get_property (ly_symbol2scm ("currentCommandColumn"))); - Pointer_group_interface (it, "bounded-by-me").add_element (staffline_p_); + staffline_p_->set_bound(RIGHT,it); Engraver_group_engraver::typeset_element (staffline_p_); staffline_p_ = 0; @@ -50,7 +50,6 @@ Line_group_engraver_group::do_creation_processing() Score_element * it = unsmob_element (get_property (ly_symbol2scm ("currentCommandColumn"))); staffline_p_->set_bound(LEFT,it); - Pointer_group_interface (it, "bounded-by-me").add_element (staffline_p_); Engraver::announce_element (staffline_p_,0); } diff --git a/lily/main.cc b/lily/main.cc index c1d6d6e6ab..1808daefc5 100644 --- a/lily/main.cc +++ b/lily/main.cc @@ -100,8 +100,10 @@ identify (ostream* os) void usage () { - identify (&cout); - cout << "\n"; + + /* + No version number or newline here. It confuses help2man + */ cout << _f ("Usage: %s [OPTION]... [FILE]...", "lilypond"); cout << "\n\n"; cout << _ ("Typeset music and or play MIDI from FILE"); diff --git a/lily/paper-column.cc b/lily/paper-column.cc index fcdc933f9a..c94541577a 100644 --- a/lily/paper-column.cc +++ b/lily/paper-column.cc @@ -43,7 +43,7 @@ Paper_column::Paper_column (SCM l) Axis_group_interface::set_interface (this); Axis_group_interface::set_axes (this, X_AXIS, X_AXIS); Spaceable_element::set_interface (this); - set_elt_property ("bounded-by-me", SCM_EOL); + line_l_=0; rank_i_ = -1; } diff --git a/lily/score-engraver.cc b/lily/score-engraver.cc index 2cbeafd1eb..66b759e7d2 100644 --- a/lily/score-engraver.cc +++ b/lily/score-engraver.cc @@ -81,11 +81,9 @@ Score_engraver::do_removal_processing() Engraver_group_engraver::do_removal_processing(); scoreline_l_->set_bound(RIGHT,command_column_l_); command_column_l_->set_elt_property ("breakable", SCM_BOOL_T); - typeset_all (); - set_columns (0,0); } @@ -170,16 +168,16 @@ Score_engraver::typeset_all() void Score_engraver::do_pre_move_processing() { + // this generates all items. + Engraver_group_engraver::do_pre_move_processing(); + + typeset_all(); if (to_boolean (command_column_l_->get_elt_property ("breakable"))) { breaks_i_ ++; if (! (breaks_i_%8)) progress_indication ("[" + to_str ( breaks_i_) + "]"); } - // this generates all items. - Engraver_group_engraver::do_pre_move_processing(); - - typeset_all(); } void @@ -194,11 +192,16 @@ Score_engraver::set_columns (Paper_column *new_command_l, if (*current[i]) { scoreline_l_->add_column ((*current[i])); +#if 0 + /* + TODO: delay this decision. + */ if (!Paper_column::used_b (*current[i])) { (*current[i])->suicide (); *current[i] =0; } +#endif } if (news[i]) *current[i] = news[i]; diff --git a/lily/spanner.cc b/lily/spanner.cc index 131f19e5af..a19032f2eb 100644 --- a/lily/spanner.cc +++ b/lily/spanner.cc @@ -18,7 +18,7 @@ #include "paper-column.hh" #include "line-of-score.hh" #include "break-align-item.hh" - +#include "group-interface.hh" void Spanner::do_break_processing () @@ -194,6 +194,19 @@ Spanner::set_bound(Direction d, Score_element*s) { set_parent (i, X_AXIS); } + + /* + Signal that this column needs to be kept alive. They need to be + kept alive to have meaningful position and linebreaking. + + [maybe we should try keeping all columns alive?, and perhaps + inherit position from their (non-)musical brother] + + */ + if (dynamic_cast (i)) + { + Pointer_group_interface (i, "bounded-by-me").add_element (this); + } } @@ -314,3 +327,70 @@ add_bound_item (Spanner* sp, Item*it) else sp->set_bound (RIGHT, it); } + +static void +extend_spanner_over_item (Item *it, SCM extremal_pair) +{ + if (!it) + return; + Item * col = it->column_l (); + Item * i1 = dynamic_cast (unsmob_element (gh_car (extremal_pair))); + Item * i2 = dynamic_cast (unsmob_element (gh_cdr (extremal_pair))); + int r = Paper_column::rank_i (col); + if (!i1 || r < Paper_column::rank_i (i1->column_l ())) + { + gh_set_car_x (extremal_pair, it->self_scm ()); + } + if (!i2 || r > Paper_column::rank_i (i2->column_l ())) + { + gh_set_cdr_x (extremal_pair, it->self_scm ()); + } +} + +static void +extend_spanner_over_elements (SCM value, SCM extremal_pair) +{ + if (gh_pair_p (value)) + { + extend_spanner_over_elements (gh_car (value), extremal_pair); + extend_spanner_over_elements (gh_cdr (value), extremal_pair); + } + else if (unsmob_element (value)) + { + if (Spanner * sp = dynamic_cast (unsmob_element(value))) + { + extend_spanner_over_item (sp->get_bound (LEFT), extremal_pair); + extend_spanner_over_item (sp->get_bound (RIGHT), extremal_pair); + } + else if (Item * it= dynamic_cast (unsmob_element(value))) + extend_spanner_over_item (it, extremal_pair); + } +} + + +/* + Make sure that the left and right bounds encompasses all objects it + points to. + + TODO: maybe be more specific. Most probably fucks up if someone sets + a pointer to the staffsymbol in S +*/ +void +extend_spanner_over_elements (Score_element*s) +{ + Spanner*sp = dynamic_cast (s); + + SCM s1 = sp->get_bound (LEFT) ? sp->get_bound (LEFT)->self_scm () : SCM_EOL; + SCM s2 = sp->get_bound (RIGHT) ? sp->get_bound (RIGHT)->self_scm () : SCM_EOL; + + SCM pair = gh_cons (s1,s2); + extend_spanner_over_elements (sp->mutable_property_alist_, pair); + + Score_element *p1 = unsmob_element (gh_car (pair)); + Score_element* p2 = unsmob_element (gh_cdr (pair)); + sp->set_bound (LEFT,p1); + sp->set_bound (RIGHT, p2); + + //extra precaution. +} + diff --git a/lily/timing-translator.cc b/lily/timing-translator.cc index 430a9939bc..b69a614ea0 100644 --- a/lily/timing-translator.cc +++ b/lily/timing-translator.cc @@ -38,40 +38,40 @@ Timing_translator::do_try_music (Music*r) return false; } } + + /* + We have to do this soon enough. Maybe we'd better disguise + \time as a \property. Then all settings will be `immediate'. + */ + if (Time_signature_change_req *c + = dynamic_cast (t)) + set_time_signature (c->beats_i_, c->one_beat_i_); timing_req_l_arr_.push(t); return true; } return false; } + void Timing_translator::do_process_music() { for (int i=0; i < timing_req_l_arr_.size (); i++) { - Timing_req * tr_l = timing_req_l_arr_[i]; - - if (Time_signature_change_req *m_l = dynamic_cast (tr_l)) - { - int b_i= m_l->beats_i_; - int o_i = m_l->one_beat_i_; - set_time_signature (b_i, o_i); - } - else if (dynamic_cast (tr_l)) + if (!dynamic_cast (timing_req_l_arr_[i])) + continue; + if (measure_position ()) { - if (measure_position ()) - { - Moment nm; - tr_l ->origin ()->warning (_f ("barcheck failed at: %s", - measure_position ().str ())); - // resync - daddy_trans_l_->set_property("measurePosition", nm.make_scm ()); - } + timing_req_l_arr_[i]->origin ()->warning (_f ("barcheck failed at: %s", + measure_position ().str ())); + Moment zero; + + // resync + daddy_trans_l_->set_property("measurePosition", zero.make_scm ()); } } } - void Timing_translator::do_pre_move_processing() { diff --git a/make/out/lilypond.lsm b/make/out/lilypond.lsm index 38d3df23ec..69e8b37573 100644 --- a/make/out/lilypond.lsm +++ b/make/out/lilypond.lsm @@ -1,15 +1,15 @@ Begin3 Title: LilyPond -Version: 1.3.83 -Entered-date: 01SEP00 +Version: 1.3.84 +Entered-date: 02SEP00 Description: Keywords: music notation typesetting midi fonts engraving Author: hanwen@cs.uu.nl (Han-Wen Nienhuys) janneke@gnu.org (Jan Nieuwenhuizen) Maintained-by: hanwen@stack.nl (Han-Wen Nienhuys) Primary-site: sunsite.unc.edu /pub/Linux/apps/sound/convert - 1000k lilypond-1.3.83.tar.gz + 1000k lilypond-1.3.84.tar.gz Original-site: ftp.cs.uu.nl /pub/GNU/LilyPond/development/ - 1000k lilypond-1.3.83.tar.gz + 1000k lilypond-1.3.84.tar.gz Copying-policy: GPL End diff --git a/make/out/lilypond.spec b/make/out/lilypond.spec index 71d33beb67..bc861526da 100644 --- a/make/out/lilypond.spec +++ b/make/out/lilypond.spec @@ -1,9 +1,9 @@ Name: lilypond -Version: 1.3.83 +Version: 1.3.84 Release: 1 Copyright: GPL Group: Applications/Publishing -Source0: ftp.cs.uu.nl:/pub/GNU/LilyPond/development/lilypond-1.3.83.tar.gz +Source0: ftp.cs.uu.nl:/pub/GNU/LilyPond/development/lilypond-1.3.84.tar.gz Summary: A program for printing sheet music. URL: http://www.cs.uu.nl/~hanwen/lilypond # Icon: lilypond-icon.gif diff --git a/scripts/etf2ly.py b/scripts/etf2ly.py index 1835fd340b..366e497e7d 100644 --- a/scripts/etf2ly.py +++ b/scripts/etf2ly.py @@ -87,6 +87,36 @@ def find_scale (transposition): trscale = map(lambda x, k=transposition: transpose(x, k), cscale) return trscale +def EDU_to_duration (edu): + log = 1 + d = 4096 + while d > edu: + d = d >> 1 + log = log << 1 + + edu = edu - d + dots = 0 + if edu == d /2: + dots = 1 + elif edu == d*3/4: + dots = 2 + return (log, dots) + +def rat_to_lily_duration (rat): + (n,d) = rat + + basedur = 1 + while d and d % 2 == 0: + basedur = basedur << 1 + d = d >> 1 + + str = 's%d' % basedur + if n <> 1: + str = str + '*%d' % n + if d <> 1: + str = str + '/%d' % d + + return str def gcd (a,b): if b == 0: @@ -126,6 +156,8 @@ def rat_neg (a): (p,q) = a return (-p,q) + + def rat_subtract (a,b ): return rat_add (a, rat_neg (b)) @@ -481,37 +513,7 @@ class Staff: return str -def EDU_to_duration (edu): - log = 1 - d = 4096 - while d > edu: - d = d >> 1 - log = log << 1 - - edu = edu - d - dots = 0 - if edu == d /2: - dots = 1 - elif edu == d*3/4: - dots = 2 - return (log, dots) - -def rat_to_lily_duration (rat): - (n,d) = rat - basedur = 1 - while d and d % 2 == 0: - basedur = basedur << 1 - d = d >> 1 - - str = 's%d' % basedur - if n <> 1: - str = str + '*%d' % n - if d <> 1: - str = str + '/%d' % d - - return str - class Chord: def __init__ (self, finale_entry): @@ -939,34 +941,37 @@ class Etf_file: e.prev = self.chords[e.finale[0][1]] e.next = self.chords[e.finale[0][2]] - - - - - def identify(): sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version)) def help (): - print r""" -Convert ETF to LilyPond. + print """Usage: etf2ly [OPTION]... ETF-FILE -Usage: etf2ly [OPTION]... ETF-FILE +Convert ETF to LilyPond. Options: - -h, --help this help - -o, --output=FILE set output filename to FILE - -v, --version version information + -h,--help this help + -o,--output=FILE set output filename to FILE + -v,--version version information Enigma Transport Format is a format used by Coda Music Technology's Finale product. This program will convert a subset of ETF to a ready-to-use lilypond file. +Report bugs to bug-gnu-music@gnu.org +Written by Han-Wen Nienhuys """ def print_version (): - print r"""etf2ly (GNU lilypond) %s""" % version + print r"""etf2ly (GNU lilypond) %s + +This is free software. It is covered by the GNU General Public License, +and you are welcome to change it and/or distribute copies of it under +certain conditions. Invoke as `midi2ly --warranty' for more information. + +Copyright (c) 2000 by Han-Wen Nienhuys +""" % version diff --git a/scripts/musedata2ly.py b/scripts/musedata2ly.py new file mode 100644 index 0000000000..ec139d467c --- /dev/null +++ b/scripts/musedata2ly.py @@ -0,0 +1,616 @@ +#!@PYTHON@ + +# musedata = musedata.stanford.edu +# musedata = COBOL for musicians. + + +# TODO +# +# * clefs, +# * keys, +# * staffs, +# * multiple voices (they use `Backspace' (shudder) +# * tuplets +# + +import re +import sys +import string +import getopt +import os +program_name = 'musedata2ly' +version = '@TOPLEVEL_VERSION@' +if version == '@' + 'TOPLEVEL_VERSION' + '@': + version = '(unknown version)' # uGUHGUHGHGUGH + + + +ref_header_dict = { + 'COM': 'composer', + 'OPR': 'collection', + 'OTL': 'title', + 'OMV': 'subtitle', + 'YOR': 'source', + 'AGN': 'instrument', + 'END': 'encodingdate', + 'CDT': 'date', + 'OCY': 'composedin', + 'AST': 'genre', + 'YEC': 'copyright', + 'YEM': 'license', + 'YEN': 'encodingcountry', + 'EED': 'editor', + 'SCA': 'opus', + 'ONM': 'onm', + 'ENC': 'musedataencoder', + 'KEY': 'musedatakey', + 'AFT': 'musedatastage' + } + + +class Ref_parser: + def __init__ (self, fn): + self.dict = {} + + ls = open (fn).readlines () + self.parse (ls) + def parse (self,ls): + for l in ls: + m = re.match('!!!([A-Z]+):[ \t]+(.*)$',l) + if m: + key = m.group(1) + val = m.group (2) + val = re.sub ('[ \t]+', ' ', val) + try: + + key =ref_header_dict [key] + except KeyError: + sys.stderr.write ('\nUnknown ref key \`%s\'' % key) + s = '' + try: + s = self.dict[key] + except KeyError: + pass + + s = s + val + self.dict[key] = s + def dump( self): + str = '' + for (k,v) in self.dict.items (): + str = str +' %s = "%s";\n' % (k,v) + str = '\\header {\n%s}' % str + return str + +verbose = 0 + + +actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'} + +def pitch_to_lily_string (tup): + (o,n,a) = tup + + nm = chr((n + 2) % 7 + ord ('a')) + nm = nm + actab[a] + if o > 0: + nm = nm + "'" * o + elif o < 0: + nm = nm + "," * -o + return nm + +def get_key (s): + i = string.atoi (s) + return '' + +def get_timesig (s): + return '\\time %s;\n' % s + + +divisions = 4 +def get_divisions_per_quarter (s): + divisions = string.atoi (s) + return '' + +def get_directive (s): + return '%% %s\n' % s + +def get_transposing (s): + return '' + +def get_num_instruments (s): + return '' + +def get_mudela_notename (p, ac): + if p > 5: + p = p - 7 + s = chr (p + ord ('c')) + infix = 'i' + if ac < 0: + infix = 'e' + ac = -ac + + while ac: + s = s + infix + 's' + ac = ac - 1 + return s +def get_clef (): + return '' + +SPACES = ' ' +DIGITS = "0123456789" + + +clef_dict = { +04: 'treble', +13 : 'alto', +22: 'bass', +} +attr_dict = { + 'C' : get_clef, + 'K' : get_key , + 'T' : get_timesig, + 'Q' : get_divisions_per_quarter, + 'D' : get_directive, + 'X' : get_transposing, + 'I': get_num_instruments, + } + + +script_table = { +'v': '\\upbow', +'n': '\\downbow', +'o': '\\harmonic', +'0': '"openstring', +'Q': '\\thumb', +'>': '^', +'V': '^', +'.': '.', +'_': '-', +'=': '"det leg"', +'i': '|', +'s': '"\\\\textsharp"', +'n': '"\\\\textnatural"', +'b': '"\\\\textflat"', +'F': '\\fermata', +'E': '\\fermata', +} + +class Attribute_set: + def __init__ (self, dict): + self.dict = dict + def dump (self): + return '' + +class Chord: + def __init__ (self): + self.pitches = [] + self.grace = 0 + self.cue = 0 + self.slurstart = [] + self.slurstop = [] + self.scripts = [] + self.syllables = [] + self.dots = 0 + self.basic_duration = 4 + self.tied = 0 + + self.note_suffix = self.note_prefix = '' + self.chord_suffix = self.chord_prefix = '' + + def add_script (self,s): + self.scripts.append (s) + def set_duration (self, d): + self.basic_duration = d + def add_syllable (self, s): + self.syllables.append (s) + def add_pitch (self,t): + self.pitches.append (t) + + def dump (self): + str = '' + + sd = '' + if self.basic_duration == 0.5: + sd = '\\breve' + else: + sd = '%d' % self.basic_duration + + sd = sd + '.' * self.dots + for p in self.pitches: + if str: + str = str + ' ' + str = str + pitch_to_lily_string (p) + sd + + for s in self.scripts: + str = str + '-' + s + + str = self.note_prefix +str + self.note_suffix + + if len (self.pitches) > 1: + str = '<%s>' % str + elif len (self.pitches) == 0: + str = 'r' + sd + + str = self.chord_prefix + str + self.chord_suffix + + return str + +class Parser: + def append_entry (self, e): + self.entries.append (e) + def append_chord (self,c ): + self.chords.append (c) + self.entries.append (c) + def last_chord (self): + return self.chords[-1] + def __init__ (self, fn): + self.divs_per_q = 1 + self.header_dict = { + 'tagline' :'automatically converted from Musedata', + 'copyright' : 'all rights reserved -- free for noncommercial use' + # musedata license (argh) + } + self.entries = [] + self.chords = [] + + lines = open (fn).readlines () + lines = self.parse_header (lines) + lines = self.append_lines (lines) + str = string.join (lines, '\n') + lines = re.split ('[\n\r]+', str) + self.parse_body (lines) + + def parse_header (self, lines): + enter = string.split (lines[3], ' ') + self.header_dict['enteredby'] = string.join (enter[1:]) + self.header_dict['enteredon'] = enter[0] + self.header_dict['opus'] = lines[4] + self.header_dict['source'] = lines[5] + self.header_dict['title'] = lines[6] + self.header_dict['subtitle'] = lines[7] + self.header_dict['instrument']= lines[8] + self.header_dict['musedatamisc'] =lines[9] + self.header_dict['musedatagroups'] =lines[10] + self.header_dict['musedatagroupnumber']=lines[11] + lines = lines[12:] + comment = 0 + while lines: + if lines[0][0] == '$': + break + lines = lines[1:] + return lines + + def parse_musical_attributes (self,l): + atts = re.split('([A-Z][0-9]?):', l) + atts = atts[1:] + found = {} + while len (atts): + id = atts[0] + val = atts[1] + atts = atts[2:] + found[id] = val + + try: + self.divs_per_q = string.atoi (found['Q']) + except KeyError: + pass + + self.append_entry (Attribute_set (found)) + def append_entry (self, e): + self.entries.append (e) + + def parse_line_comment (self,l): + pass + + def parse_note_line (self,l): + ch = None + if verbose: + print DIGITS+DIGITS+DIGITS + print l + pi = l[0:5] + di = l[5:8] + tied = l[8:9] == '-' + + cue = grace = 0 + if (pi[0] == 'g'): + grace = 1 + pi = pi[1:] + elif (pi[0] == 'c'): + cue = 1 + pi = pi[1:] + + if pi[0] == ' ': + ch = self.last_chord () + pi = pi[1:] + else: + ch = Chord () + self.append_chord (ch) + + + ch.cue = ch.cue or cue + ch.grace = ch.grace or grace + + while pi[0] in SPACES: + pi = pi[1:] + + if pi[0] <> 'r': + name = ((ord (pi[0]) -ord('A')) + 5) % 7 + alter = 0 + pi = pi[1:] + while pi and pi[0] in '#f': + if pi[0] == '#': + alter = alter + 1 + else: + alter = alter - 1 + pi = pi[1:] + + oct = string.atoi (pi) - 3 + + pittup = (oct, name ,alter) + ch.add_pitch (pittup) + + base_dur = None + if ch.cue or ch.grace: + c = di[2] + if c == '0': + ch.accaciatura = 1 + elif c == 'A': + base_dur = 0.5 + else: + base_dur = 1 << (9 - (ord (c) - ord ('0'))) + else: + base_dur = (4 * self.divs_per_q) / string.atoi (di) + ch.set_duration (base_dur) + + ch.tied = ch.tied or tied + dots = 0 + + + if l[18:19] == '.': + dots = 1 + elif l[18:19] == ':': + dots = 2 + + if l[26:27] == '[': + ch.start_beam = 1 + elif l[26:27] == ']': + ch.end_beam = 1 + + + additional = l[32:44] + for c in additional: + try: + s = list('([{z').index (c) + ch.slurstart.append( s) + sluropen = list(')]}x').index (c) + ch.slurstop.append( s) + except ValueError: + pass + if c == '*': + ch.start_tuplet = 1 + continue + elif c == '!': + ch.stop_tuplet = 1 + continue + + if c in DIGITS: + ch.add_script (c) + continue + + if c == ' ' : + continue + + try: + scr = script_table[c] + ch.add_script (scr) + c = None + except KeyError: + sys.stderr.write ("\nFixme: script `%s' not done\n" % c) + + text = l[40:81] + sylls = string.split (text,'|') + + for syl in sylls: + ch.add_syllable (syl) + + + def parse_measure_line (self,l): + pass + + + def parse_duration (l): + s = '' + while l[0] in '0123456789': + s = s + l[0] + l= l[1:] + print l + num = string.atoi (s) + den = 4 * divisions + + current_dots = 0 + try_dots = [3, 2, 1] + for d in try_dots: + f = 1 << d + multiplier = (2*f-1) + if num % multiplier == 0 and den % f == 0: + num = num / multiplier + den = den / f + current_dots = current_dots + d + + if num <> 1: + sys.stderr.write ('huh. Durations left') + return '%s%s' % (den, '.' * current_dots) + + def append_lines (self,ls): + nls = [] + for l in ls: + if l[0] == 'a': + nls[-1] = nls[-1]+l[1:] + else: + nls.append(l) + return nls + def dump (self): + s = '' + ln = '' + for e in self.entries: + + next = ' ' + e.dump() + if len (ln) + len (next) > 72: + s = s +ln + '\n' + ln = '' + ln = ln + next + + s = s + ln + + s = '\\notes {\n %s \n}' % s + return s + + def parse_body (self,lines): + comment_switch = 0 + for l in lines: + + + c = l[0] + if c == '&': + comment_switch = not comment_switch + continue + + if comment_switch: + continue + + if 0: + pass + elif c == '$': + self.parse_musical_attributes (l) + elif c == '@': + self.parse_line_comment (l) + elif c == '*': + self.parse_musical_directions (l) + elif c in 'ABCDEFGr ': + self.parse_note_line (l) + elif c == 'm': + self.parse_measure_line (l) + elif c == '/': + break + elif c in 'PS': + pass # ignore sound & print + else: + sys.stderr.write ("\nUnrecognized record `%s'\n"% l) + + + + + +def help (): + sys.stdout.write ( +"""Usage: musedata2ly [OPTION]... FILE1 [FILE2 ...] + +Convert musedata to LilyPond. + +Options: + -h,--help this help + -o,--output=FILE set output filename to FILE + -v,--version version information + -r,--ref=REF read background information from ref-file REF + +Musedata (http://www.ccarh.org/musedata/) is an electronic library of +classical music scores, currently comprising XXX scores. The music is +encoded in so-called Musedata format +(http://www.ccarh.org/publications/books/beyondmidi/online/musedata). +musedata2ly converts a set of musedata files to one .ly file, and will +include a \header field if a .ref file is supplied + +Report bugs to bug-gnu-music@gnu.org. + +Written by Han-Wen Nienhuys +""") + + +def print_version (): + sys.stdout.write ("""musedata2ly (GNU LilyPond) %s + +This is free software. It is covered by the GNU General Public License, +and you are welcome to change it and/or distribute copies of it under +certain conditions. Invoke as `midi2ly --warranty' for more information. + +Copyright (c) 2000 by Han-Wen Nienhuys +""" % version) +def identify(): + sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version)) + + + +(options, files) = getopt.getopt (sys.argv[1:], 'r:vo:h', ['ref=', 'help','version', 'output=']) +out_filename = None +ref_file = None +for opt in options: + o = opt[0] + a = opt[1] + if o== '--help' or o == '-h': + help () + sys.exit (0) + elif o == '--version' or o == '-v': + print_version () + sys.exit(0) + elif o == '--ref' or o == '-r': + ref_file = a + elif o == '--output' or o == '-o': + out_filename = a + else: + print o + raise getopt.error + +identify() + + + +ly = '' + + +found_ids = '' +for f in files: + if f == '-': + f = '' + + sys.stderr.write ('Processing `%s\'\n' % f) + + e = Parser(f) + + id = os.path.basename (f) + id = re.sub ('[^a-zA-Z0-9]', 'x', id) + id = 'voice%s' % id + ly =ly + '\n\n%s = \\context Staff = "%s" %s\n\n' % (id, id, e.dump ()) + + found_ids = found_ids + '\\%s\n' % id + +found_ids = '\n\n\n\\score { < %s > } ' % found_ids + +ly_head = '' +if ref_file: + head = Ref_parser (ref_file) + if not out_filename: + t = '' + st = '' + try: + t = head.dict['title'] + st= head.dict['subtitle'] + except KeyError: + pass + + t = t + '-' +st + + t = re.sub ("^ +(.*) +$", r"\1", t) + t = re.sub ("\\.", '', t) + out_filename = re.sub ('[^a-zA-Z0-9-]', '-', t) + out_filename = out_filename+ '.ly' + ly_head = head.dump () + +if not out_filename: + out_filename = 'musedata.ly' + +sys.stderr.write ('Writing `%s\'\n' % out_filename) + +fo = open (out_filename, 'w') +fo.write ('%% lily was here -- automatically converted by musedata.ly\n') +fo.write(ly_head + ly + found_ids) +fo.close () + diff --git a/scripts/pmx2ly.py b/scripts/pmx2ly.py new file mode 100644 index 0000000000..0f656fe0bf --- /dev/null +++ b/scripts/pmx2ly.py @@ -0,0 +1,766 @@ +#!@PYTHON@ + +# (urg! wat een pokkeformaat (pokkenformaat?)) +import os +import string +import sys +import re +import getopt + +program_name = 'pmx2ly' +version = '@TOPLEVEL_VERSION@' +if version == '@' + 'TOPLEVEL_VERSION' + '@': + version = '(unknown version)' # uGUHGUHGHGUGH + + +def encodeint (i): + return chr ( i + ord ('A')) + +def stripcomment (l): + return re.sub ('[ \t]*%.*$\n', '', l) + +def stripwhite (l): + return re.sub ('[ \n\t]+', ' ', l) + +def stripeols (l): + return re.sub ('^ ', '', re.sub (' $', '', l)) + +actab = {-2: 'eses', -1: 'es', 0 : '', 1: 'is', 2:'isis'} + +def pitch_to_lily_string (tup): + (o,n,a) = tup + + nm = chr((n + 2) % 7 + ord ('a')) + nm = nm + actab[a] + if o > 0: + nm = nm + "'" * o + elif o < 0: + nm = nm + "," * -o + return nm + +def gcd (a,b): + if b == 0: + return a + c = a + while c: + c = a % b + a = b + b = c + return a + +def rat_simplify (r): + (n,d) = r + if d < 0: + d = -d + n = -n + if n == 0: + return (0,1) + else: + g = gcd (n, d) + return (n/g, d/g) + +def rat_multiply (a,b): + (x,y) = a + (p,q) = b + + return rat_simplify ((x*p, y*q)) + +def rat_divide (a,b): + (p,q) = b + return rat_multiply (a, (q,p)) + +tuplet_table = { + 2: 3, + 3: 2, + 5: 4 +} + + +def rat_add (a,b): + (x,y) = a + (p,q) = b + + return rat_simplify ((x*q + p*y, y*q)) + +def rat_neg (a): + (p,q) = a + return (-p,q) + + +def rat_larger (a,b): + return rat_subtract (a, b )[0] > 0 + +def rat_subtract (a,b ): + return rat_add (a, rat_neg (b)) + +def rat_to_duration (frac): + log = 1 + d = (1,1) + while rat_larger (d, frac): + d = rat_multiply (d, (1,2)) + log = log << 1 + + frac = rat_subtract (frac, d) + dots = 0 + if frac == rat_multiply (d, (1,2)): + dots = 1 + elif frac == rat_multiply (d, (3,4)): + dots = 2 + return (log, dots) + + +class Barcheck : + def __init__ (self): + pass + def dump (self): + return '|\n' + +class Beam: + def __init__ (self, ch): + self.char = ch + def dump (self): + return self.char + +class Slur: + def __init__ (self,id): + self.id = id + self.start_chord = None + self.end_chord = None + def calculate (self): + s =self.start_chord + e= self.end_chord + + if e and s: + s.note_suffix = s.note_suffix + '(' + e.note_prefix = ')' + e.note_prefix + else: + sys.stderr.write ("\nOrphaned slur") + +class Voice: + def __init__ (self): + self.entries = [] + self.chords = [] + self.staff = None + self.current_slurs = [] + self.slurs = [] + def toggle_slur (self, id): + + for s in self.current_slurs: + if s.id == id: + self.current_slurs.remove (s) + s.end_chord = self.chords[-1] + return + s = Slur (id) + s.start_chord = self.chords[-1] + self.current_slurs.append (s) + self.slurs.append (s) + + def last_chord (self): + return self.chords[-1] + def add_chord (self, ch): + self.chords.append (ch) + self.entries.append (ch) + def add_nonchord (self, nch): + self.entries.append (nch) + + def idstring (self): + return 'staff%svoice%s ' % (encodeint (self.staff.number) , encodeint(self.number)) + def dump (self): + str = '' + ln = '' + for e in self.entries: + next = ' ' + e.dump () + if next[-1] == '\n': + str = str + ln + next + ln = '' + continue + + if len (ln) +len (next) > 72: + str = str+ ln + '\n' + ln = '' + ln = ln + next + + + str = str + ln + id = self.idstring () + + str = '%s = \\notes { \n %s }\n '% (id, str) + return str + def calculate_graces (self): + lastgr = 0 + lastc = None + for c in self.chords: + if c.grace and not lastgr: + c.chord_prefix = c.chord_prefix + '\\grace { ' + elif not c.grace and lastgr: + lastc.chord_suffix = lastc.chord_suffix + ' } ' + lastgr = c.grace + lastc = c + def calculate (self): + self.calculate_graces () + for s in self.slurs: + s.calculate () + +class Clef: + def __init__ (self, cl): + self.type = cl + def dump(self): + return '\\clef %s;' % self.type + +clef_table = { + 'b':'bass' , + 'r':'baritone', + 'n':'tenor', + 'a':'alto', + 'm':'mezzosoprano', + 's':'soprano', + 't':'treble', + 'f':'frenchviolin', + } +class Staff: + def __init__ (self): + self.voices = (Voice (), Voice()) + self.clef = None + self.instrument = 0 + self.voice_idx = 0 + self.number = None + + i = 0 + for v in self.voices: + v.staff = self + v.number = i + i = i+1 + def set_clef (self, letter): + + clstr = clef_table[letter] + self.voices[0].add_nonchord (Clef (clstr)) + + def current_voice (self): + return self.voices[self.voice_idx] + def next_voice (self): + self.voice_idx = (self.voice_idx + 1)%len (self.voices) + + def calculate (self): + for v in self.voices: + v.calculate () + def idstring (self): + return 'staff%s' % encodeint (self.number) + def dump (self): + str = '' + + refs = '' + for v in self.voices: + str = str + v.dump() + refs = refs + '\\' + v.idstring ()+ ' ' + + str = str + '\n\n%s = \\context Staff = %s \n < \n %s >\n\n\n'% (self.idstring (), self.idstring (), refs) + return str + +class Tuplet: + def __init__ (self, number, base, dots): + self.chords = [] + self.number = number + self.replaces = tuplet_table[number] + self.base = base + self.dots = dots + + length = (1,base) + if dots == 1: + length = rat_multiply (length, (3,2)) + elif dots == 2: + length = rat_multiply (length, (7,4)) + + length = rat_multiply (length, (1,self.replaces)) + + (nb,nd) =rat_to_duration (length) + + self.note_base = nb + self.note_dots = nd + + def add_chord (self, ch): + ch.dots = self.note_dots + ch.basic_duration = self.note_base + self.chords.append (ch) + + if len (self.chords) == 1: + ch.chord_prefix = '\\times %d/%d { ' % (self.replaces, self.number) + elif len (self.chords) == self.number: + ch.chord_suffix = ' }' + +class Chord: + def __init__ (self): + self.pitches = [] + self.dots = 0 + self.basic_duration = 0 + self.scripts = [] + self.grace = 0 + self.chord_prefix = '' + self.chord_suffix = '' + self.note_prefix = '' + self.note_suffix = '' + + def dump (self): + str = '' + + sd = '' + if self.basic_duration == 0.5: + sd = '\\breve' + else: + sd = '%d' % self.basic_duration + sd = sd + '.' * self.dots + for p in self.pitches: + if str: + str = str + ' ' + str = str + pitch_to_lily_string (p) + sd + + for s in self.scripts: + str = str + '-' + s + + str = self.note_prefix +str + self.note_suffix + + if len (self.pitches) > 1: + str = '<%s>' % str + elif len (self.pitches) == 0: + str = 'r' + sd + + str = self.chord_prefix + str + self.chord_suffix + + return str + +SPACE=' \t\n' +DIGITS ='0123456789' +basicdur_table = { + 9: 0.5, + 0: 0 , + 2: 2 , + 4: 4 , + 8: 8 , + 1: 16, + 3: 32, + 6: 64 + } + + +ornament_table = { + 't': '\\prall', + 'm': '\\mordent', + 'x': '"x"', + '+': '+', + 'u': '"pizz"', + 'p': '|', + '(': '"paren"', + ')': '"paren"', + 'g': '"segno"', + '.': '.', + 'fd': '\\fermata', + 'f': '\\fermata', + '_': '-', + 'T': '\\trill', + '>': '>', + '^': '^', + } + +class Parser: + def __init__ (self, filename): + self.staffs = [] + self.forced_duration = None + self.last_name = 0 + self.last_oct = 0 + self.tuplets_expected = 0 + self.tuplets = [] + self.last_basic_duration = 4 + + self.parse (filename) + + def set_staffs (self, number): + self.staffs = map (lambda x: Staff (), range(0, number)) + + self.staff_idx = 0 + + i =0 + for s in self.staffs: + s.number = i + i = i+1 + def current_staff (self): + return self.staffs[self.staff_idx] + + def current_voice (self): + return self.current_staff ().current_voice () + + def next_staff (self): + self.staff_idx = (self.staff_idx + 1)% len (self.staffs) + + def parse_note (self, str): + name = None + ch = None + + grace = 0 + if str[0] == 'G': + grace = 1 + str = str[1:] + + if str[0] == 'z': + ch = self.current_voice().last_chord() + str = str[1:] + else: + ch = Chord () + self.current_voice().add_chord (ch) + if str[0] <> 'r': + name = (ord (str[0]) - ord('a') + 5) % 7 + + str = str[1:] + + ch.grace = ch.grace or grace + + forced_duration = 0 + alteration = 0 + dots = 0 + oct = None + durdigit = None + multibar = 0 + tupnumber = 0 + extra_oct = 0 + while str[0] in 'dsfmnul0123456789.,+-': + c = str[0] + str = str[1:] + if c == 'f': + alteration = alteration -1 + elif c == 'n': + alteration = 0 + elif c == 'm': + multibar = 1 + elif c == 's': + alteration = alteration +1 + elif c == 'd': + dots = dots + 1 + elif c in DIGITS and durdigit == None and \ + self.tuplets_expected == 0: + durdigit = string.atoi (c) + elif c in DIGITS: + oct = string.atoi (c) - 3 + elif c == '+': + extra_oct = extra_oct + 1 + elif c == '-': + extra_oct = extra_oct - 1 + elif c == '.': + dots = dots+ 1 + forced_duration = 2 + elif c == ',': + forced_duration = 2 + + if str[0] == 'x': + str = str[1:] + tupnumber = string.atoi (str[0]) + str = str[1:] + str=re.sub (r'^n?f?[+-0-9.]+', '' , str) + + + if durdigit: + try: + basic_duration = basicdur_table[durdigit] + self.last_basic_duration = basic_duration + except KeyError: + sys.stderr.write (""" +Huh? expected duration, found %d Left was `%s'""" % (durdigit, str[:20])) + + basic_duration = 4 + else: + basic_duration = self.last_basic_duration + + + + if name <> None and oct == None: + e = 0 + if self.last_name < name and name -self.last_name > 3: + e = -1 + elif self.last_name > name and self.last_name -name > 3: + e = 1 + + oct = self.last_oct +e + extra_oct + + if name <> None: + self.last_oct = oct + self.last_name = name + + if name <> None: + ch.pitches.append ((oct, name, alteration)) + + # do before adding to tuplet. + ch.basic_duration = basic_duration + ch.dots = dots + + if forced_duration: + self.forced_duration = ch.basic_duration / forced_duration + + if tupnumber: + tup =Tuplet (tupnumber, basic_duration, dots) + self.tuplets_expected = tupnumber + self.tuplets.append (tup) + + if self.tuplets_expected > 0: + self.tuplets[-1].add_chord (ch) + self.tuplets_expected = self.tuplets_expected - 1 + + return str + def parse_basso_continuo (self, str): + while str[0] in DIGITS +'#n-': + scr = str[0] + + if scr == '#': + scr = '\\\\textsharp' + + if len(scr)>1 or scr not in DIGITS: + scr = '"%s"' % scr + + self.current_voice().last_chord ().scripts.append (scr) + str=str[1:] + return str + def parse_beams (self,str): + c = str[0] + # self.current_voice().add_nonchord (Beam(c)) + if str[0] == '[': + str = str[1:] + while str[0] in '+-0123456789': + str=str[1:] + else: + str = str[1:] + + return str + + def clean (self, ls): + ls = map (stripcomment, ls) + ls = map (stripwhite, ls) + ls = map (stripeols, ls) + + + ls = filter (lambda x: x <> '', ls) + return ls + + def parse_header (self, ls): + + opening = ls[0] + ls = ls[1:] + + + opening = map (string.atoi, re.split ('[\t ]+', opening)) + + (no_staffs, no_instruments, timesig_num,timesig_den, ptimesig_num, + ptimesig_den, pickup_beats,keysig_number) = tuple (opening) + + opening = ls[0] + ls = ls[1:] + + # ignore this. + # opening = map (string.atoi, re.split ('[\t ]+', opening)) + # (no_pages,no_systems, musicsize, fracindent) = tuple (opening) + + instruments = [] + while len (instruments) < no_instruments: + instruments.append (ls[0]) + ls = ls[1:] + + + l = ls[0] + ls = ls[1:] + + + self.set_staffs (no_staffs) + for s in self.staffs: + s.set_clef(l[0]) + l = l[1:] + + # dump path + ls = ls[1:] + + # dump more ? + ls = ls[2:] + + return ls + + def parse_ornament (self, left): + left = left[1:] + e = self.current_voice ().last_chord () + + id = left[0] + left = left[1:] + if left[0] == 'd': + id = id +'d' + left = left [1:] + + orn = '"orn"' + try: + orn = ornament_table[id] + except KeyError: + sys.stderr.write ("unknown ornament `%s'\n" % id) + + e.scripts.append (orn) + return left + def parse_barcheck (self, left): + self.current_voice ().add_nonchord (Barcheck ()) + + return left [1:] + + def parse_slur (self, left): + left = left[1:] + + id = None + + if re.match ('[A-Z0-9]', left[0]): + id = left[0] + left= left[1:] + while left[0] in 'uld0123456789+-.': + left= left[1:] + + self.current_voice ().toggle_slur (id) + return left + + def parse_mumbo_jumbo (self,left): + left = left[1:] + while left and left[0] <> '\\': + left = left[1:] + + left = left[1:] + return left + def parsex (self,left): + left = left[1:] + while left[0] in DIGITS: + left = left[1:] + + return left + + def parse_body (self, left): + left = re.sub ('[ \t\n]+', ' ', left) + + while left: + c = left[0] + if c in 'Gzabcdefgr': + left = self.parse_note (left) + elif c in DIGITS + 'n#-': + left = self.parse_basso_continuo (left) + elif c in SPACE: + left = left[1:] + elif c == 's': + left = self.parse_slur (left) + elif c == '|': + left = self.parse_barcheck (left) + elif c == 'o': + left = self.parse_ornament (left) + elif c == 'x': + left = self.parsex (left) + elif c in "[]": + left = self.parse_beams (left) + elif left[:2] == "//": + self.current_staff().next_voice () + left = left[2:] + elif c == '/': + self.next_staff () + left = left[1:] + elif c == '\\': + left = self.parse_mumbo_jumbo(left) + else: + sys.stderr.write (""" +Huh? Unknown directive `%s', before `%s'""" % (c, left[:20] )) + left = left[1:] + + for c in self.staffs: + c.calculate () + + def dump (self): + str = '' + + refs = '' + for s in self.staffs: + str = str + s.dump () + refs = '\\' + s.idstring() + refs + + str = str + "\n\n\\score { <\n %s\n > }" % refs + return str + + + def parse (self,fn): + ls = open (fn).readlines () + ls = self.clean (ls) + ls = self.parse_header (ls) + left = string.join (ls, ' ') + self.parse_body (left) + + + + +def help (): + sys.stdout.write ( +"""Usage: pmx2ly [OPTION]... PMX-FILE + +Convert PMX to LilyPond. + +Options: + -h, --help this help + -o, --output=FILE set output filename to FILE + -v, --version version information + +PMX is a Musixtex preprocessor written by Don Simons, see +http://www.gmd.de/Misc/Music/musixtex/software/pmx/ + +Report bugs to bug-gnu-music@gnu.org. + +Written by Han-Wen Nienhuys +""") + + +def print_version (): + sys.stdout.write ("""pmx2ly (GNU LilyPond) %s + +This is free software. It is covered by the GNU General Public License, +and you are welcome to change it and/or distribute copies of it under +certain conditions. Invoke as `midi2ly --warranty' for more information. + +Copyright (c) 2000 by Han-Wen Nienhuys +""" % version) +def identify(): + sys.stderr.write ("%s from LilyPond %s\n" % (program_name, version)) + + + +(options, files) = getopt.getopt (sys.argv[1:], 'vo:h', ['help','version', 'output=']) +out_filename = None +for opt in options: + o = opt[0] + a = opt[1] + if o== '--help' or o == '-h': + help () + sys.exit (0) + if o == '--version' or o == '-v': + print_version () + sys.exit(0) + + if o == '--output' or o == '-o': + out_filename = a + else: + print o + raise getopt.error + +identify() + +for f in files: + if f == '-': + f = '' + + sys.stderr.write ('Processing `%s\'\n' % f) + e = Parser(f) + if not out_filename: + out_filename = os.path.basename (re.sub ('(?i).pmx$', '.ly', f)) + + if out_filename == f: + out_filename = os.path.basename (f + '.ly') + + sys.stderr.write ('Writing `%s\'' % out_filename) + ly = e.dump() + + + + fo = open (out_filename, 'w') + fo.write ('%% lily was here -- automatically converted by pmx2ly from %s\n' % f) + fo.write(ly) + fo.close () + + -- 2.39.2