change Cover version no to 4
[music.git] / mscore-halve
index 4d92702e1058eccea5761c8d7ef17bfc93deb4e3..7272a7657c61e9a22bff88ed17074d62bda15ff1 100755 (executable)
@@ -1,4 +1,4 @@
-#!/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('./*')) {
@@ -82,7 +137,7 @@ sub process
                                                                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);
@@ -117,14 +172,14 @@ sub process
                                                        }
 
                                                        # 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
@@ -146,11 +201,11 @@ sub process
                                                                        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;
@@ -212,6 +267,26 @@ sub display
 
 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;
 }