3 # A program for processing Musescore XML files and halving the times of all the notes
4 # together with anything else that may be relevant (eg Time Sig, rests, trailing
5 # '_' after lyrics etc).
7 # Having written this and seen that there isn't really any state preserved from
8 # from one XML clause to another, it could all be done in an XSLT stylesheet. But I've
11 # Copyright (c) Dirk Koopman 2016
21 our %half = ( # decode from one note length to its half
38 our %yesno = ( qw(yes 1 no 0) ); # used for turning translating yes/no text values
41 our $dbg = 1; # show debugging
42 our $removebeam = 1; # if set remove any BeamMode clauses
46 binmode STDOUT, "utf8";
48 foreach my $fn (@ARGV) {
49 my ($name, $path, $suffix) = fileparse($fn, qr/\.[^.]*/);
51 if ($suffix eq ".mscx") {
53 $ofn = $path . $name . "-halved" . $suffix;
67 my $of = IO::File->new(">$ofn") or die "Cannot open $ofn $!\n";
68 my $p = XML::LibXML->new();
69 my $doc = $p->load_xml(location=>$ifn);
71 foreach my $staff ($doc->findnodes('/museScore/Score/Staff')) {
72 my ($sigN, $sigD); # current time sig values (may be needed later)
73 my $syllabic = 0; # track syllabic mode (whether we are in the middle of a word in lyrics).
74 display($staff) if $dbg;
75 foreach my $measure ($staff->findnodes('./Measure')) {
78 # obtain the measure no and any len attr. Change the len attribute
79 my ($l) = $measure->findnodes('./@len');
81 my ($t,$b) = split m{/}, $l->to_literal;
87 foreach my $node ($measure->findnodes('./*')) {
88 if ($node->nodeType == XML_ELEMENT_NODE) {
89 my $name = $node->nodeName;
90 if ($name eq 'Rest') {
91 my ($dt) = $node->findnodes('./durationType');
93 my $type = $dt->to_literal;
94 if ($type eq 'measure') {
95 my ($nz) = $node->findnodes('./duration/@z');
96 my ($nn) = $node->findnodes('./duration/@n');
97 my $was = $nn->to_literal;
99 my $z = $nz->to_literal;
100 display($staff, $measure, $node, "$type $z/$was -> $z/$now") if $dbg;
103 display($staff, $measure, $node, "$type -> $half{$type}") if $dbg;
104 $dt->firstChild->setData($half{$type});
107 } elsif ($name eq 'Chord') {
108 my ($dt) = $node->findnodes('./durationType');
110 my $type = $dt->to_literal;
111 display($staff, $measure, $node, "type $type -> $half{$type}") if $dbg;
112 $dt->firstChild->setData($half{$type});
114 my ($bm) = $node->findnodes('./BeamMode');
116 my $v = $bm->to_literal;
118 display($staff, $measure, $node, "remove BeamMode '$v'") if $dbg;
119 $node->removeChild($bm);
122 my ($lyrics) = $node->findnodes('./Lyrics');
124 my ($ticks) = $lyrics->findnodes('./ticks');
126 my $v = $ticks->to_literal;
128 display($staff, $measure, $node, $lyrics, "ticks $v -> $newv") if $dbg;
129 $ticks->firstChild->setData($newv);
132 # determine where we are in a word and if there is a <syllabic>
133 # clause, note its value (which is "in word" or "not in word")
135 # This is for dealing with musicxml imports where there is no
136 # explicit detection of trailing '-' signs, if there are such signs and
137 # there is no <syllabic> clause, add one of the correct sort and remove
138 # any trailing '-' from the text.
140 # Sadly, it's too much hard work to deal with any trailing '_' 'cos
141 # mscore calulates the distance in advance because they appear
142 # to be too lazy to have another <syllabic> state to deal with
143 # it. Manual edit will therefore be required. Hopefully, not
145 my ($syl) = $lyrics->findnodes('./syllabic');
147 my $v = $syl->to_literal;
148 if ($v eq 'begin' || $v eq 'middle') {
149 display($staff, $measure, $node, $lyrics, "syllabic $v = $syllabic -> 1") if $dbg;
151 } elsif ($v eq 'end') {
152 display($staff, $measure, $node, $lyrics, "syllabic $v = $syllabic -> 0") if $dbg;
156 my ($text) = $lyrics->findnodes('text/text()');
158 my $v = $text->to_literal;
163 $newv = 'begin' unless $syllabic;
164 $newv = 'middle' if $syllabic;
166 $newtext =~ s/[-–]+$//;
168 $newv = 'end' if $syllabic;
172 display($staff, $measure, $node, $lyrics, "text '$v' -> '$newtext' create syllabic $newv sylstate $syllabic -> $newstate") if $dbg;
173 $syllabic = $newstate;
174 $text->setData($newtext) if $v ne $newtext;
175 my $newsyl = $doc->createElement('syllabic');
176 $newsyl->appendText($newv);
177 $lyrics->appendChild($newsyl);
182 } elsif ($name eq 'TimeSig') {
183 my ($sN) = $node->findnodes('./sigN');
184 my ($sD) = $node->findnodes('./sigD');
186 my $sn = $sN->to_literal;
187 my $sd = $sD->to_literal;
189 display($staff, $measure, $node, "$sn/$sd -> $sn/$newsd") if $dbg;
192 $sD->firstChild->setData($newsd);
200 print $of $doc->toString($doc);
208 foreach my $node (@_) {
209 if ((ref $node) =~ /XML/ && $node->nodeType == XML_ELEMENT_NODE) {
210 $s .= $node->nodeName . " ";
211 my @attr = $node->findnodes('@*');
213 $s .= $_->nodeName . " ";
214 $s .= $_->to_literal . " ";
228 say "$0: usage <filename.mscx> ...";