#!/usr/bin/perl
#
#This software was originally written by me.  I hereby place it in
#the public domain.  Feel free to hack it, steal routines from it, etc.
#
#        Doug Taylor (doug-taylor+@osu.edu)

sub GetNoteNames {
#Hack circle of fourths to get most probable note names
 @CircleOfFourths = ("b","e","a","d","g","c","f");
 $Natural = "n";
 $Flat = "&";
 $Sharp = "#";
 $SplitPoint = 2;#2=more sharp accidentals, 3=more flat accidentals
 for ($i=0;$i<7;$i++) {
  $Location = 5*($i+7)-12*int(5*($i+7)/12);
  $LocationMinusOne = $Location+11-12*int(($Location+11)/12);
  $LocationPlusOne = $Location+1-12*int(($Location+1)/12);
  if ($KeySignature + $i < 0) {
   $Note[$LocationMinusOne] = $CircleOfFourths[$i];
   $Note[$Location] = $CircleOfFourths[$i].$Natural;
  }
  elsif ($KeySignature + $i < $SplitPoint) {
   $Note[$LocationMinusOne] = $CircleOfFourths[$i].$Flat;
   $Note[$Location] = $CircleOfFourths[$i];
  }
  elsif ($KeySignature + $i < $SplitPoint + 2) {
   $Note[$Location] = $CircleOfFourths[$i];
  }
  elsif ($KeySignature + $i < 7) {
   $Note[$Location] = $CircleOfFourths[$i];
   $Note[$LocationPlusOne] = $CircleOfFourths[$i].$Sharp;
  }
  else {
   $Note[$Location] = $CircleOfFourths[$i].$Natural;
   $Note[$LocationPlusOne] = $CircleOfFourths[$i];
  }
 }
 if ($KeySignature >= 0) {
  print(OUTFILE "M$AccumTime score key=$KeySignature#\n");
 }
 else {
  $KeySignature *= -1;
  print(OUTFILE "M$AccumTime score key=$KeySignature&\n");
 }
}

sub SetEverythingUp {
 @Octave = ("-----","----","---","--","-","","+","++","+++","++++","+++++","++++++");
 $KeySignature = 0;
 if ($ARGV[0] eq "") {
  print("What is the name of the MIDI file to process?\n");
  $InFile = <STDIN>;
  chop $InFile;
 }
 else{
  $InFile = $ARGV[0]
 }
 if (not(index($InFile,".mid",0))) {$InFile .= ".mid"}
 $Base = substr($InFile,0,index($InFile,".mid",0));
 if ($ARGV[1] eq "") {
  print("What is the key signature?  (Ignored if embedded in MIDI file.)\n");
  print("(1 = 1 sharp; -1 = 1 flat, etc.  Default = $KeySignature)\n");
  $KeySignature = <STDIN>;
  chop $KeySignature;
 }
 else {
  $KeySignature = $ARGV[1];
 }
 &GetNoteNames;
# $InFile = "<bells4.mid";
 $WrkFileOut = ">".$Base.".wrk";
 $WrkFileIn = "<".$Base.".wrk";
 $Wk2FileOut = ">".$Base.".wk2";
 $Wk2FileIn = "<".$Base.".wk2";
 $OutFile = ">".$Base.".mup";
 $PPQN = 96;
 $CountsPerBar = 4*$PPQN;
 $Granularity = 16;
 }

sub LengthOfChunk {
   $ChunkLength=0;
   for ($i=0;$i<4;$i++){
    read(INFILE,$InByte,1);
    $ChunkLength=256*$ChunkLength+unpack("C",$InByte);
   }
 }

sub ReadMThd {
   read(INFILE,$chunkname,4,0);
   if ($chunkname ne "MThd") {die "File $InFile is not a MIDI file.\n"}
   &LengthOfChunk;
   $total=0;
   for ($i=0;$i<2;$i++){
    read(INFILE,$InByte,1);
    $total=256*$total+unpack("C",$InByte);
   }
   $NoOfTracks=0;
   for ($i=0;$i<2;$i++){
    read(INFILE,$InByte,1);
    $NoOfTracks=256*$NoOfTracks+unpack("C",$InByte);
   }
   print(OUTFILE "M00000000.score staffs=$NoOfTracks\n");
   print("File $InFile has $NoOfTracks tracks.\n");
   for ($i=0;$i<$NoOfTracks;$i++) {
    $j = $i + 1;
    if ($ARGV[2+$i] eq "") {
     print("What clef for track $j?\n");
     print("(B = bass; T = Treble.  Default is Treble.)\n");
     $chunkname = <STDIN>;
    }
    else {
     $chunkname = $ARGV[2+$i];
    }
    if (lc(substr($chunkname,0,1)) eq "b") { 
     $TrackClef[$i] = 1;
     print(OUTFILE "M00000000.staff $j clef=bass\n");
    }
    else {$TrackClef[$i] = 0;}
   }
   $PPQN=0;
   for ($i=0;$i<2;$i++){
    read(INFILE,$InByte,1);
    $PPQN=256*$PPQN+unpack("C",$InByte);
   }
   $CountsPerBar = 4*$PPQN;
   $MTrkNumber = 0;
   $MTrkOffset[$MTrkNumber] = 14;
 }

sub GetVarLength {
   $NoOfBytesRead = 0;
   $VarLength = 0;
   for ($i=0;$i<4;$i++){
    $NoOfBytesRead += read(INFILE,$InByte,1);
    if (unpack("C",$InByte) < 128) {
     $VarLength = 128*$VarLength + unpack("C",$InByte);
     last;
    }
    else {
     $VarLength = 128*$VarLength + unpack("C",$InByte) - 128;
    }
   }
   return $VarLength;
 }

sub Sysex {
   $EventLength = &GetVarLength;
   read(INFILE,$InByte,$EventLength);
 }

sub GetMIDIEvent {
   $EventLength = &GetVarLength;
   read(INFILE,$InByte,$EventLength);
   if     ($Event == 47) {$EndOfTrack = 1}
   elsif ($Event == 88) {
    $TimeNumerator = unpack("C",substr($InByte,0,1));
    $TimeDenominator = 2**unpack("C",substr($InByte,1,1));
    $CountsPerBar = 4*$PPQN*$TimeNumerator/$TimeDenominator;
    print(OUTFILE "M$AccumTime.score time=$TimeNumerator/$TimeDenominator\n");
   } 
   elsif ($Event == 89) {
    $KeySignature = unpack("c",substr($InByte,0,1));
    &GetNoteNames;
   }
 }

sub GetNoteEvent {
   read(INFILE,$InByte,1);
   if ((unpack("C",$InByte) == 0) || ($RunStatus < 144)) {
    if ($RunStatus < 144) {$TempStatus = $RunStatus+ 16}
    else {$TempStatus = $RunStatus}
    if ($NoteOn{$TempStatus.$Event}) {
     seek(OUTFILE,$NoteOn{$TempStatus.$Event} + 9,0);
     print(OUTFILE $AccumTime);
     seek(OUTFILE,0,2);
     delete $NoteOn{$TempStatus.$Event};
    }
   }
   else {
    $NoteOn{$RunStatus.$Event} = tell(OUTFILE);
    print(OUTFILE "N");
    print(OUTFILE $AccumTime);
    print(OUTFILE "00000000");
    print(OUTFILE substr("000".$Event,-3));
    print(OUTFILE "\n");
   }
 }

sub ReadEvent {
   $DeltaTime = &GetVarLength;
   $ExactAccumTime += $DeltaTime;
   $AccumTime = 4*$PPQN*int($ExactAccumTime*$Granularity/(4*$PPQN) + .5)/$Granularity;
   $AccumTime = substr("00000000".$AccumTime,-8);
   read(INFILE,$InByte,1);
   $Event = unpack("C",$InByte);
   if ($Event == 240) {&Sysex; next}
   elsif ($Event >= 128) {
    $RunStatus = $Event;
    read(INFILE,$InByte,1);
    $Event = unpack("C",$InByte);
   }
   if     ($RunStatus == 255) {&GetMIDIEvent}
   elsif ($RunStatus < 160) {&GetNoteEvent}
   elsif (($RunStatus < 192) || ($RunStatus >= 224)) {    
    read(INFILE,$InByte,1);
   }
 }

sub ReadMTrk {
   seek(INFILE,$MTrkOffset[$MTrkNumber],0);
   read(INFILE,$chunkname,4);
   &LengthOfChunk;
   $MTrkOffset[$MTrkNumber + 1] = $MTrkOffset[$MTrkNumber] + $ChunkLength + 8;
   $EndOfTrack = 0;
   $ExactAccumTime = 0;
   while (not($EndOfTrack)) {
    &ReadEvent;
   }
   print(OUTFILE "\n#$AccumTimeEnd of Track\n");
 }

sub EndOfChord {
   if ($Chord[$ChordCounter] eq "") {
    $Chord[$ChordCounter] = "r";
   }
   $ChordLength[$ChordCounter] = $StartTime - $OldStartTime;
   $ChordCounter++;
   $Chord[$ChordCounter] = "";
 }

sub NewBar {
   $OldStartTime = $CountsPerBar*$BarNumber;
   if (not($MusicMode)) {
    print(OUTFILE "music\n");
    $MusicMode = 1;
   }
   &EndOfChord;
   $NoOfChords = $ChordCounter;
   $CountsLeft = $CountsPerBar;
   if ($BarNumber >= 0) {
    $StaffNumber = $MTrkNumber + 1;
    print(OUTFILE "$StaffNumber:  " );
    for ($ChordCounter = 0;$ChordCounter<$NoOfChords;$ChordCounter++) {
     if ($ChordLength[$ChordCounter] > $CountsLeft) {
      $ChordLength[$ChordCounter] = $CountsLeft;
     }
     $CountsLeft -= $ChordLength[$ChordCounter];
     $PowerOfTwo = 1;
     while ($ChordLength[$ChordCounter] > 0) {
      $Approximation = 4*$PPQN/$PowerOfTwo;
      while ($ChordLength[$ChordCounter] >= $Approximation) {
       print(OUTFILE $PowerOfTwo);
       print(OUTFILE $Chord[$ChordCounter]);
       $ChordLength[$ChordCounter] -= $Approximation;
       if (($ChordLength[$ChordCounter] > 0) && 
           ($Chord[$ChordCounter] ne "r")){
        print(OUTFILE "~");
       }
       print(OUTFILE ";");
      }
      $PowerOfTwo *= 2;
     }
    }
    $PowerOfTwo = 1;
    while ($CountsLeft > 0) {
     $Approximation = 4*$PPQN/$PowerOfTwo;
     while ($CountsLeft >= $Approximation) {
      print(OUTFILE $PowerOfTwo);
      print(OUTFILE "r;");
      $CountsLeft -= $Approximation;
     }
     $PowerOfTwo *= 2;
    }
    print(OUTFILE "\nbar;\n");
   }
   $Chord[0] = "";
   $ChordCounter = 0;
   $BarNumber++;
   $OldStartTime = $CountsPerBar*$BarNumber;
 }

sub ProcessNoteEvent {
   if ($OldStartTime < $StartTime) {&EndOfChord}
   $OldStartTime = $StartTime;
   $EndTime = substr($InString,9,8);
   $Event = substr($InString,17,3);
   $Chord[$ChordCounter] =
$Chord[$ChordCounter].$Note[$Event-(12*int($Event/12))].$Octave[int($Event/12)
+ $TrackClef[$MTrkNumber]]; 
 }

sub ProcessWorkFile {
   $EventType = substr($InString,0,1);
   $StartTime = substr($InString,1,8);
   while ($BarNumber < int($StartTime/$CountsPerBar)) {&NewBar}
   if ($EventType eq "M") {
    print (OUTFILE substr($InString,10));
    $MusicMode = 0;
   }
   if ($EventType eq "N") {
    &ProcessNoteEvent;
   }
   if ($EventType eq "#") {
    $StartTime = $OldStartTime + $CountsPerBar;
    &NewBar;
    print(OUTFILE "\n#\n");
    $MTrkNumber++;
    $WrkTrkOffset[$MTrkNumber] = tell(OUTFILE);
    if ($BarNumber > $MaxBars) {$MaxBars = $BarNumber}
    $BarNumber = 0;
   }
 }

#Okay, here we start the main routine
&SetEverythingUp;
#
open(INFILE,$InFile);
open(OUTFILE,$WrkFileOut);
&ReadMThd;
for ($MTrkNumber=0;$MTrkNumber<$NoOfTracks;$MTrkNumber++){
 &ReadMTrk;
 }
close (INFILE);
close (OUTFILE);
#
open(INFILE,$WrkFileIn);
open(OUTFILE,$Wk2FileOut); 
$BarNumber = 0;
$MTrkNumber = 0;
$MusicMode = 0;
$OldStartTime = 0;
$MaxBars = 0;
while ($InString = <INFILE>) {
 &ProcessWorkFile;
 }
close (INFILE);
close (OUTFILE);
#
open(INFILE,$Wk2FileIn);
open(OUTFILE,$OutFile);
$BarNumber = 0;
while ($BarNumber <= $MaxBars) {
 $MTrkNumber = 0;
 while ($MTrkNumber < $NoOfTracks) {
  $InString = <INFILE>;
  chop $InString;
  if (eof(INFILE) || ($InString eq "#")) {
   $MTrkNumber++;
   seek(INFILE,$WrkTrkOffset[$MTrkNumber],0);
  }
  elsif ($InString eq "bar;") {
   $WrkTrkOffset[$MTrkNumber] = tell(INFILE);
   $MTrkNumber++;
   seek(INFILE,$WrkTrkOffset[$MTrkNumber],0);
  }
  elsif ($InString ne "") {
   $WrkTrkOffset[$MTrkNumber] = tell(INFILE);
   print(OUTFILE "$InString\n");
  }
 }
 if ($InString eq "bar;") {print(OUTFILE "bar;\n");}
 $BarNumber++;
}
close (INFILE);
close (OUTFILE);
unlink $Base.".wrk",$Base.".wk2";
