Ties need to add a notation element, too
[pae2xml.git] / pae2xml.pl
blob6345108bb164dd58f12867d9341d501f756dcadf
1 #!/usr/bin/perl
4 # Copyright (C) 2003 Rainer Typke
5 # Copyright (C) 2010 Reinhold Kainhofer <reinhold@kainhofer.com>
6 #pae2xml is licensed under the terms of the GNU General Public License Version
7 #2 as published by the <a href="http://www.fsf.org/" target="_top">Free Software Foundation</a>.
8 #This gives you legal permission to copy, distribute and/or modify <em>pae2xml</em> under
9 #certain conditions. Read
10 #the <a href="http://www.gnu.org/copyleft/gpl.html" target="_top">online version of the license</a>
11 #for more details. pae2xml is provided AS IS with NO WARRANTY OF ANY KIND,
12 #INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
14 our $script = "pae2xml.pl";
15 our $version = "1.0";
17 ### Handle command-line options
19 # import module
20 use Getopt::Long;
21 use File::Basename;
23 our $format = 'rism';
25 sub print_help {
26 print 'pae2xml.pl [OPTIONS...] FILE
28 Converts the Plaine & Easie file FILE to MusicXML. If FILE is -, STDIN is used.
30 Possible options:
31 -h, --help Prints out this help message
32 --format=FORMAT, -f FORMAT
33 Interprets input file as FORMAT. Possible values are
34 rism, pae (default: rism)
35 -v, -- version Prints out version information
38 sub print_version {
39 print "$script $version
40 Copyright (C) 2003 Rainer Typke
41 Copyright (C) 2010 Reinhold Kainhofer <reinhold\@kainhofer.com>
44 sub handle_options {
45 my $print_help = 0;
46 my $print_version = 0;
47 my $result = GetOptions ("f|format=s" => \$format, "help|h" => \$print_help, "version|v" => \$print_version);
48 if ($print_version) {
49 print_version ();
50 exit 0;
52 if ($print_help || (@ARGV == 0)) {
53 print_help ();
54 exit 0;
57 handle_options ();
61 $divisions = 960;
62 $old_duration = $divisions;
63 $old_octave = 4;
65 ($mday, $mon, $year) = (localtime()) [3..5];
66 $encoding_date = sprintf("%4d-%02d-%02d", $year + 1900, $mon+1, $mday);
68 $TIE = 0;
70 foreach $a (@ARGV) {
71 $p = read_file($a);
72 $toprint = "";
73 if ($format eq "rism") {
74 $p =~ s/\s*\=\=+\s*(.*?)\s*\=\=+\s*/$1/sg;
75 $p =~ s/\s*included.*?-------------*\s*(.*?)\s*/$1/s;
77 ($q, $r) = ($p, $p);
78 if ($q !~ /^.*1\.1\.1.*$/gsx && $r =~ /^.*plain.*$/gsx) {
79 print_error("$a contains 'plain', but not 1.1.1!\n");
80 } else {
81 if ($p =~ /^\s*([^\n]+)\n(.*?)\n((\d+\.\d+\.\d.*?plain.*?\n)+)(.*?)\n?([^\n]+)\n([^\n]+)\s*$/gs) {
82 my ($comp, $title, $incipits, $sonst, $libsig, $rismsig) = ($1, $2, $3, $5, $6, $7);
84 $toprint .= "
85 COMPOSER: $comp
86 TITLE: $title
87 INCIPIT(S): $incipits
88 OTHER INFO: $sonst
89 LIB. SIGN.: $libsig
90 RISM SIGN.: $rismsig\n\n";
91 parse_rism_incipits($incipits, $comp, $title, $sonst, $libsig, $rismsig);
93 else {
94 if (index($p,"plain&easy") > -1) {
95 print_error("Ignoring the following text:\n\n\n$p\n\n\n");
99 } else {
100 # Just a plaine & easie snippet, without any further RISM fields
101 if ($a eq "-") {
102 $filename = "out.xml";
103 } else {
104 $filename = basename ($a, ".pae") . ".xml";
106 parse_pe ($filename, $p, "", "", "", "", "", "", "");
111 ##############################################################################
112 ### RISM file parsing
113 ##############################################################################
115 sub parse_rism_incipits {
116 my ($incipits, $comp, $title, $sonst, $libsig, $rismsig) = @_;
118 $toprint .= "parsing: $incipits\n";
120 while ($incipits =~ /^(\d+\.\d+\..+?)(\d+\.\d+\..*)$/gs) {
121 my ($inc1) = $1;
122 $incipits = $2;
123 parse_rism_incipit($inc1, $comp, $title, $sonst, $libsig, $rismsig);
125 parse_rism_incipit($incipits, $comp, $title, $sonst, $libsig, $rismsig);
128 sub parse_rism_incipit {
129 my ($pe, $comp, $title, $sonst, $libsig, $rismsig) = @_;
131 if ($pe =~ /^\s*(\d+\.\d+\.\d)(\.|:)\s*(.*?)\nplain&easy:\s*(.*)$/gs) {
132 my ($inr, $instr, $pecode) = ($1, $3, $4);
134 my $filename="$rismsig-$inr.xml";
135 $filename =~ s/RISM\s*A\/II\s*:?\s*//gs;
137 foreach $_ ($rismsig,$title,$inr,$instr,$comp,$libsig,$sonst)
142 $toprint .= "
143 INCIPIT NO.: $inr
144 INSTR.: $instr\n";
145 parse_pe ($filename, $pecode, $inr, $instr, $comp, $title, $sonst, $libsig, $rismsig);
147 } else {
148 print_error("could not parse $pe\n");
153 ##############################################################################
154 ### pure Plaine & Easie data parsing
155 ##############################################################################
157 sub parse_pe {
158 my ($filename, $pe, $inr, $instr, $comp, $title, $sonst, $libsig, $rismsig) = @_;
160 $pe =~ s/@ü/@0ü/gs; # make missing time signature explicit
161 while ($pe =~ s/([^\-])(\d+)(\'|\,)(A|B|C|D|E|F|G)/$1$3$2$4/gs) {}; # octave first, then duration. Truly global.
163 if ($pe =~ /\s*(%([\w\-\+\d]+))?(@([\d\w\/]+))?\s*&?\s*(\$([^ü]+))(.*)$/gs) {
164 my ($clef, $timesig, $keysig, $rest) = ($2, $4, $6, $7);
166 print "Writing $filename...\n";
167 open(OUT, ">$filename");
170 print OUT '<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
171 <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
172 <score-partwise>
174 print OUT " <work>\n" if ($rismsig || $title);
175 print OUT " <work-number>$rismsig</work-number>\n" if ($rismsig);
176 print OUT " <work-title>$title</work-title>\n" if ($title);
177 print OUT " </work>\n" if ($rismsig || $title);
178 print OUT " <movement-number>$inr</movement-number>\n" if ($inr);
179 print OUT " <movement-title>$instr</movement-title>\n" if ($instr);
180 print OUT " <identification>\n";
181 print OUT " <creator type=\"composer\">$comp</creator>\n" if ($comp);
182 print OUT ' <encoding>
183 <software>pae2xml by R. Typke</software>
184 <encoding-date>'.$encoding_date.'</encoding-date>
185 </encoding>
187 print OUT " <source>$libsig</source>\n" if ($libsig);
188 print OUT ' </identification>
189 <part-list>
190 <score-part id="P1">
191 <part-name>'.$sonst.'</part-name>
192 </score-part>
193 </part-list>
194 <part id="P1">
195 <measure number="1">
196 <attributes>
197 <divisions>'.$divisions.'</divisions>
198 '.keysignature ($keysig)
199 .timesignature ($timesig)
200 .clef ($clef)
201 .' </attributes>
205 $toprint .= "
206 CLEF: $clef
207 KEY SIG.: $keysig
208 TIME SIG.: $timesig
209 REST: $rest\n";
210 parse_notes($rest, $keysig);
212 else { print_error("could not parse $pe\n"); }
213 print OUT " </part>
214 </score-partwise>\n";
215 close OUT;
218 # Repeat $1 by a count of $2
219 sub repeat {
220 (my $e, my $count) = @_;
221 my $res = "";
222 for (my $i=1; $i <= $count; ++$i)
224 $res .= $e;
226 return $res;
229 sub parse_notes {
230 my ($notes, $keysig) = @_;
231 my $qq = 0; # in group of cue notes
233 my $meas = 2; # measure number
234 my $mopen = 1; # measure tag still open
236 if ($notes =~ /^\s*(.*?)\s*$/) {
237 $notes = $1;
240 $notes =~ s/!([^!]*)!(f*)/repeat($1, length($2)+1)/gse; # write out repetitions
241 $notes =~ s/\{([^\}]*)\}/$1/gs; # ignore beamings
242 while ( $notes =~ s/(:?\/+:?|^)([^\/:]*)(:?\/+:?)i(:?\/+:?)/$1$2$3$2$4/gs) {}; # replace whole-measure repeats (i notation)
244 $notes =~ s/(\d+)\(([^;]+\))/\($1$2/gs; # pull note lengths into fermatas or triplets
245 $notes =~ s/(xx|x|bb|b|n)\(/\($1/gs; # pull accidentals into tuplets or fermatas:
246 $notes =~ s/(\d+)(xx|x|bb|b|n)(A|B|C|D|E|F|G)/$2$1$3/gs; # accidentals first, then duration
248 # $notes =~ s/x\(/\(x/gs; # pull accidentals into tuplets or fermatas
249 # $notes =~ s/bb\(/\(bb/gs; # pull accidentals into tuplets or fermatas
250 # $notes =~ s/b\(/\(b/gs; # pull accidentals into tuplets or fermatas
251 # $notes =~ s/n\(/\(n/gs; # pull accidentals into tuplets or fermatas
252 # $notes =~ s/(\'+|\,+)\(/\($1/g; # pull octave marks into tuplets or fermatas
254 $notes =~ s/(\.|\d|\,|\')qq/qq$1/gs; # pull beginning mark of group of grace notes in front of corresponding notes
255 $notes =~ s/(xx|x|bb|b|n)qq/qq$1/gs; # qq first, then parts of notes
257 while ($notes ne "") {
258 if ($notes =~ /^(\'+|\,+)(.*)$/) { # Octave marks
259 ($oct, $notes) = ($1, $2);
260 octave($oct);
261 } elsif ($notes =~ /^qq(.*)$/) { # Begin grace
262 $notes = $1;
263 $qq = 1;
264 } elsif ($notes =~ /^r(.*)$/) { # End grace
265 $notes = $1;
266 $qq = 0;
267 } elsif ($notes =~ /^\=(\d*)(\/.*)$/) { # multi-measure rests
268 $measrest = $1;
269 $notes = $2;
270 if ($measrest eq '') {
271 $measrest = 1;
273 $toprint .= "$measrest measures of rest.\n";
274 if ($measrest > 0) {
275 # Create a real multi-bar rest
276 print OUT ' <attributes>
277 <measure-style>
278 <multiple-rest>'.$measrest.'</multiple-rest>
279 </measure-style>
280 </attributes>
283 # Now create the measures
284 for $n (1..$measrest) {
285 print OUT ' <note>
286 <rest />
287 <duration>'.($beats*$divisions*4/$beattype).'</duration>
288 </note>
290 if ($n < $measrest) {
291 print OUT " </measure>\n";
292 if ($notes ne "") {
293 print OUT ' <measure number="'.$meas.'">
295 $meas++;
296 } else {
297 $mopen = 0;
301 } elsif ($notes =~ /^((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)(.*)$/) { # a note
302 ($note, $notes) = ($1,$6);
303 parse_note($note, $keysig, "", "", $qq);
304 } elsif ($notes =~ /^(\((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?\))(.*)$/) { # one note with a fermata
305 ($note, $notes) = ($1,$6);
306 parse_note($note, $keysig, "", "", $qq);
307 } elsif ($notes =~ /^(\(((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?){3}\))(.*)$/) { # a triplet
308 ($triplet, $notes) = ($1,$7);
309 # print "TRIPLET: ".$triplet." -> ";
310 $triplet =~ /^\(((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)(.*)\)$/gs;
311 ($note, $triplet) = ($1,$6);
312 #print "$note $triplet\n";
313 parse_note($note, $keysig, '<tuplet type="start"/>', ' <time-modification>
314 <actual-notes>3</actual-notes>
315 <normal-notes>2</normal-notes>
316 </time-modification>', $qq);
317 $triplet =~ /^((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)(.*)$/gs;
318 ($note, $triplet) = ($1,$6);
319 #print "$note $triplet\n";
320 parse_note($note, $keysig, '', ' <time-modification>
321 <actual-notes>3</actual-notes>
322 <normal-notes>2</normal-notes>
323 </time-modification>', $qq);
324 parse_note($triplet, $keysig, '<tuplet type="stop"/>', ' <time-modification>
325 <actual-notes>3</actual-notes>
326 <normal-notes>2</normal-notes>
327 </time-modification>', $qq);
328 } elsif ($notes =~ /^((\d+)\(((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)+\;(\d+)\))(.*)$/) { # an n-tuplet
329 ($tuplet, $notes) = ($1,$9);
330 # print "N-TUPLET: ".$tuplet." -> ";
331 $tuplet =~ /^(\d+)\(((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)(.*);(\d)\)$/gs;
332 ($combdur, $note, $tuplet, $numval) = ($1,$2,$7,$8);
333 #print "i=$combdur, n=$numval; $note / $tuplet\n";
334 my $ind_dur = duration($combdur)/$numval;
335 # my $norm_notes =
336 my $act_notes = $numval;
337 parse_note($note, $keysig, '<tuplet type="start"/>', ' <time-modification>
338 <actual-notes>'.$act_notes.'</actual-notes>
339 <normal-notes>1</normal-notes>
340 </time-modification>', $qq);
341 while ($tuplet =~ /^((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)(.+)$/gs) {
342 ($note, $tuplet) = ($1,$6);
343 #print "$note / $tuplet\n";
344 parse_note($note, $keysig, '', ' <time-modification>
345 <actual-notes>'.$act_notes.'</actual-notes>
346 <normal-notes>1</normal-notes>
347 </time-modification>', $qq);
349 parse_note($tuplet, $keysig, '<tuplet type="stop"/>', ' <time-modification>
350 <actual-notes>'.$act_notes.'</actual-notes>
351 <normal-notes>1</normal-notes>
352 </time-modification>', $qq);
353 } elsif ($notes =~ /^(%(\w(-|\+)\d))(.*)$/) { # Clef change
354 ($clef,$notes) = ($2,$4);
355 print OUT " <attributes>\n";
356 print OUT clef ($clef);
357 print OUT " </attributes>\n";
358 } elsif ($notes =~ /^@(\d\/\d|c\/?)\s*(.*)$/) { # time signatue change
359 ($timesig,$notes) = ($1,$2);
360 print OUT " <attributes>\n";
361 print OUT timesignature($timesig);
362 print OUT " </attributes>\n";
363 } elsif ($notes =~ /^\$((b|x)[ABCDEFG]*)\s*(.*)$/) { # key signature change
364 ($keysig, $notes) = ($1, $3);
365 print OUT " <attributes>\n";
366 print OUT keysignature ($keysig);
367 print OUT " </attributes>\n";
368 } elsif ($notes =~ /^(:?\/+:?)(.*)$/) { # Barline (and repeats)
369 $barline = $1;
370 $notes = $2;
371 if ($barline =~ /^:\/\/:/) {
372 print OUT ' <barline location="right">
373 <bar-style>light-light</bar-style>
374 <repeat direction="backward"/>
375 </barline>
377 } elsif ($barline =~ /^:\/\/$/ ) {
378 print OUT ' <barline location="right">
379 <bar-style>light-heavy</bar-style>
380 <repeat direction="backward"/>
381 </barline>
383 } elsif ($barline =~ /^\/\/$/) {
384 print OUT ' <barline location="right">
385 <bar-style>light-light</bar-style>
386 <repeat direction="backward"/>
387 </barline>
390 print OUT " </measure>\n";
391 if ($notes ne "") {
392 print OUT ' <measure number="'.$meas.'">
394 if ($barline =~ /^\/\/:$/) {
395 print OUT ' <barline location="left">
396 <bar-style>heavy-light</bar-style>
397 <repeat direction="forward"/>
398 </barline>
400 } elsif ($barline =~ /^:\/\/:$/) {
401 print OUT ' <barline location="left">
402 <repeat direction="forward"/>
403 </barline>
406 print OUT $clefattr;
407 $meas++;
408 } else {
409 $mopen = 0;
411 $toprint .= "bar line\n";
412 } #elsif ($notes =~ /^(\d*\.*\-)(.*)$/) {
413 #($rst, $notes) = ($1, $2);
414 #$toprint .= "rest: $rst\n";
415 #$rst =~ /^(\d*)(\.*)\-$/;
416 #($rst, $dots) =($1,$2);
417 #print OUT ' <note>
418 # <rest />
419 # <duration>'.duration($rst, $dots).'</duration>
420 #'.# <type>quarter</type>
422 # </note>
424 elsif ($notes =~ /^\((\=)\)(.*)$/) { # a bar of rest with a fermata
425 ($rst, $notes) = ($1, $2);
426 $toprint .= "rest: $rst\n";
427 print OUT ' <note>
428 <rest />
429 <duration>'.($beats*$divisions*4/$beattype).'</duration>
430 <notations>
431 <fermata type="upright"/>
432 </notations>
433 </note>
436 elsif ($notes =~ s/(\d+\.*)\(((\,|\')*(x|xx|b|bb|n)?\d*\.*(g|q)?(\-|A|B|C|D|E|F|G)t?\+?)\)/\($1$2\)/gs) { # pull duration into fermata parentheses
437 # print "after replacement: $notes\n"; exit;
439 elsif ($notes =~ /^ +(.*)$/) {
440 $notes = $1;
441 print("Invalid space encountered in notes before $notes\n");
443 else {
444 print_error("got stuck with $notes\n");
445 $notes = "";
448 if ($mopen) {
449 print OUT " </measure>\n";
454 sub parse_note {
455 my($note, $keysig, $notation, $addition, $in_qq_group) = @_;
457 my ($fermata) = (0);
458 my ($actualnotes, $normalnotes) = (1,1);
460 if ($addition =~ /^\s*<time-modification>\s*<actual-notes>\s*(\d+)\s*<\/actual-notes>\s*<normal-notes>\s*(\d+)\s*<\/normal-notes>\s*<\/time-modification>\s*$/) {
461 ($actualnotes, $normalnotes) = ($1, $2);
464 if ($note =~ /^\((.*)\)$/) {
465 $note = $1;
466 $fermata = 1;
469 $note =~ /^((\,|\')*)(x|xx|b|bb|n)?(\d*)(\.*)(g|q)?(\-|A|B|C|D|E|F|G)(t?)(\+?)$/;
470 my ($oct, $acc, $dur, $dot, $gracecue, $pitch, $trill, $tie) = ($1, $3, $4, $5, $6, $7, $8, $9);
472 print OUT ' <note>
474 if ($gracecue eq "g") {
475 print OUT ' <grace steal-time-following="33"/>
478 if ($gracecue eq "q" || $in_qq_group) {
479 print OUT ' <cue/>
482 if ($pitch eq "-") {
483 print OUT " <rest />\n";
484 } else {
485 print OUT ' <pitch>
486 <step>'.$pitch.'</step>
487 '.alter($pitch, $acc, $keysig)
488 .' <octave>'.octave($oct).'</octave>
489 </pitch>
492 if ($gracecue ne "g") {
493 print OUT ' <duration>'.(duration($dur, $dot)*$normalnotes/$actualnotes).'</duration>
497 $tienotation = "";
498 if ($tie eq "+") {
499 if ($TIE) {
500 $tienotation = " <tied type=\"stop\"/>\n";
502 $tienotation .= " <tied type=\"start\"/>\n";
503 if (!$TIE) {
504 $TIE = 1;
505 print OUT ' <tie type="start"/>
508 } else {
509 if ($TIE) {
510 print OUT ' <tie type="stop"/>
512 $tienotation = " <tied type=\"stop\"/>\n";
513 $TIE = 0;
517 print OUT $addition;
519 my $notationbracket = $fermata || $tienotation || ($trill eq "t") || ($notation ne "");
520 if ($notationbracket) {
521 print OUT " <notations>\n";
523 if ($tienotation) {
524 print OUT $tienotation;
526 if ($fermata) {
527 print OUT '
528 <fermata type="upright"/>'."\n"; }
529 if ($trill eq "t") {
530 print OUT ' <ornaments>
531 <trill-mark/>
532 </ornaments>
535 if ($notation ne "") {
536 print OUT " $notation\n";
538 if ($notationbracket) {
539 print OUT " </notations>\n";
542 print OUT ' </note>
545 $toprint .= "note: oct. $oct/acc. $acc/dur. $dur/dots $dot/grace,cue $gracecue/pitch $pitch\n";
548 sub alter {
549 my ($pitch, $acc, $keysig) = @_;
551 my $alt = 0;
553 if (index ($keysig,$pitch) > -1) {
554 $keysig =~ /^(.).*$/gs;
555 if ($1 eq 'x') {
556 $alt = 1;
557 } else {$alt = -1;}
560 my %acc_alt = ("n", 0, "b", -1, "bb", -2, "x", 1, "xx", 2);
561 if ($acc_alt{$acc} ne "") {
562 $alt = $acc_alt{$acc};
565 if ($alt != 0) {
566 return "\t\t\t\t\t<alter>$alt</alter>\n";
568 return "";
571 sub duration {
572 my ($duration, $dots) = @_;
574 if ($duration.$dots ne "") {
575 my %du=("1",4*$divisions,"2",2*$divisions,"4",$divisions,
576 "8",$divisions/2,"6",$divisions/4,"3",$divisions/8,
577 "5",$divisions/16,"7",$divisions/32,
578 "9",$divisions*8,"0",$divisions*16); # breve/long
579 $old_duration = $du{$duration};
580 if ($old_duration eq "") {
581 print_error("strange duration '$duration'\n");
583 my $add = $old_duration;
584 while ($dots ne "") {
585 $add /= 2;
586 $old_duration += $add;
587 $dots =~ /^.(.*)$/gs;
588 $dots = $1;
591 return $old_duration;
594 sub octave {
595 my ($octave) = @_;
597 if ($octave ne "") {
598 $octave =~ /^(.)(.*)$/gs;
599 if ($1 eq ",") {
600 $old_octave = 4 - length $octave;
601 } else {
602 $old_octave = 3 + length $octave;
605 return $old_octave;
608 sub clef {
609 my ($clef) = @_;
610 my $clefoctave = '';
611 if ($clef =~ /^(\w)(\-|\+)(\d)$/) {
612 ($clefsign, $clefline) = ($1, $3);
613 if ($2 =~ /^\+$/) {
614 print "Warning: Mensural clefs are not supported by MusicXML, using modern clef (input: $clef)\n";
616 if ($clefsign eq 'g') {
617 $clefsign = "G";
618 $clefoctave = " <clef-octave-change>-1</clef-octave-change>\n";
620 } else {
621 ($clefsign, $clefline) = ("G", 2);
623 return ' <clef>
624 <sign>'.$clefsign.'</sign>
625 <line>'.$clefline.'</line>
626 '.$clefoctave.' </clef>
630 sub keysignature {
631 my ($keysig) = @_;
633 # TODO: How is a change to C major represented? by "$ " or "$x " or "$b "?
634 # At the beginning, the $ part is left out, but mid-piece key changes
635 # need to way to clear all accidentals! We accept all three cases above!
636 my %fif=("", 0, "x", 0, "b", 0, "xF", 1, "xFC", 2, "xFCG",3, "xFCGD",4, "xFCGDA",5, "xFCGDAE",6, "xFCGDAEB",7, "bB",-1, "bBE",-2, "bBEA",-3, "bBEAD",-4, "bBEADG",-5, "bBEADGC",-6, "bBEADGCF",-7);
637 $keysig =~ s/(\s+)|&//gs; # it is unclear what the & means, so we'll ignore it for now.
638 $keysig =~ s/\[|\]//gs; # IGNORING brackets around a key sig.
639 $fifths = $fif{$keysig};
640 if ($fifths eq "") {
641 $fifths = "0";
642 print_error("Strange key signature '$keysig'.\n");
644 return ' <key>
645 <fifths>'.$fifths.'</fifths>
646 </key>
651 sub timesignature {
652 my ($timesig) = @_;
654 if ($timesig eq "c3") {
655 $timesig = "3/2"; # it would be better to display it as "C". Example: 451.023.814
657 if ($timesig =~ /^c(\d+)\/(\d+)$/gs) {
658 $timesig = "$1/$2"; # it would be better to show the "C"
661 if ($timesig eq "0" || $timesig eq "") { # unclear how to handle absence of time signature.
662 $timesig =' <time symbol="common">
663 <beats>4</beats>
664 <beat-type>4</beat-type>
665 </time>
666 '; # using 4/4 for now.
667 ($beats, $beattype) = (4,4);
668 } elsif ($timesig =~ /^c(\/?)$/gi) {
669 if ($1 eq "/") {
670 $timesig = ' <time symbol="cut">
671 <beats>2</beats>
672 <beat-type>2</beat-type>
673 </time>
675 ($beats, $beattype) = (2,2);
676 } else {
677 $timesig = ' <time symbol="common">
678 <beats>4</beats>
679 <beat-type>4</beat-type>
680 </time>
682 ($beats, $beattype) = (4,4);
684 } elsif ($timesig =~ /^(\d+)\/(\d+)$/gs) {
685 ($beats, $beattype) = ($1, $2);
686 $timesig = ' <time>
687 <beats>'.$beats.'</beats>
688 <beat-type>'.$beattype.'</beat-type>
689 </time>
691 } else {
692 print_error("Time signature '$timesig' looks strange.\n");
693 # $timesig = ""; we assume 4/4 just to get something legible:
694 ($beats, $beattype) = (4,4);
695 $timesig = ' <time>
696 <beats>'.$beats.'</beats>
697 <beat-type>'.$beattype.'</beat-type>
698 </time>
701 return $timesig;
704 sub print_error {
705 my ($msg) = @_;
707 print "\nAn error occurred; context:\n\n$toprint\n
708 Error: $msg\n";
711 sub read_file {
712 my ($fn) = @_;
713 my $res = "";
714 if ($fn eq "-") {
715 while (<STDIN>) { $res .= $_; } # read all lines
716 } else {
717 if (!(open FH, $fn)) {
718 return "";
720 while (<FH>) { $res .= $_; } # read all lines
721 close (FH);
723 return $res;