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() { drumScore.addPart(drums); changeDynamics(); } public void changeDynamics() { 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()
and stopPlayback() 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.
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); }
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).
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
|