Making Music with Java: I'll be Bach—Part 1

Version 7

    by James Weaver

     

    A musical example of migrating C code to Java

     

    It has long been recognized that there is a correlation between software development aptitude and music appreciation. This is the first in a series of articles that leverages that correlation by featuring aspects of Java (and related APIs) for developing applications in the music domain. The applications in this series typically perform music analysis or algorithmic composition.

     

    The application that I'd like to begin highlighting in this article is one that I developed, known as Counterpoint Composer.  This application will serve, among other things, as an example of migrating a procedural C program into an object-oriented Java program.   Before diving into relevant portions of the code, it will be helpful to explore a bit of an introduction to counterpoint music.

     

    Understanding Counterpoint Music

     

    Counterpoint music is characterized by the relationship between voices that are interdependent harmonically yet independent in rhythm and contour.  For example, take a look at the following music score and notice that there are four independent melody lines:

     

    f1.png

    Figure 1. Example of counterpoint music

     

    Even though the melody lines have four different rhythms and contours, when played together, they produce a harmonically pleasing song.  For an example, watch Video 1 and listen for the interdependent harmonies and independent rhythms and contour:

     

    Video 1. Example of counterpoint music

     

    Counterpoint music has been around for a long time, and has been written by many famous composers.  In the 1700s, composers such as Giovanni Pierluigi da Palestrina and Johann Joseph Fux created rules for writing a style of counterpoint known as species counterpoint.  Over 70 rules for species counterpoint were created, mainly for teaching students how to compose counterpoint, and Johann Fux published them in a manuscript called Gradus ad Parnassum.  These rules for species counterpoint influenced the music of Hayden, Mozart, and Beethoven.

     

    In species counterpoint, there are five levels, known as species, of progression.  For example, the first species is known as note-on-note, and is characterized by two melody lines that each consist entirely of whole notes.  To become familiar with creating first-species counterpoint, take a look at Video 2.  It walks through the process of using the Counterpoint Composer application to automatically compose a counter-melody to the familiar melody known as "Twinkle Twinkle, Little Star."

     

    Video 2. Example of using Counterpoint Composer to create a counterpoint

     

    As shown in Video 2, Figure 2 shows the main melody of "Twinkle Twinkle, Little Star," which is also known as a cantus firmus.  Note that the initial chord contains the number of desired melodies and their starting notes.

     

    f2.png

    Figure 2. Main melody, including the initial chord

     

    If you'd like to play with Counterpoint Composer, as shown in Video 2, here are the steps to perform:

     

    1. Click the Clear menu item to clear the staff.

     

    2. Click the piano icon icon1.jpg to reveal the piano keyboard.

     

    3. Click in the staff icon2.jpg to give it focus.

     

     

    4. While pressing the Shift key, click each of the notes in the initial chord.

     

    5. Release the Shift key and click the remaining notes.

     

    6. Verify that First species and Ionian mode are selected in the menu bar.

     

    7. Click the Compose menu item.

     

    The result should be similar to the musical score shown in Figure 3:

     

    f3.jpg

    Figure 3. "Twinkle Twinkle" in first-species counterpoint

     

    Clicking the Listen button icon3.jpg will play the composition.

     

     

    Now that you've had an introduction to counterpoint music, and you've seen a portion of the Counterpoint Composer application, let's examine the overall architecture of the application.

     

    High-Level Architecture of Counterpoint Composer

     

    As you saw in the previous section, Counterpoint Composer is a web-based application.  Architecturally, the application consists of a single HTML5 page and a few RESTful services.  Figure 4 illustrates the high-level architecture of Counterpoint Composer:

     

    f4.jpg

    Figure 4. Counterpoint Composer high-level architecture

     

    The RESTful services are located in two clouds in the diagram.  The top cloud in the diagram represents a Cloud Foundry instance hosted by Pivotal Web Services.  Inside that Cloud Foundry instance are two applications (CounterpointService and ChordAnalyzerService) that are written in Java and use the Spring framework to provide various RESTful services.

     

    The smaller cloud in the diagram represents RESTful services provided by Noteflight that enable musical input, notation, and playback.

     

    The next article in this series will take a deeper dive into the architecture and will walk through lots of the code in the Counterpoint Composer application.  In the balance of this article, I'd like to focus on some of the code that exists in the box labeled CounterpointService.

     

    Migrating a Procedural C Program to Java

     

    As mention previously, Johann Fux expressed what constitutes species counterpoint by defining over 70 rules.  Johann's aim was to teach students the art of music composition, but those same rules are being leveraged to also teach computers how to compose counterpoint.  For example, Counterpoint Composer, developed in the Java programming language, expresses those rules in Java.  This application builds on the work of Bill Schottstaedt, who coded those rules using the SAIL and C programming languages in the 1980s.

     

    One of the challenges of creating Counterpoint Composer was to migrate hundreds of lines of Bill Schottstaedt's procedural C code to Java.  This migrated Java code is accessible behind REST services represent by the CounterpointService box in Figure 4.  Bill Schottstaedt's C code was designed to be run from the command line, taking arguments that represent things that were mentioned earlier—such as the main melody, the initial chord, the musical mode, and the species type.  By contrast, the migrated Java code in the Counterpoint Composer application is part of the implementation of a REST service, not run from the command line.

     

    In spite of the inherent differences cited above, because many of the keywords and constructs in Java are similar to their C counterparts, migrating the code was in some ways straightforward.  For example, the C code in Listing 1 ascertains the type of motion that notes in two different melody lines have relative to each other:

     

    #define DirectMotion 1
    #define ContraryMotion 2
    #define ObliqueMotion 3
    #define NoMotion 4
    
    int MotionType(int Pitch1, int Pitch2, int Pitch3, int Pitch4)
    { 
      if ((Pitch1 == Pitch2) || (Pitch3 == Pitch4)) 
        {       if ((Pitch1 == Pitch2) && (Pitch3 == Pitch4)) 
            return(NoMotion); 
          else return(ObliqueMotion); 
        } 
      else 
        { 
          if ((Pitch2-Pitch1)*(Pitch4-Pitch3)>0) 
            return(DirectMotion); 
          else return(ContraryMotion); 
        } 
    }
    

     

    Listing 1. MotionType() function in C programming language

     

    Please compare the C code in Listing 1 with the Java code in Listing 2:

     

    static int directMotion = 1; 
    static int contraryMotion = 2; 
    static int obliqueMotion = 3; 
    static int noMotion = 4; 
    
    int motionType(int pitch1, int pitch2, int pitch3, int pitch4) { 
      if ((pitch1 == pitch2) || (pitch3 == pitch4)) { 
        if ((pitch1 == pitch2) && (pitch3 == pitch4)) { 
          return (noMotion); 
        } 
         else { 
          return (obliqueMotion); 
        } 
      } 
       else { 
        if ((pitch2 - pitch1) * (pitch4 - pitch3) > 0) { 
          return (directMotion); 
        } 
         else { 
          return (contraryMotion); 
        } 
      } 
    }
    

     

    Listing 2. motionType() method in Java programming language

     

    Notice that in both listings, the syntax of defining C functions and Java methods are very similar.  Also, the if-else clauses are the same, as are the return statements.  In fact, other than the use of #define versus static int, and identifier naming conventions, Listing 1 and Listing 2 are very similar.

     

    Of course, not all keywords and constructs are the same between C and Java, so there were several areas in which migration wasn't as straightforward.  One of these areas has to do with the difference between C pointers and Java references.

     

    Moving from C Pointers to Java References

     

    In the C program, a utility function named ARRBLT is defined, as shown in Listing 3:

     

    void ARRBLT(int *dest, int *source, int num) { 
      int i; 
       for (i=0;i<num;i++) dest[i]=source[i]; 
    }
    

     

    Listing 3. Code from the C program that uses pointers

     

    The code in Listing 3 defines a function named ARRBLT() that utilizes C pointers to copy the contents of an array to another location in memory.  Java has a notion similar to C pointers, which is known as Java references, but one important difference is that the Java runtime manages its own memory.  This is a very good thing from the standpoints of security and memory management, but as a result, we can't write directly to a memory location.  Listing 4 shows the Java code used to perform similar functionality:

     

    void arrBlt(int array[], int destIdx, int sourceOffset, int numToCopy){ 
      int i; 
      for (i = 0; i < numToCopy; i++) { 
        array[i] = array[i + sourceOffset]; 
      } 
    }
    

     

    Listing 4. Code from the Java program that uses references

     

    The code in Listing 4 defines a method named arrBlt() that has four parameters, one of which is a reference to an array.  The for loop in this method copies a portion of the contents of the array to another location in the same array.  Note that the array must have already been defined with enough elements to hold the results of the copied elements.  Listing 5 shows how this was accomplished in the C program and in the Java program.

     

    // Allocating memory to hold an array in the original C code 
    Pens=(int *)calloc(1+(Field*NumFields),sizeof(int)); 
    
    // Allocating memory to hold an array in the Java code 
    pens = new int[1 + (field * numFields) + field];
    

     

    Listing 5. Allocating memory for an array in C versus Java

     

    Now I'd like to point out one more difference between C and Java that was relevant in migrating the counterpoint code.

     

    Migrating Boolean Data Types Represented by int in C

     

    The C code that I ported to Java was developed in the 1980s, long before a Boolean data type called _Bool was introduced in C99.   Consequently, Boolean values in the code were represented by int data types.  Listing 6 contains a snippet of relevant C code:

     

    int ASeventh(int Interval) { 
      return((Interval == MinorSeventh) || (Interval == MajorSeventh)); 
    }
    ... 
    int Above; 
    ... 
    Above=(Interval >= 0); 
    ... if (Above) { 
      if (!(ASeventh(LastIntClass))) { 
        Val += DissonancePenalty; 
      }
    } 
    else { 
      if (LastIntClass != Fourth) { 
        Val += DissonancePenalty;
       } 
    }
    

     

    Listing 6. Using int data type in C to represent Boolean data

     

    In Listing 6, we see that the variable named Above is defined as an int, but it is used in logical operations.  When the value of Above is zero, it represents a logical false, and when it is nonzero, its logical value is true.  Similarly, the ASeventh() function used in the snippet is defined as returning an int but is used in Boolean operations.  Compare the C code snippets in Listing 6 with the migrated Java code in Listing 7, where Boolean data types are used:

     

    boolean aSeventh(int interval) { 
      return ((interval == minorSeventh) || (interval == majorSeventh)); 
    }
    ... 
    boolean above; 
    ...
    above = (interval >= 0);
    ...
    if (above) {
       if (!(aSeventh(lastIntClass))) {
         val += rp.getDissonancePenalty();
       }
    }
    else {
       if (lastIntClass != fourth) {
         val += rp.getDissonancePenalty();
       }
    }
    

     

    Listing 7. Migrated code using boolean data type

     

    Another factor when migrating from procedural C to object-oriented Java code is noticeable when comparing Listing 6 and Listing 7.  There are often opportunities to decompose code into classes, as is the case where the various types of penalties (for example, DissonancePenalty) are represented in their own class (for example, RulePenalties) and given methods (for example, getDissonancePenalty()).

     

    Conclusion

     

    This first article in the "Making Music with Java" series gave you an introduction to counterpoint music and helped you get started using the Counterpoint Composer application.  It then gave a very high-level view of the application's architecture followed by a discussion of one of the aspects of developing this application, which involved migrating existing C code to Java.  The next article will drill down into the application's architecture and highlight additional relevant code.

     

    See Also

     

     

    About the Author

     

    James Weaver is a Java developer, author, and speaker with a passion for helping Java to be increasingly leveraged in rich-client and cloud-native applications. James has authored and coauthored many books, including Inside Java, Beginning J2EE, the Pro JavaFX series, and Raspberry Pi with Java: Programming the Internet of Things (IoT). As a Pivotal Evangelist, he speaks internationally at software technology conferences about Java and cloud-native development. James tweets at @JavaFXpert, blogs at http://JavaFXpert.com and http://CulturedEar.com, and can be reached at jweaver AT pivotal.io.

     

    Join the Conversation

     

    Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!