Converting MusicXML or midi files for Sonic Pi part 2

SonicPiPlayingConvertedPiece

Sonic Pi playing a piece converted from midi via MusicScore

In this part I will look a little more closely at the script and its use.

I modified the script slightly in various ways, and the modified version is shown below:

/*
 This Processing program converts from the MusicXML file to the Sonic Pi code.
 Programmed by Hiroshi TACHIBANA, 2016.8, CC-BY Modified by Robin Newman Aug 2016
 
 NB SET INPUT MUSICXML FILE NAME IN LINE 77
 AND pn value IN LINE 78 before running the script
 ALSO ADJUST GRACE NOTE RATIO IN LINE 79 gn =1.0/16, or 1.0/8,....1,0/5 if required for each part
 THE MUSICXML FILE(S) SHOULD BE PLACED IN THE SCRIPT DATA FOLDER

 A part one of the top score is converted.
  Please set the MusicXML file name to the line 77.
  data0=loadStrings("???.xml");
 Sonic Pi code is printed out to the standard output. (n the black window below this one)
 Output files "data??.txt" are generated and used by the script internally.
 Grace note is 1/16 beat. (adjustable)
 Chords are available.
 Ties are available.
 Chords with ties are unavailable.
 
 RBN has translated comments from Japanses to English using Google Earth. Some may be helpful. "Thailand" should perhaps be "tie type"
 other mods: increase array sizes used in temp data files  and tieS and tieN arrays to accommodate longer files
 add suffix number (variable pn set manually in line xx to give different variable names for different part runs
 add sustain: and release: settings to output code to give 90% sustained notes
 
 For multi-runs accommodating different parts the code produced for each part can be concatenated to give
 one longer file. This will run "as is" You can add use_synth settings as desired
*/


// (notes from the original script used to process the Spring of Sea piece)
// If the spring of sea, shakuhachi of xml, koto of the right hand of xml, to convert the 3 files of the harp of the left hand of the xml export separately.
// Left hand right hand, select and delete the only specific voice, to create from the rest. (1 and may exchange of 2 is also use of voice)
// Note: part to two, speed specification is not.
// Note in MuseScore: If you created a Parts is, even if there is a speed sign at the beginning of the part, the location does not have a note (or rest) in the same position of the part marks side, Parts side because it is not taken over, there is a need to add a speed sign in the part marks side.
// Thailand in the code is not allowed without corresponding

/* Sonic Pi at use_bpm and a, and b in the following manner, to run concurrently in the thread in every part
 in_thread do
 use_bpm 60
 a=[ ]
 b=[ ]
 i=0
 a.length.times do
 play a[i]
 sleep b[i]
 i=i+1
 end
*/



String[] XMLtag1= {
  "part", "divisions"
};//, "score-part","part-list", "part-name"};
String[] XMLtag2= {
  "per-minute", "measure", "<note", "rest", "step", "alter", "octave", "duration", "tie type", "grace", "chord", "arpeggiate", "voice"
};
String[] data0=new String[900000];
String[] data1= {}, data2= {}, data3= {};
String[][] data4=new String[9][20000];
String[][] data5=new String[9][20000];
String[][] data6=new String[9][20000];
String[][] data7=new String[9][20000];
String[][] data7c=new String[9][20000];
int[] partStartLine=new int[9], partEndLine=new int[9], partLength=new int[9]; // position of the data for each part
int[] partLength5=new int[9]; 
float[] partDivisions=new float[9]; // speed of each part division value
int Npart;

void setup() {
  readData();
  //  readData2();
  //  readData3();
}

void readData() {
    data0=loadStrings("Test_for_Sonic_Pi-Piano-1.xml");  // <<<<<<<<<<<<<< INPUT Music XML file <<<<<<<<<<<
    int pn=2; //<<<<<<<<<<<<<< set part suffix number here <<<<<<<<<<<
    float gn = 1.0/2; //<<<<<<<<<<<<<<<<<<< set grace note fraction eg 1.0/16, 1.0/8,.....1.0/2  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  //  println("overall structure");
  for (int i=0; i<data0.length; i++) {
    for (int j=0; j<XMLtag1.length; j++) {       if (data0[i].indexOf(XMLtag1[j])>=0) {
         //      println(data0[i]);
        data1=(String[])append(data1, trim(data0[i]));
      }
    }
  }
  saveStrings("data1.txt", data1); //       SAVE1

  //  println("the entire structure - up to here");
  for (int i=0; i<data0.length; i++) {
    data0[i]=trim(data0[i]);  // the beginning of removing the blank
  }
  for (int i=0; i<data0.length; i++) {
    for (int j=0; j<XMLtag2.length; j++) {       if (data0[i].indexOf(XMLtag2[j])>=0) {
        // println(data0[i]);
        data2=(String[])append(data2, data0[i]);
      }
    }
  }
  saveStrings("data2.txt", data2);  //          SAVE2
  for (int i=0; i<data0.length; i++) {
    for (int j=0; j<XMLtag1.length; j++) if (data0[i].indexOf(XMLtag1[j])>=0) data3=(String[])append(data3, data0[i]);
    for (int j=0; j<XMLtag2.length; j++) if (data0[i].indexOf(XMLtag2[j])>=0) data3=(String[])append(data3, data0[i]);
  }
  saveStrings("data3.txt", data3); //           SAVE3
  //  println(data0.length); // whole
  //  println(data1.length); // header
  //  println(data2.length); // music part
  //  println(data3.length); // header + music part
  Npart=0;
  for (int i=0; i<data1.length; i++) {
    if (data1[i].indexOf("=0) Npart++;
  }
  //println("パート数: "+Npart);
  int j=0;
  for (int i=0; i<data3.length; i++) {
    if (data3[i].indexOf("=0) {
      partStartLine[j++]=i; // int(data3[i].substring(11, 12));
      //      println(data3[i].substring(11, 12)+" "+i);
    }
  }
  for (int i=0; i<Npart-1; i++) partEndLine[i]=partStartLine[i+1]-1;
  partEndLine[Npart-1]=data3.length;
  //for (int i=0; i<Npart; i++) println("Part Line"+(i+1)+": "+partStartLine[i]+" "+partEndLine[i]);
  for (j=0; j<Npart; j++) {
    int k=0;
    for (int i=partStartLine[j]; i<partEndLine[j]; i++) {
      if (data3[i].indexOf("")>=0) {
        data4[j][k++]="divisions "+data3[i].substring(data3[i].indexOf("")+11, data3[i].indexOf(""));
        partDivisions[j]=float(data3[i].substring(data3[i].indexOf("")+11, data3[i].indexOf("")));
      }
      if (data3[i].indexOf("")>=0) data4[j][k++]="per-minute "+data3[i].substring(data3[i].indexOf("")+12, data3[i].indexOf(""));
      if (data3[i].indexOf("")>=0) data4[j][k++]="r";
      if (data3[i].indexOf("<grace")>=0) data4[j][k++]="grace";
      if (data3[i].indexOf("")>=0) data4[j][k++]="step "+data3[i].substring(data3[i].indexOf("")+6, data3[i].indexOf(""));
      if (data3[i].indexOf("")>=0) data4[j][k++]="alter "+data3[i].substring(data3[i].indexOf("")+7, data3[i].indexOf(""));
      if (data3[i].indexOf("")>=0) data4[j][k++]="octave "+data3[i].substring(data3[i].indexOf("")+8, data3[i].indexOf(""));
      if (data3[i].indexOf("")>=0) data4[j][k++]="duration "+data3[i].substring(data3[i].indexOf("")+10, data3[i].indexOf(""));
      if (data3[i].indexOf("=0) data4[j][k++]="tie start";
      if (data3[i].indexOf("=0) data4[j][k++]="tie stop";
      if (data3[i].indexOf("")>=0) data4[j][k++]="chord"; // previous step
      if (data3[i].indexOf("")>=0) data4[j][k++]="voice "+data3[i].substring(7, 8); // If there is a voice
    }
    partLength[j]=k;
  }
  for (j=0; j<Npart; j++) saveStrings("data4"+j+".txt", data4[j]); //     SAVE4

  for (j=0; j<Npart; j++) {
    //println("########## PART= "+(j+1));
    int k=0;
    for (int i=0; i<partLength[j]; i++) {       if (data4[j][i].indexOf("chord")>=0) data5[j][k++]="chord"; // chord
      if (data4[j][i].indexOf("per-minute")>=0) data5[j][k++]="use_bpm "+data4[j][i].substring(11, data4[j][i].length()); // bps display
      if (data4[j][i].equals("r")) {  //Display of rest and time
        data5[j][k++]="play :r "+str(float(data4[j][i+1].substring(9, data4[j][i+1].length()))/partDivisions[j]);
        //        println(data5[j][k-1]);
      }
      if (data4[j][i].equals("grace")) {
        data5[j][k++]="grace";  // octave is not attached to the note at the back of the grace.
      }
      if (data4[j][i].indexOf("step")>=0) {
        data5[j][k++]="play :"+data4[j][i].substring(5, 6); // Display notes when step there is

        if (data4[j][i+1].indexOf("alter")>=0) {// step there is a alter to the next line in the case of ♯ ♭
          if (data4[j][i+1].substring(6, data4[j][i+1].length()).equals("1")) data5[j][k-1]+="s";  // in the case of sharp
          else if (data4[j][i+1].substring(6, data4[j][i+1].length()).equals("-1")) data5[j][k-1]+="f";  // in the case of flat
          data5[j][k-1]+=data4[j][i+2].substring(7, data4[j][i+2].length());     // octave
          //            println(i+":"+j+": "+data4[j][i+2]+": "+data4[j][i+3]);
          if (data4[j][i+3].indexOf("duration")>=0) data5[j][k-1]+=" "+str(float(data4[j][i+3].substring(9, data4[j][i+3].length()))/partDivisions[j]); // length
        } else if (data4[j][i+1].indexOf("octave")>=0 & data4[j][i+2].indexOf("duration")>=0) {// step If the line is not a there is octave ♯ ♭
          data5[j][k-1]+=data4[j][i+1].substring(7, data4[j][i+1].length());  // octave not appear at the time of grace? ? ? ? ?
          //            println("$$$$$"+data4[j][i+2]);
          if (data4[j][i+2].indexOf("duration")>=0) data5[j][k-1]+=" "+str(float(data4[j][i+2].substring(9, data4[j][i+2].length()))/partDivisions[j]); // length
        } else  if (data4[j][i+1].indexOf("octave")>=0) {
          data5[j][k-1]+=data4[j][i+1].substring(7, data4[j][i+1].length()); // for when the octave grace
        }
      }
      if (data4[j][i].indexOf("tie")>=0) data5[j][k++]=data4[j][i];
    }
    saveStrings("data5"+j+".txt", data5[j]); //        SAVE5
    //    println("k="+k);
    partLength5[j]=k;
  }

  //  println(data5[0][100]);
  int[][] tieS=new int[9][2000], tieE=new int[9][2000];
  //String tmp;
  //  for (j=0; j<Npart; j++) {
  j=0;
  int k1=0, k2=0; // Thailand start, the number of end k1 and k2 is necessary equal
  for (int i=0; i<partLength5[j]; i++) {     //      tmp=data5[j][i];     if (data5[j][i].indexOf("tie start")>=0) {
      tieS[j][k1]=i;   // to record the start of the line of Thailand
      //println(k1+" tie start="+i);
      k1++;
    }
    if (data5[j][i].indexOf("tie stop")>=0) { 
      tieE[j][k2]=i; //  to record the end of the line of Thailand
      //println(k2+" tie stop="+i);
      k2++;
      if (tieS[j][k1-1] != tieE[j][k2-1]-2) println("#### warning there is a tie not a single note ###");
      if (k1 != k2) println("#### warning not fit to start and the number of the end of Thailand ###");
    }
  }
  //println("Thailand number ="+k1);

 // The length of the front of the line (start note of Thailand) of the tie start to the length of the entire Thailand.
 // Note of in Thailand of up to tie stop is left intact.
  float sum=0;
  j=0; // part (corresponding part is not used only by 0)
  for (int i=0; i<k1; i++) { // Thailand number minute of the loop
    //println("tie= "+(tieS[j][i]+1)+" 〜 "+tieE[j][i]);
    int nseq=0; // number of consecutive Thailand
    for (int iseq=i; iseq<k1-1; iseq++) {
      if ( tieE[j][iseq] != tieS[j][iseq+1]-1) break;
      else nseq++;
    }
    //println("consolidated the number of Thailand nseq =" +nseq);
    if (nseq==0) { // if Thailand is one
      String[] t1=splitTokens(data5[j][tieS[j][i]-1 ], " "); // front of Thailand. If Thailand is followed, the error becomes a tie stop
      String[] t2=splitTokens(data5[j][tieS[j][i]+1], " ");  // back side of Thailand
      //println(i+" "+j+" "+tieS[j][i]+" "+data5[j][tieS[j][i]-1]);
      data5[j][tieS[j][i]-1]=t1[0]+" "+t1[1]+" "+str(float(t1[2])+float(t2[2])); // three or more notes does not correspond to the tie linked
      //println("New data5:"+data5[j][tieS[j][i]-1]);
    } else { // If Thailand is linked
      //println("Thailand is consolidated i ="+i);
      String[] t1=splitTokens(data5[j][tieS[j][i]-1 ], " "); // front of Thailand
      String[] t2=splitTokens(data5[j][tieE[j][i]-1 ], " "); // back side of the first Thailand
      //println("t1="+t1[0]+" "+t1[1]+" "+t1[2]);
      //println("t2="+t2[0]+" "+t2[1]+" "+t2[2]);
      sum=float(t1[2])+float(t2[2]); // the sum of the note of the length of the at until the first one in Thailand
      // println("sum="+sum);
      for (int iseq=0; iseq<nseq; iseq++) {
        String[] t3=splitTokens(data5[j][ tieE[j][i]+(iseq+1)*3-1 ], " ");  // back side of Thailand
        sum+=float(t3[2]);
      }
      data5[j][tieS[j][i]-1]=t1[0]+" "+t1[1]+" "+str(sum); // to Thailand three or more notes have been consolidated It does not correspond
      i+=nseq;
    }
  }

  // Add time 0.0625 to the note of the line of the back of the grace (when gn = 1.0/16)
  // If the grace continues, as well as adding a time 0.0625 to the note of the end of the line
  // Draw the length of the grace amount from the notes of the two after the last grace.
  j=0;
  for (int i=0; i<partLength5[j]; i++) {     //    println("i="+i+" "+partLength5[j]);     int NNgrace=0;     //    println("data5[j][i]="+data5[j][i]);     if (data5[j][i].indexOf("grace")>=0) { // the current position is grace whether
      data5[j][i+1]+=" "+str(gn);// grace gives the length of the note 0.125 = 32 minutes note set by gn variable
      NNgrace=1;
      while (data5[j][i+NNgrace*2].indexOf("grace")>=0) {//the current position is grace whether
        data5[j][i+NNgrace*2+1]+=str(gn);//  grace gives the length of the note as set by gn
        NNgrace++;
      }
      //      println("NNgrace="+NNgrace);
      //      println(data5[j][i+NNgrace*2]);
      String[] t1=splitTokens(data5[j][i+NNgrace*2], " ");
      data5[j][i+NNgrace*2]=t1[0]+" "+t1[1]+" "+str(float(t1[2])-NNgrace*gn);// pull the length of the grace worth from notes behind
      i+=NNgrace*2+2;
    }
  }
  saveStrings("data6"+j+".txt", data5[j]);

 // Remove the from the tie start to tie stop
 // Remove only the rows of the grace
  j=0;
  int k=0;
  for (int i=0; i<partLength5[j]; i++) {     data6[j][k]=data5[j][i];     //    int tie_s;     int tieLines=0;     if (data5[j][i].indexOf("tie start")>=0) {
      //      println("tie start "+i+" "+data5[j][i]);
      //      tie_s=i;
      while (data5[j][i+tieLines].indexOf("tie stop")<0) {
        tieLines++;
      }
      //      println("tieLines="+tieLines);
      //      println("tie stop "+(i+tieLines)+" "+data5[j][i+tieLines]);
      k--;
    }
    i+=tieLines;
    k++;
  }

  int k3=0;
  for (int i=0; i<k; i++) {
    if (data6[j][i].indexOf("grace")<0) { // to delete rows of grace
      data7[j][k3]=data6[j][i];
      k3++;
    }
  }

  int kc=0; // Of new notation array element
  String tmpTime="";
  for (int i=0; i<k3; i++) {     String tmpC=data7[j][i];     if (data7[j][i].indexOf("chord")>=0) { // when chord (2 sound eye)
      int ic=1; // when the two sound code = 1
      while ((i+ic*2)=0) { // count the number of notes of a chord. (3 sound more)
        //        println(i+" "+ic+" "+data7[j][i+ic*2]);  // last error in the case of a chord
        ic++;
      }
      tmpC="play ["; // case of the code, the head of the notation is, "["
      for (int ic2=0; ic2<ic+1; ic2++) {
        String[] t1=splitTokens(data7[j][i-1+ic2*2], " ");
        tmpC+=t1[1]+","; // extract the notation of chord
        tmpTime=t1[2]; // length of the chord
      }
      tmpC=tmpC.substring(0, tmpC.length()-1)+"]"+" "+tmpTime; // of the end "," to remove the "]"
      //      println("tmpC="+tmpC);
      i+=ic*2; // promote the original array only chord minute
      data7c[j][kc-1]=tmpC;  // put the chord to the sequence of the new notation
      data7c[j][kc]=data7[j][i];
    } else data7c[j][kc]=tmpC; // if not a chord, put the original notation into a new array.
    kc++;
  }
  //  for (int i=0; i<kc; i++) println("data7c= "+data7c[j][i]);

  //  println("============data7=======");
  //  for (int i=0; i<partLength5[j]; i++) println(data7c[j][i]);
  int len7=0;
  for (int i=0; i<partLength5[j]; i++) if (data7c[j][i] != null) len7++;
  //  println("data7_len="+len7);
  String[] data8;
  data8=new String[len7];  // make data8 of the sequence of the data length from data7.
  for (int i=0; i<len7; i++) data8[i]=data7c[j][i]; // from data8 deals with only part 1
  saveStrings("data8"+j+".txt", data8); //                      SAVE8
  //  println("============data8=======");
  //  for (int i=0; i<data8.length; i++) println(data8[i]);

  println("#=========== Sonic Pi program ================================");
  //  String data9[] =new String[len7];
  String tmpS1;
  String tmpS2;
  String tmpS3="c"+pn+"=[";  // insert pn to distinguish runs of script for different parts
  int ibpm=0;
  println("a"+pn+"=[]"); // add pn
  println("b"+pn+"=[]"); // add pn
  for (int i=0; i<data8.length; i++) {     // Every use_bpm put a note in the array     //int playLines=0;     tmpS1="a"+pn+"["+ibpm+"]=["; // add pn     tmpS2="b"+pn+"["+ibpm+"]=["; // add pn     if (data8[i].indexOf("use_bpm")>=0) {
      //      println(data8[i]);
      tmpS3+=data8[i].substring(8, data8[i].length())+",";
      if (i==0)i++;
    }
    while (i=0) {
      tmpS3+=data8[i].substring(8, data8[i].length())+",";
      ibpm++;
    }
  }
  tmpS3=tmpS3.substring(0, tmpS3.length()-1)+"]";
  if(!tmpS3.equals("c"+pn+"=]")) println(tmpS3);
  println("in_thread do");
  //  println("i=0");
  println("for i in 0..a"+pn+".length-1");
  if(!tmpS3.equals("c"+pn+"=]")) println("use_bpm c"+pn+"[i]");
  //  println("j=0");
  println("for j in 0..a"+pn+"[i].length-1");
  println("play a"+pn+"[i][j],sustain: b"+pn+"[i][j]*0.9,release: b"+pn+"[i][j]*0.1");
  println("sleep b"+pn+"[i][j]");
  //  println("j+=1");
  println("end");
  //  println("i+=1");
  println("end");
  println("end");
}

1) I increased the size of the data arrays data0data7 and the tieS and tieN arrays to accommodate longer files. It doesn’t matter if they are too big, but you will get script errors if they are not large enough.
2) I google translated the Japanese comments. The results are not always very clear, but can help understand the program in some cases.
3) I added a variable pn which is set manually in line 78. If you are converting several instrument files from the same piece, then you can adjust this to a different value for each file, so that the arrays holding the notes and durations have different suffixes added:
eg a1[0], b1[0] if pn=1     a2[0],b2[0] if pn =2 etc.
(4) I added the float variable gn to allow the grace note length to be altered for a given run. This is set to to 1.0/16 for a 16th note, ….1.0/2 for a half note. It can be set to a different value for different parts if required.
5) I added sustain and release parameters to the output code to set the duration of each note to 90% of its duration: eg play a1[i][j],sustain: b1[i][j]*0.9,release: b1[i][j]*0.1

In Sonic Pi 2.10 (the current version at time of writing) there is a size limit on the length of code which can run in one buffer. It is possible to split the code to run in two or more buffers, with cue and sync commands linking them together, and this is what was done in the example in part 1 of the article. However, I wanted to be able to use longer files than this limit without having to modify the code to accommodate this limit. Happily Sam Aaron has been able to add a command to Sonic Pi 2.11dev, soon to be released, which enables you to run code from an external file without this limit having to apply. The command has the syntax run_file <filename including path: can use ~ for home folder> eg run_file “~/spfiles/file_to_play.rb” would play the file file_to_play.rb in folder spfiles in the user’s home directory.

I will now walk through converting a short sample piece I produced in Muse Score which illustrates the features of the script.

SonicPiTestScore

This has two piano staves added, one treble and the other bass. I started with a blank score and used edit -> instruments from where I added piano twice, deleting the bass staff from the first and the treble staff from the second, thus ending up with two independent “voice” piano parts. This then enables MuseScore to produce separate instrument parts for each one. The piece illustrates the following. First, tempo changes which are reflected in three different set_bpm settings for each part in the resulting Sonic Pi program. Secondly, the use of a tied note in the base part which results in a single note of double length instead of two breve notes. Third, the use of chords in the first part which are handled by the conversion script, and finally, the use of two grace notes. To prepare for using the script we first use the parts command on the File menu in MuseScore and create individual parts for the two voices.

Next we prepare the processing script, and the data folder to which the MuseScore parts can be exported as MusicXML files. If you have not already done so, place a copy of the MusicXMLtoSonicPi_01_rbn.pde file inside the processing folder created in the Doucments folder when processing is installed. The first time you open this file with the processing app it will automatically create a folder of the same name, and move itself inside that folder. Open that folder (named MusicXMLtoSonicPi_01_rbn) and create a folder named data inside it. Now return to the MuseCore app and select Export Parts… from the File Menu. Navigate to the data folder you have just created and save the files there, making sure to select MusicXML as the file type from the Save Dialog window. This should place two files Test_for_Sonic_Pi-Piano.xml and Test_for_Sonic_Pi-Piano-1.xml inside the folder.

Switch back to the Processing sketch script and scroll down to line 77. Copy the first filename into that line, replacing the filename which is there already, giving data0=loadStrings(“Test_for_Sonic_Pi-Piano.xml”);
In the next line set pn=1; and in the line after that set the grace note variable gn=1.0/2
Now run the script by clicking the run arrowhead icon, and all being well Sonic Pi code will appear in the black window below. Copy all of the text after the line
#=========== Sonic Pi program ================================
by dragging across it and using cmd+c. Paste the code into an empty buffer in Sonic Pi.
Now change the filename in the sketch script to that of the second exported file, giving data0=loadStrings(“Test_for_Sonic_Pi-Piano-1.xml”); and also change the following line to pn=2; The grace note variable remains unaltered. Run the sketch again, and copy the resulting Sonic Pi code from the window below, and paste it into the Sonic Pi buffer below the code from the first part. You can insert a line at the beginning specifying the synth you want to use eg use_synth :tri If you now play the sonic Pi buffer you should hear the Test piece being played.
The code for the resulting pice is shown below:

#Test for Sonic Pi program converted from MuseScore
use_synth :tri

a1=[]
b1=[]
a1[0]=[:C4,:D4,:E4,:F4,:G4,:A4,:B4,:C5]
b1[0]=[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
a1[1]=[:C5,:B4,:A4,:G4,:F4,:E4,:D4,:C4,:F4,:E4,:C4,:E4,:C4,:F4,:G4,:E4,:G4,:C5]
b1[1]=[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0]
a1[2]=[[:G4,:B4,:D5],[:G4,:C5,:E5],[:G4,:B4,:D5],[:G4,:B4],[:C4,:E4,:G4,:C5]]
b1[2]=[1.0,1.0,1.0,1.0,4.0]
c1=[160,240,120]
in_thread do
  for i in 0..a1.length-1
    use_bpm c1[i]
    for j in 0..a1[i].length-1
      play a1[i][j],sustain: b1[i][j]*0.9,release: b1[i][j]*0.1
      sleep b1[i][j]
    end
  end
end

a2=[]
b2=[]
a2[0]=[:C3,:r,:G2,:r]
b2[0]=[2.0,2.0,2.0,2.0]
a2[1]=[:C3,:r,:G2,:C3]
b2[1]=[2.0,2.0,4.0,8.0]
a2[2]=[:r,:C2]
b2[2]=[4.0,4.0]
c2=[160,240,120]
in_thread do
  for i in 0..a2.length-1
    use_bpm c2[i]
    for j in 0..a2[i].length-1
      play a2[i][j],sustain: b2[i][j]*0.9,release: b2[i][j]*0.1
      sleep b2[i][j]
    end
  end
end

Note the code for each part is created in three sections. For part 1 the notes are in a2[0],a1[1] and a1[2] with durations in b1[0],b1[1] and b1[2]. Each of these sections corresponds to one of the three different tempos in the piece 160,240 and 120 bpm. For part 2 the variables are a2[0],a2[1] and a2[2] with durations in b1[0],b1[1] and b2[2]. Notice that teh grace notes are actually played as half the duration of the associated note (as gn was 1.0/2) In b1[1] you will see most notes have duration 1.0 (a crotchet) but there are two sequences of 0.5,0.5 where the grace note takes half the value of a crotchet, leaving the following crotchet also halved to 0.5

Having got the Sonic Pi code from the MuseScore, you can now add extra Sonic Pi features if you wish, eg choice of synth for each part, use of effects such as reverb etc. I have successfully used this script with a range of MuseScore files and midi files which can first be opened into Muse Scores and then processed as described above. The biggest conversion I have done is for a 9 part fugue which lasts 23.5 minutes. In this case I took the note arrays produced by the conversion process but used sampled based voices to play them as I have previously detailed in articles in this blog. In the case of long files which exceed the limit imposed by UDP communications internally within Sonic Pi, a command has been added to Sonic Pi 2.11dev which enables the playing of long files using a run_file command.

Finally a brief word about the files data1.txt, data2.txt ….data80.txt created in the Sketch folder when the code is run. These are files generated by the script and used internally to aid the processing. The are regenerated each time the script is run, and their contents will vary depending upon the music.xml being processed. Without going into great detail, you may like to examine their contents and you will see how the sonic pi array lists of notes and durations are gradually built up as the sketch code runs.

I think that Hiroshi TACHBANA has done a fantastic job in writing this script. I hope you like it too, and will be able to use it to extend the range of code you can run in Sonic Pi. Once you have it set up, it is a lot quicker than having to type in all the notes and durations from scratch yourself!

Downloads: original script from Hiroshi TACHIBANA
modified script by Robin Newman
MuseScore 2.0 Sonic Pi Test piece
Test_for_Sonic_pi.rb

A selection of other MuseScore pieces that I have tried can be downloaded in this zip file which also contains the associated midi files and the Sonic Pi playable files, to which I have added embellishments such as synth choices, volumes, reverb etc.

Postscript

It is perhaps worth saying a little bit about MuseScore 2, as this is used quite extensively in the conversion process. It can be installed on Mac or PC or linux, and there is an early version available on the Pi. I have seen discussions from people trying to install 2.03 on a Pi but it is not a painless process, and performance is not great. I had not really used it much before this project, but have found it to be very powerful, if a little unintuitive in certain tasks.

The requirements for the conversion script are for a separate voice track for each part to be transformed. (essentially Sonic Pi will play each of these in a separate thread, thus combining the sounds). Although you can have chords in a part, all the notes in the chord must be of the same duration, and you cannot have notes tied to chords.
Piano scores. A piano part is essentially one instrument with two staves (treble and bass). In order to convert piano music you have to separate the treble and bass staffs into separate instrument voices. To do this, create two further piano instruments using instruments… from the edit menu. select the treble staff in the existing piano score. (Click in the first bar, then shift click in the last bar) Copy (cmd+c). click on the rest in the treble staff of the first additional piano part, then paste (cmd+v). Now you can return to the instruments… command on the edit menu and remove the first piano part, and the second and first staves respectively from the second and third piano parts leaving separate piano treble and bass voices containing the two parts you want.

The second technique you may need to use is to split two voices mixed on the same staff. This will occur when you get notes of different lengths playing at the same time in the same staff. You can see this most easily by selecting a staff containing such a part. You will see the first voice with the notes shown in blue, the second with the notes shown in green, the third (if any) with the notes shown in red. By enabling the selection filter (on the View Menu) you can selectively switch off the blue notes (voice 1) or the green notes (voice 2) or the red notes (voice 3). Note if you switch all the voices off you will lose the selection, and have to switch the voices back on again and reselect the part. What you need to do is to create a new staff for each of the voices to be separated (using the instruments command). Then copy the original part to each of these new staves as described previously. Then select each of these new staves in turn, and use the selection filter to leave ONE of the voices deselected and press the delete key to remove the others.If you make a mistake MuseScore supports a multiple undo (cmd+z). The aim is to end up with each voice on a separate staff. You will notice that if (for example you are leaving voice 2) you will also get a line of rests for part 1 in the staff. To get rid of these extra rests you need to select the relevant staff and from the edit menu select Voices and choose to Exchange voice 1-2, 1-3 depending on the colour green or red that you have. This will swap over the notes to voice 1, leaving the line of rests in voice 2 or 3. You can then then use the filter to select just the extra rests (leaving the notes black) by deselecting voice 1, and then delete the rests with the delete key.

Two final tips. If your piece contains phrasing slurs as opposed to note slurs preventing a note from sounding twice then these should be deleted by selecting them and then pressing delete. Finally if you have repeats or ornaments like mordents in the piece these will not be rendered by the conversion script. The way round this is to export the piece in its present form as a midi file and then open it in a fresh MuseScore. This will explicitly include the ornaments written out as individual notes, and also will write out the repeats. You can then create separate parts and export these as musicxml files as discussed previously and run the script on them.

Errors when running the script. I have encountered the following.

The file “xxxxxxxxxxxx.xml” is missing or inaccessible Fairly self explanatory. Check that the file is in the data folder and that you have copied its name correctly. Also check that you have exported parts as .xml not as .mid

ArrayIndexOutOfBoundsException: 2000  This occurs when the script has tried to access a particular array (highlighted) with an index which is out of the declared range. Solution declare the array with a larger index. (I have increased most arrays so you are unlikely to get this now) -The 2000 shows the size specified in the example error

ArrayIndexOutOfBoundsException with  array t1[2] often accompanied by warning messages printed in output screen. Usually this is caused by errant ties in the part, or by a second voice in the part. Check the MuseScore file carefully and eliminate these.

If you get warning messages when the script runs, without the script halting, then the result will probably run in sonic pi, but will probably have some timing errors in it. The aim is to have a warning free run.

Finally I have found that this script gives a new lease of life to my work with Sonic Pi. It has also enabled me to get to grips with the excellent MuseScore program which I had not significantly used before. I think that the script could possibly be extend further, but already it is a very useful tool, in enabling a wider variety of music to be used with Sonic Pi, without the very tedious task of typing all the notes in from scratch.

A link to a video of my biggest conversion In this case I have used sample voices to play the notes converted from the original midi file. The piece lasts over 23 minutes and was composed by my late Father.

NEW added page of gotchas to be aware of when using the script here