-#!/usr/bin/perl
+#!/usr/bin/env 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
#
use strict;
+
+require 5.10.1;
+
use XML::LibXML;
use File::Basename;
+use File::Temp qw{ :mktemp };
use IO::File;
-
use v5.10;
+use utf8;
+
+our $VERSION = "1.0";
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(
+ maxima long
long breve
breve whole
whole half
half quarter
quarter eighth
- eighth sixteenth
- sixteenth thirtysecond
- thirtysecond sixtyfourth
+ eighth 16th
+ 16th 32nd
+ 32nd 64th
+ 64th 128th
+ 128th 256th
+ 256th 512th
+ 512th 1024th
)
);
our %yesno = ( qw(yes 1 no 0) ); # used for turning translating yes/no text values
-our $dbg = 1; # show debugging
+our $dbg = 0; # show debugging
our $removebeam = 1; # if set remove any BeamMode clauses
usage() unless @ARGV;
+binmode STDOUT, "utf8";
+
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;
+
+ if ($fn =~ /^-\w/) {
+ usage() if $fn =~ /^\-+[\?h]/i;
+ $dbg ^= 1 if $fn =~ /^\-+x/;
+ $removebeam ^= 1 if $fn =~ /^\-+b/;
} else {
- usage();
- }
+ my ($ifn, $ofn, $tfn);
- process($ifn, $ofn);
+ my ($name, $path, $suffix) = fileparse($fn, qr/\.[^.]*/);
+ if ($suffix eq ".mscx" || $suffix eq ".mscz") {
+ $ifn = $fn;
+ $ofn = $path . $name . "-halved.mscx";
+
+ # extract out the zipped up .mscx file from an .mscz archive
+ if ($suffix eq '.mscz') {
+ $tfn = mktemp("/tmp/msczXXXXXXX");
+ my $xifn = $ifn;
+ $xifn =~ s/z$/x/;
+ system("unzip -p $ifn $xifn > $tfn");
+ $ifn = $tfn; # the tmp file is the actual input.
+ }
+ } else {
+ usage("Only Musescore .mscx or .mscz files allowed (got: $fn)");
+ }
+
+ process($ifn, $ofn, $fn);
+ unlink $tfn if $tfn;
+ }
}
exit 0;
sub process
{
- my ($ifn, $ofn) = @_;
+ my ($ifn, $ofn, $fn) = @_;
- my $of = IO::File->new(">$ofn") or die "Cannot open $ofn $!\n";
my $p = XML::LibXML->new();
- my $doc = $p->load_xml(location=>$ifn);
+ my $doc = eval { $p->load_xml(location=>$ifn) };
+
+ usage("Invalid Musescore file detected (in $fn) $@") unless $doc;
+
+ my $version;
+
+ my ($muse) = $doc->findnodes('/museScore');
+ if ($muse) {
+ my ($v) = $muse->findnodes('./@version');
+ $version = $v->to_literal if $v;
+ }
+ if (!$version || $version < 2) {
+ $version ||= "Unknown";
+ usage("Version $version detected in $fn, this program will only work with MuseScore 2 (or greater) files");
+ }
+
+ my $of = IO::File->new(">$ofn") or usage("Cannot open $ofn $!");
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')) {
+ my $lens;
+
+ # obtain the measure no and any len attr. Change the len attribute
+ my ($l) = $measure->findnodes('./@len');
+ if ($l) {
+ my ($t,$b) = split m{/}, $l->to_literal;
+ $b *= 2;
+ $lens = "$t/$b";
+ $l->setValue($lens);
+ }
# process nodes
foreach my $node ($measure->findnodes('./*')) {
my ($nz) = $node->findnodes('./duration/@z');
my ($nn) = $node->findnodes('./duration/@n');
my $was = $nn->to_literal;
- my $now = $sigD || $was * 2;
+ my $now = $was * 2;
my $z = $nz->to_literal;
display($staff, $measure, $node, "$type $z/$was -> $z/$now") if $dbg;
$nn->setValue($now);
}
# determine where we are in a word and if there is a <syllabic>
- # clause, and it is necessary, add an appropriate one
+ # clause, note its value (which is "in word" or "not in word")
#
# This is for dealing with musicxml imports where there is no
- # explicit detection of trailing '-' signs, if there are and
- # there is no <syllabic> add one of the correct sort and remove
+ # explicit detection of trailing '-' signs, if there are such signs and
+ # there is no <syllabic> clause, 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
+ # Sadly, it's too much hard work to deal with any trailing '_' 'cos
# mscore calulates the distance in advance because they appear
# to be too lazy to have another <syllabic> state to deal with
# it. Manual edit will therefore be required. Hopefully, not
my $newv;
my $newstate;
my $newtext = $v;
- if ($v =~ /-$/) {
+ if ($v =~ /[-–]$/) {
$newv = 'begin' unless $syllabic;
$newv = 'middle' if $syllabic;
$newstate = 1;
- $newtext =~ s/\-+$//;
+ $newtext =~ s/[-–]+$//;
} else {
$newv = 'end' if $syllabic;
$newstate = 0;
sub usage
{
- say "$0: usage <filename.mscx> ...";
+ my $s = shift;
+ my ($name, $path, $suffix) = fileparse($0, qr/\.[^.]*/);
+ $name = "$name$suffix: ";
+
+ if ($s) {
+ say "\n${name}$s\n";
+ $name = "\t";
+ }
+ say "${name}version $VERSION usage: [-b] [-x] <filename.msc[xz]> ...\n";
+ say "\tA program to halve the note values of a MuseScore 2.x file.\n";
+ say "\tThis designed to be used to convert 'early music' note values";
+ say "\tinto something more 'modern'. It will also sort out things like";
+ say "\tinter-syllablic hyphenation if it comes across trailing hyphens";
+ say "\tsuch as come from imported Finale musicxml files.";
+ say "\n\tfilenames: 'a.mscz' (or 'a.mscx') will be written to 'a-halved.mscx'.";
+ say "\tYou can do several files at a time!\n";
+ say "\n\tArguments:";
+ say "\t-b - normally any beaming is converted to auto, use this to retain beaming info";
+ say "\t-x - enable debugging (actually more a stream of conscienceness)";
+ say;
+
exit 1;
}