Extended Drum Kit: Introducing classes with multiple methods

This demo class enhances the simple drum kit class in a number of ways. So hold on to your hat!

Firstly, the structure of the class is reorganised into a more object-oriented design where different sections are divided into separate methods (functions). In this way, the class is more reflective of a conventional Java program. This is a breakaway from what we have been doing in earlier tutorials where the entire process has been compacted into the 'main' method .... but more on this later.

Secondly, in order to make the drum patterns more interesting there are numerous random changes throughout.

Thirdly, rather than storing all the drum phrases in one part, in this class each drum type (snare, kick, etc.) are stored in their own part. Why? So far in these tutorials jMusic files have been exported as MIDI files. As MIDI files all drums share the one MIDI channel and so jMusic drum parts are combined during the conversion process into one MIDI track. However, jMusic can also output audio files and to do so each part needs to have a different instrument - snare is different to kick which is different to closed hihat which is different to open hihat. Hence, this class creates a jMusic score with each instrument having its own part so it can be written as either a MIDI or an audio file. In this tutorial we will only deal with the MIDI file exporting.

Have a listen to the MIDI playback below to hear the result.

Click here to view source.

This class divides the work of the program into several sections. After the class declares its class variables we start by running the main() method. It immediately calls the constructor which, in turn, calls methods to create the bass drum, snare and hi hat phrases. Next, the doScore() method puts all the phrases into parts and into a score. Control passes back to the main() method which sets the tempo and saves the score as a MIDI file. Here is a diagram of that passing of control between the methods.

Let's have a closer look at the code.

  import jm.JMC;
import jm.music.data.*;
import jm.util.Write;
import jm.music.tools.Mod;

Lines 1-3 import useful packages that we need to use in the program.

  	private Score pattern1 = new Score("JMDemo - Kit");   
	// "kick" = title, 0 = instrument (kit), 9 = MIDI channel 10  
	private Part drumsBD = new Part("Kick",0, 9); 
	private Part drumsSD = new Part("Snare",0, 9);
        	private Part drumsHHC = new Part("Hats Closed",0, 9); 
	private Part drumsHHO = new Part("Hats Open",0, 9); 
	private Part drumsCY = new Part("Cymbal",0, 9);
	private Phrase phrBD = new Phrase(0.0);
	private Phrase phrSD = new Phrase(0.0);
	private Phrase phrHHC = new Phrase(0.0);
	private Phrase phrHHO = new Phrase(0.0); 
	private Phrase end = new Phrase(64.0);  
	//define repeatedly used rests  
	private Note restSQ = new Note(REST, SEMI_QUAVER);   //quater note rest  
	private Note restC = new Note(REST, CROTCHET);   //eight note rest  

The code above declares all the class variable for use in this program. Class variables, declared outside methods, can be used by any methods within the class. These variables are declared private because they only need to be accessed from within this class, not from other classes. They need to be static because the class is static, a result of having a static method; main() in this case.

 	public static void main(String[] args){
ExtendedKit ek = new ExtendedKit();
}

The main() method will be the first to be executed when the program runs.

  	public ExtendedKit() {		  
            //Let us know things have started  
            System.out.println("Creating drum patterns . . .");
            this.doBassDrum();
            this.doSnare();
            this.doHiHats();
            Note crashSB = new Note(49, SB); // crash
            end.addNote(crashSB);
            this.doScore();
            Write.midi(pattern1, "ExtendedKit.mid");
	}  

The constructor method always has the same name as the class. It also does not require a return type in the declaration (notice there is no void keyword as in the other methods). After printing a message to the standard output the constructor calls each of the other methods in the class one after another to do their bit of the job. The crash cymbal part is so trivial (one note) that the constructor does that itself rather than use a different method. After each part is created the doScore() method packs all the parts into a score.

Notice that each of the method class begins with the word this. That indicates that the method is in this class. Often the keyword this is left off method calls - in which case it is assumed - but it is good practice to include it as it reminds you of exactly what is going on.

  	private void doBassDrum() {
System.out.println("Doing kick drum. . .");
for(int i=0;i<4;i++){
Note note = new Note(36, C);
phrBD.addNote(note);
phrBD.addNote(restC);
}
Mod.repeat(phrBD, 8);
}

The doBassDrum() method first prints a message confirming that it has been called. There are two for-loops, one inside the other. We say the loops are nested. The outer loop, which uses the r index counter, forces the inner loop to repeat 8 times over. The inner loop, whic uses the i index counter, adds a note and a rest to the phrBD phrase each time around the loop. resulting in four note and rest pairs. A two bar pattern in all (assuming simple quadruple time), 16 bars in all.

  	private void doSnare() {
System.out.println("Doing snare. . .");
for(int j=0;j<16;j++){
//repeat for 16 bars of 4/4 phrSD.addNote(restC); Note snareC = new Note(38, CROTCHET); phrSD.addNote(snareC); phrSD.addNote(restC); for(int i=0;i<3;i++){ //vary the last crotchet beat each bar int rand = (int)(Math.random()*3); if (rand > 1) { Note snareSEMI_QUAVER = new Note(
38, SEMI_QUAVER); phrSD.addNote(snareSEMI_QUAVER); } else { phrSD.addNote(restSQ); } } phrSD.addNote(restSQ); } }

The doSnare() method is a more involved version of the bass drum method. There are two loops, the i loop nested inside the j loop. The j loop repeats 16 times and the i loop produces a one bar phrase. The first three beats of the phrase are created in the j loop, a rest, a note, and another rest - each a crotchet (quarter note) long.

The first three semiquavers (16th notes) of the final beat are created in the i loop. There is a 1 in 3 chance (at random) of a note being generated on each semiquaver, otherwise a rest is used.

The final semiquaver pulse of the bar is always set as a rest after the i loop has completed.

  	private void doHiHats() {
System.out.println("Doing Hi Hats. . .");
for(int r = 0; r < 8; r++){
//repeat for 8 two bar cycles //start with closed hat Note note1 = new Note(42, SEMI_QUAVER,
(int)Math.random()*80+45); phrHHC.addNote(note1);
//add a rest to the other HH part so they align
phrHHO.addNote(restSQ);
for(int i=0;i<30;i++){ int rand = (int)(Math.random()*16); if (rand < 1) { //select occasional open hi hat Note note5 = new Note(46, SEMI_QUAVER,
(int)Math.random()*80+45);
phrHHO.addNote(note5);
phrHHC.addNote(restSQ);
} else {
Note note2 = new Note(42, SEMI_QUAVER,
(int)Math.random()*80+45);
phrHHC.addNote(note2);
phrHHO.addNote(restSQ);
}
}
// open hi hat at the end of the pattern
Note note6 = new Note(46, SEMI_QUAVER, 60);
phrHHO.addNote(note6); phrHHC.addNote(restSQ); } }

Once you understand the snare method, the doHiHats() method should look familiar. There are two loops, one nested inside the other. The outside loop, r, repeats the inner process sight times. Inside the r loop a closed hihat is added at the start of each two bar group.

Notice, that this method does two phrases at once. It fills the closed and open hihat phrases at the same time. In order for them to be in sync (that is no open and closed hats together) rests are added to the alternate phrase for every note added to the primary phrase.

The next 30 semiquavers are determined inside the i loop which chooses a closed (pitch 42) or open (pitch 46) hihat note. There is a one in sixteen chance of getting an open hihat - so mostly we will get closed hihats. Additionally, the dynamic value of each note is determined at random between 45 and 125.

On completion of the i loop control drops back out to the r loop where the last semiquaver of the group will always be a semiquaver note.

  	private void doScore() {  
		// add phrases to the parts  
		System.out.println("Assembling. . .");
		drumsBD.addPhrase(phrBD);
		drumsSD.addPhrase(phrSD);
		drumsHHC.addPhrase(phrHHC);
		drumsHHO.addPhrase(phrHHO);
		drumsCY.addPhrase(end);
		  
		// add the drum parts to a score.  
		pattern1.addPart(drumsBD);
		pattern1.addPart(drumsSD);
		pattern1.addPart(drumsHHC);
		pattern1.addPart(drumsHHO);
		pattern1.addPart(drumsCY);
	}  

Now its time to bring all the drum phases and parts together into a score. In this method each phrase is added to the appropriate part - one for each the instrument sound. Then each of the parts is added to the score, called pattern1.

After this method control passes back to the constructor method which, now also complete, passes control back to the main() method for saving the now complete score as a MIDI file.

Audio:

For those of you who can't wait to get the audio version of this class working, below are links to the source and audio samples required.

Click here to view source.

Drum samples required - download these files: Kick, Snare, Hats, OpenHH



jMusic Tutorial Page




© 2001 Andrew R. Brown