X-Git-Url: http://dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=mscore-halve;fp=mscore-halve;h=4d92702e1058eccea5761c8d7ef17bfc93deb4e3;hb=e45fffbae918fe0de13ea71124e7cb38352ab987;hp=0000000000000000000000000000000000000000;hpb=a776850b70ca4aca4b64bdcb92653aa884fe938c;p=music.git diff --git a/mscore-halve b/mscore-halve new file mode 100755 index 0000000..4d92702 --- /dev/null +++ b/mscore-halve @@ -0,0 +1,217 @@ +#!/usr/bin/perl +# +# A program for processing Musescore XML files and halving the times of all the notes +# together with anything else that may be relevant (eg Time Sig, rests, trailing +# '_' after lyrics etc). +# +# Having written this and seen that there isn't really any state preserved from +# from one XML clause to another, it could all be done in an XSLT stylesheet. But I've +# written it now. +# +# Copyright (c) Dirk Koopman 2016 +# + +use strict; +use XML::LibXML; +use File::Basename; +use IO::File; + +use v5.10; + +our %half = ( # decode from one note length to its half + # there may be mispellings, as I can't be bothered + # to look at the code, as I use this for early music + + qw( + long breve + breve whole + whole half + half quarter + quarter eighth + eighth sixteenth + sixteenth thirtysecond + thirtysecond sixtyfourth + ) + ); +our %yesno = ( qw(yes 1 no 0) ); # used for turning translating yes/no text values + + +our $dbg = 1; # show debugging +our $removebeam = 1; # if set remove any BeamMode clauses + +usage() unless @ARGV; + +foreach my $fn (@ARGV) { + my ($name, $path, $suffix) = fileparse($fn, qr/\.[^.]*/); + my ($ifn, $ofn); + if ($suffix eq ".mscx") { + $ifn = $fn; + $ofn = $path . $name . "-halved" . $suffix; + } else { + usage(); + } + + process($ifn, $ofn); +} + +exit 0; + +sub process +{ + my ($ifn, $ofn) = @_; + + my $of = IO::File->new(">$ofn") or die "Cannot open $ofn $!\n"; + my $p = XML::LibXML->new(); + my $doc = $p->load_xml(location=>$ifn); + + foreach my $staff ($doc->findnodes('/museScore/Score/Staff')) { + my ($sigN, $sigD); # current time sig values (may be needed later) + my $syllabic = 0; # track syllabic mode (whether we are in the middle of a word in lyrics). + display($staff) if $dbg; + foreach my $measure ($staff->findnodes('./Measure')) { + + # process nodes + foreach my $node ($measure->findnodes('./*')) { + if ($node->nodeType == XML_ELEMENT_NODE) { + my $name = $node->nodeName; + if ($name eq 'Rest') { + my ($dt) = $node->findnodes('./durationType'); + if ($dt) { + my $type = $dt->to_literal; + if ($type eq 'measure') { + my ($nz) = $node->findnodes('./duration/@z'); + my ($nn) = $node->findnodes('./duration/@n'); + my $was = $nn->to_literal; + my $now = $sigD || $was * 2; + my $z = $nz->to_literal; + display($staff, $measure, $node, "$type $z/$was -> $z/$now") if $dbg; + $nn->setValue($now); + } else { + display($staff, $measure, $node, "$type -> $half{$type}") if $dbg; + $dt->firstChild->setData($half{$type}); + } + } + } elsif ($name eq 'Chord') { + my ($dt) = $node->findnodes('./durationType'); + if ($dt) { + my $type = $dt->to_literal; + display($staff, $measure, $node, "type $type -> $half{$type}") if $dbg; + $dt->firstChild->setData($half{$type}); + } + my ($bm) = $node->findnodes('./BeamMode'); + if ($bm) { + my $v = $bm->to_literal; + if ($removebeam) { + display($staff, $measure, $node, "remove BeamMode '$v'") if $dbg; + $node->removeChild($bm); + } + } + my ($lyrics) = $node->findnodes('./Lyrics'); + if ($lyrics) { + my ($ticks) = $lyrics->findnodes('./ticks'); + if ($ticks) { + my $v = $ticks->to_literal; + my $newv = $v / 2; + display($staff, $measure, $node, $lyrics, "ticks $v -> $newv") if $dbg; + $ticks->firstChild->setData($newv); + } + + # determine where we are in a word and if there is a + # clause, and it is necessary, add an appropriate one + # + # This is for dealing with musicxml imports where there is no + # explicit detection of trailing '-' signs, if there are and + # there is no add one of the correct sort and remove + # any trailing '-' from the text. + # + # Sadly, it's too much hard work to deal with trailing '_' 'cos + # mscore calulates the distance in advance because they appear + # to be too lazy to have another state to deal with + # it. Manual edit will therefore be required. Hopefully, not + # too often. + my ($syl) = $lyrics->findnodes('./syllabic'); + if ($syl) { + my $v = $syl->to_literal; + if ($v eq 'begin' || $v eq 'middle') { + display($staff, $measure, $node, $lyrics, "syllabic $v = $syllabic -> 1") if $dbg; + $syllabic = 1; + } elsif ($v eq 'end') { + display($staff, $measure, $node, $lyrics, "syllabic $v = $syllabic -> 0") if $dbg; + $syllabic = 0; + } + } else { + my ($text) = $lyrics->findnodes('text/text()'); + if ($text) { + my $v = $text->to_literal; + my $newv; + my $newstate; + my $newtext = $v; + if ($v =~ /-$/) { + $newv = 'begin' unless $syllabic; + $newv = 'middle' if $syllabic; + $newstate = 1; + $newtext =~ s/\-+$//; + } else { + $newv = 'end' if $syllabic; + $newstate = 0; + } + if ($newv) { + display($staff, $measure, $node, $lyrics, "text '$v' -> '$newtext' create syllabic $newv sylstate $syllabic -> $newstate") if $dbg; + $syllabic = $newstate; + $text->setData($newtext) if $v ne $newtext; + my $newsyl = $doc->createElement('syllabic'); + $newsyl->appendText($newv); + $lyrics->appendChild($newsyl); + } + } + } + } + } elsif ($name eq 'TimeSig') { + my ($sN) = $node->findnodes('./sigN'); + my ($sD) = $node->findnodes('./sigD'); + if ($sN && $sD) { + my $sn = $sN->to_literal; + my $sd = $sD->to_literal; + my $newsd = $sd * 2; + display($staff, $measure, $node, "$sn/$sd -> $sn/$newsd") if $dbg; + $sigN = $sd; + $sigD = $newsd; + $sD->firstChild->setData($newsd); + } + } + } + } + } + } + + print $of $doc->toString($doc); + $of->close; +} + +sub display +{ + my $s; + + foreach my $node (@_) { + if ((ref $node) =~ /XML/ && $node->nodeType == XML_ELEMENT_NODE) { + $s .= $node->nodeName . " "; + my @attr = $node->findnodes('@*'); + foreach (@attr) { + $s .= $_->nodeName . " "; + $s .= $_->to_literal . " "; + } + } else { + $s .= $node . " "; + } + } + if ($s) { + chop $s; + say $s; + } +} + +sub usage +{ + say "$0: usage ..."; + exit 1; +}