JM-550 Drum Machine: No GUI version

This series of classes introduces a simple, but extendible, drum machine program. It is a series of jm-550 tutorials (influenced by the drum patterns of the Boss DR-550 drum machine) which show the development of a Java graphical user interface for a jMusic application. This first tutorial looks at the application without any GUI code, in the next tutorial some simple start and stop buttons are displayed in a window frame, then sliders are added to control tempo and volume, then a list of drum patterns is added. So here goes...

This tutorial introduces the abstract class underlying the JM-550 classes. There is a more detailed jMusic tutorial on abstract classes elsewhere and so here we will be focussing more on the implementation of the graphical user interface as this series of jm-550 tutorials proceeds.
Listen to a MIDI file generated by this program:

There are three classes dealt with in this tutorial. The abstract class (Basic_550) and two extensions of it.
Click here to view Basic_550 source.
Click here to view EightBeat1 source.
Click here to view EightBeat1FillIn source.
Lets have a closer look.

abstract class Basic_550 implements JMC {
protected static Score drumScore = new Score("Drums", 130.0);
protected static Part drums = new Part("Drum Part", 0, 9);
static double barLength = 4.0;
private static int scoreVolume = 100;
private static int dynDeviation = 8;

public Basic_550() {
drumScore.empty();
drums.empty();
makePattern();
}

public void makePattern() {
// add the drum part to the score
drumScore.addPart(drums);
changeDynamics();
}

public void changeDynamics() {
// change volume
int tempDyn = 100;
for (int i = 0; i < drums.size(); i++) {
for(int j = 0; j < drums.getPhrase(i).size(); j++) {
do {
tempDyn = (int)(Math.random() * dynDeviation +
scoreVolume - dynDeviation/2);
} while (tempDyn < 0 || tempDyn > 127);
drums.getPhrase(i).getNote(j).setDynamic(tempDyn);
}
}
}

public void playback(){
Play.midiCycle(drumScore);
}

public void stopPlayback(){
Play.stopCycle();
}

After some import statements (not shown) the class is declared and some static class variables created. These variable, such as the Score drumScore, will be accessible by all the classes that extend this one. We only need declare them here once which will save time for the classes that implement each of the drum patterns.

The constructor empties the score and drum part of any previous content (the first time through this is unnecessary but subsequently it is required) then calls the makePattern() method. 

The makePattern() method simply adds the drum part to the score, then calls changeDynamics() to adjust the dynamic of every note in the score to the current volume level (this is set at present but later on we'll add a slider in the GUI to change this dynamically). Simply adding a part seems trivial - especially considering that up to this point the part appears to be empty - but in the classes that extend this one their role will be to fill the part with phrases for each of the drums (kick, snare, cymbals etc.).

The last two methods are fairly basic:  playback()andstopPlayback() do exactly that - they can be called to play or stop the percussion loop.  This function is used in the next tutorial. 

	public void setTempo(double tempo){
drumScore.setTempo(tempo);
}

public static void setBarLength(double newLength) {
barLength = newLength;
}

public static double getBarLength() {
return barLength;
}

public static Score getScore() {
return drumScore;
}

public static int getScoreVolume() {
return scoreVolume;
}

public static void setScoreVolume(int newVolume) {
if (newVolume > 1 && newVolume < 128) scoreVolume = newVolume;
}

public static int getDynDeviation() {
return dynDeviation;
}

public static void setDynDeviation(int newDeviationValue) {
if (newDeviationValue > 1 && newDeviationValue < 128)
dynDeviation = newDeviationValue;
}

Above is the code for access methods for any of the class variables we might want to use later on.  The exception is setTempo() which is NOT a class variable, rather it changes the property of the drumScore, so it cannot be static.  In typical Java style all these methods are labelled getXXX and setXXX where XXX is the name of the variable or property.

	// this method used by many others below
private void makePhrase(int pitch, double[] rhythmArray,
double startTime, int repeats) {
Phrase phrase = new Phrase(0.0);
if (startTime > 0.0) phrase.addNote(new Note(REST, startTime));
phrase.addNoteList(pitches(pitch, rhythmArray), rhythmArray,
dynamics(120, 4, rhythmArray));
if (repeats > 0) Mod.repeat(phrase, repeats);
drums.addPhrase(phrase);
}

// convert a single pitch into a pitch array of the
// length matching the rhythm array.

private int[] pitches(int pitch, double[] rhythmArray) {
int[] pitchArray = new int[rhythmArray.length];
for (int i = 0; i < rhythmArray.length; i++) {
pitchArray[i] = pitch;
}
return pitchArray;
}

In these methods we find the 'hard yakka' (Australian colloquialism for work) of this class. To make the extended classes as simple as possible these classes take input in the form of a pitch (for the particular drum we are concerned with, i.e., 36 for kick drum in general MIDI devices), an array of the rhythm values, and the start time of the rhythms (for example the snare may not start until the second beat of the bar).

    // kick
protected void kck(double[] rhythmArray) {
this.kck( rhythmArray,0.0);
}
 
protected void kck(double[] rhythmArray, double startTime) {
this.kck( rhythmArray, startTime, 0);

}
protected void kck(double[] rhythmArray, double startTime, int repeats) {
makePhrase(C2, rhythmArray, startTime, repeats);
}
For each of the parts of the 'drum kit' there are a set of three overloaded methods, as above for the kick drum. These call the makePhrase() method for the appropriate drum and with a particular rhythm. It is these methods that are called by the drum pattern classes that extend Basic_550.

To see all the percussive instruments supported by this class check the source code.


OK. Now lets look at the extended classes - the ones we actually run. First the EightBeat1 class.

import jm.JMC;
import jm.music.data.*;
import jm.music.tools.*;
import jm.util.*;
 
public class EightBeat1 extends Basic_550 {
static double[] kckRhythmArray = {2.0, 0.5, 1.5};
static double[] snrRhythmArray = {2.0, 1.0};
static double[] chhRhythmArray = {0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5};

static double barLength = 4.0;
static double[] accents = {1.0, 2.0, 3.0, 4.0};

public static void main(String[] args) {
new EightBeat1();
View.print(drumScore);
Write.midi(drumScore, "EightBeat1.mid");
}

public EightBeat1() {
kck(kckRhythmArray);
snr(snrRhythmArray, 1.0);
chh(chhRhythmArray);
Mod.accents(drumScore, barLength, accents);
}
}

It creates an array for the rhythm of each part of the drum kit used in the EightBeat pattern. Also its sets up attributes of the accents() call - more on this latter.

The main method calls the default constructor - which in turn runs the constructor of the super class, Basic_550. Then the score is printed to the standard output for verification and saved as a MIDI file.

The constructor first runs the constructor of its super class (this is a standard function in Java when one class extends another) then calls the kck(), snr(), and chh() methods inherited from Basic_550 to create the kick, snare and closed hi hat phrases. Finally the Mod.accents() method is called which applies a slight dynamic boost to notes occurring on particular beat locations - specified in the accents array. Many of the patterns on the DR-550 drum machine used this accent function which is why it is replicated here. This and the slight dynamic deviation applied in the Basic_550 class provide some 'feel' to the drum patterns.


The fill-in class follows the same procedure as above, but for a different drum pattern. Notice that it uses low, mid and high toms and a crash cymbal, and pedal hi hats rather than closed hihats. It's accent points are more complex as well.

import jm.JMC;
import jm.music.data.*;
import jm.music.tools.*;
import jm.util.*;
 
 
public class EightBeat1FillIn extends Basic_550 {
static double[] kckRhythmArray = {3.0, 1.0};
static double[] snrRhythmArray = {0.25, 0.25};
static double[] crshRhythmArray = {4.0};
static double[] phhRhythmArray = {1.0, 1.0, 1.0, 1.0};
static double[] ltmRhythmArray = {0.5, 0.5, 1.25};
static double[] mtmRhythmArray = {0.5, 2.75};
static double[] htmRhythmArray = {0.25, 3.75};

static double[] accents = {1.0, 2.0, 2.25, 3.0, 3.25, 4.0};

public static void main(String[] args) {
setBarLength(4.0);
new EightBeat1FillIn();
View.print(drumScore);
Write.midi(drumScore, "EightBeat1FillIn.mid");

}

public EightBeat1FillIn() {
kck(kckRhythmArray);
snr(snrRhythmArray, 3.0);
crsh(crshRhythmArray);
phh(phhRhythmArray);
ltm(ltmRhythmArray, 1.75);
mtm(mtmRhythmArray, 0.75);
htm(htmRhythmArray);
Mod.accents(drumScore, barLength, accents);
}
}

Move on to the next tutorial which adds a graphical user interface to these classes:
JM-550 Drum Machine: Simple AWT GUI




jMusic Tutorial Index


© 2001 Andrew R. Brown