Geertjan is a DZone Zone Leader and has posted 466 posts at DZone. You can read more from them at their website. View Full User Profile

Interview: JFugue Goes Hip Hop

05.07.2008
| 6914 views |
  • submit to reddit
A new release of JFugue API was announced last week. Its author, Dave Koelle, is at JavaOne and here he talks about the API and some of its hidden treasures.

For those who don't know, JFugue is a Java API that makes programming music as easy as writing one or two lines of code, such as the following:

Player player = new Player();
player.play("C D E F G A B");

After a successful session last year, Dave will again present some cool and fun JFugue API stuff tomorrow, in a technical session at 16.10 in room 303, as well as in a BOF later in the day. Below, he talks about some of the lesser known but very cool features of the API, that you'll see in action if you go to his session.

Dave, you're back at JavaOne with JFugue. Let's not beat around the bush and get down to some brass tacks right away. Show us something really cool!

OK. We'll look at some real code, three examples, which result in the Midi files that you can download via a ZIP file by clicking here. So, let's first look at a lesser used feature of the JFugue API, not quite as popular as most of the others, but still very cool. It allows you to create a rhythm by just banging keys on your keyboard, which is what the block of code below illustrates:

rhythm.setLayer(1, "O..oO...O..oOO..");
rhythm.setLayer(2, "..*...*...*...*.");
rhythm.setLayer(3, "^^^^^^^^^^^^^^^^");
rhythm.setLayer(4, "...............!");

With JFugue API, you can map keys to instruments. For example, the letter "o" could be a bass drum, the asterisk could be a snare drum, the exclamation mark something else, and so on. Look at the 16-beat rhythm code below and see how easily this mapping can be done:

    public static void beat16()
{
Rhythm rhythm = new Rhythm();

// Each MusicString should have a total duration of an eighth note
rhythm.addSubstitution('O', "[BASS_DRUM]i");
rhythm.addSubstitution('o', "Rs [BASS_DRUM]s");
rhythm.addSubstitution('*', "[ACOUSTIC_SNARE]i");
rhythm.addSubstitution('^', "[PEDAL_HI_HAT]s Rs");
rhythm.addSubstitution('!', "[CRASH_CYMBAL_1]s Rs");
rhythm.addSubstitution('.', "Ri");

rhythm.setLayer(1, "O..oO...O..oOO..");
rhythm.setLayer(2, "..*...*...*...*.");
rhythm.setLayer(3, "^^^^^^^^^^^^^^^^");
rhythm.setLayer(4, "...............!");

Pattern pattern = rhythm.getPattern();
pattern.repeat(4);

Player player = new Player();
player.play(pattern);

try {
player.saveMidi(pattern, new File("beat16.mid"));
} catch (IOException e)
{
e.printStackTrace();
}
}

This is really cool. Why is it a lesser known feature that is not really used?

It is probably less known because, from my understanding of how the API is used, banging a keyboard is not a typical way of making music. To use this approach, people need to break with their mental model of sheet music and, instead, expand their thinking into new ways of how music is made. However, the result looks a lot like a beatbox. People are used to that concept, but here it's a little bit cooler than a beatbox because once you get a "JFugue pattern" (more on this below) you can manipulate it in unique and interesting ways.

So how does this really work under the hood, i.e., how do you map keystrokes to instruments?

A hashmap is created that maps the letters to the instruments. Then long strings are created that simply use those substitutions. The strings are JFugue music strings that the JFugue parser can interpret to make Midi events. In fact, this is one of the first features requested by a member of the JFugue community.

Since when has this "keyboard banging" feature been part of the API?

It's been in there since JFugue 3.0.

And now version 4.0 has come out, just in time for JavaOne. What does this release give me?

Several new features, in fact. For example, JFugue API now supports chord inversions and tuplets, both of which were requested by members of the user community. This latest release integrates a Michael Good's Music XML format, which was a contribution from a member of the user community, called E. Phil Sobolik. And there have been improvements in the way patterns in the API can be manipulated.

For those of us who don't know, what is a JFugue pattern?

A pattern is a snippet of music that can be reused and recombined. For example, a lot of the music we listen to today has repeating segments of music. JFugue alllows you to specify a repeating segment of music once and then reuse that pattern.

So, what's new with patterns in this latest release?

One cool feature of JFugue is that you can read existing Midi files into a pattern and then you can transform that pattern to make new music (provided you have legal rights to that music in the first place, of course!). The new version of JFugue improves the way in which patterns can be transformed. Specifically, I improved the interface.

As an example, what we are going to do below is take some music from an existing music file (again, assuming we have permission to change it), then we do some simple cleaning up of the Midi, after which we invert the pattern around a note. Next, we reverse the pattern and change the interval of the notes in the pattern. Then, we reverse the pattern again and add it to itself. Finally, we have a new piece of music, a fugue, which uses this pattern and plays it on top of itself with different instruments.

All the code below is compilable, i.e., you can simply copy and paste it into your own application, put the latest JFugue API JAR on the classpath, and then simply run it. Make sure to replace "mymusic.mid" in line 112 in the code below with the name and location of a file that you have access to:

import java.io.File;
import java.io.IOException;

import javax.sound.midi.InvalidMidiDataException;

import org.jfugue.Instrument;
import org.jfugue.MusicStringParser;
import org.jfugue.Note;
import org.jfugue.Pattern;
import org.jfugue.PatternTransformer;
import org.jfugue.Player;
import org.jfugue.extras.DurationPatternTransformer;
import org.jfugue.extras.GetPatternForVoiceTool;
import org.jfugue.extras.IntervalPatternTransformer;
import org.jfugue.extras.ReversePatternTransformer;

public class RemixMidi {

private Player player;
private File file;

public RemixMidi(File file) {
this.player = new Player();
this.file = file;
}

/** Loads a MIDI file, and converts the MIDI into a JFugue Pattern. */
public Pattern getPattern() {
Pattern pattern = null;
try {
pattern = player.loadMidi(file);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pattern;
}

/**
* Using the GetPatternForVoiceTool, isolate a single voice (or channel) of the
* Pattern and return it.
*/
public Pattern getPatternForVoice(Pattern pattern, int voice) {
GetPatternForVoiceTool tool = new GetPatternForVoiceTool(voice);

MusicStringParser parser = new MusicStringParser();
parser.addParserListener(tool);
parser.parse(pattern);
Pattern voicePattern = tool.getPattern();

return voicePattern;
}

/** Plays a Pattern - this is a pass-through method to JFugue's Player */
public void play(Pattern pattern) {
player.play(pattern);
}

/**
* Using the InvertPatternTransformer, invert the given pattern.
*/
public Pattern invertPattern(Pattern pattern) {
InvertPatternTransformer ipt = new InvertPatternTransformer(MusicStringParser.getNote("C5"));
Pattern invertPattern = ipt.transform(pattern);
return invertPattern;
}

/**
* Using the ReversePatternTransformer, reverse the given pattern.
* "C D E" becomes "E D C"
*/
public Pattern reversePattern(Pattern pattern) {
ReversePatternTransformer rpt = new ReversePatternTransformer();
Pattern reversePattern = rpt.transform(pattern);
return reversePattern;
}

/**
* Causes the duration of each note in the Pattern to be lengthened
* by the provided factor.
*/
public Pattern stretchPattern(Pattern pattern, double stretch) {
DurationPatternTransformer dpt = new DurationPatternTransformer(stretch);
Pattern stretchedPattern = dpt.transform(pattern);
return stretchedPattern;
}

/**
* Changes the note values of each note in the Pattern - this causes
* the entire Pattern to be played with higher or lower pitches.
*/
public Pattern changeInterval(Pattern pattern, int delta) {
IntervalPatternTransformer it = new IntervalPatternTransformer(delta);
Pattern intervalPattern = it.transform(pattern);
return intervalPattern;
}

public Pattern cleanInstrument(Pattern pattern) {
PatternTransformer t = new PatternTransformer() {

@Override
public void instrumentEvent(Instrument instrument) {
// Do nothing
}
};
Pattern cleanedPattern = t.transform(pattern);
return cleanedPattern;
}

private static void remixBach() {
RemixMidi mix = new RemixMidi(new File("mymusic.mid"));
Pattern pattern = mix.getPattern();
System.out.println("From midi: " + pattern);
Pattern voicePattern = mix.getPatternForVoice(pattern, 0);

Pattern cleanInstrumentPattern = mix.cleanInstrument(voicePattern);

Pattern invertedPattern = mix.invertPattern(cleanInstrumentPattern);
System.out.println("After inversion: " + invertedPattern);

Pattern reversedPattern = mix.reversePattern(invertedPattern);
System.out.println("After reversal: " + reversedPattern);
// mix.play(reversedPattern);

// reversedPattern = mix.stretchPattern(reversedPattern, 0.3);
reversedPattern = mix.changeInterval(reversedPattern, +16);
// mix.play(reversedPattern);
System.out.println(reversedPattern);

// Add pattern to itself, reversed again
Pattern reversedAgain = mix.reversePattern(reversedPattern);
reversedPattern.add(reversedAgain);

// Repeat it
reversedPattern.repeat(4);

Pattern fugue = new Pattern();
fugue.add("V0 I[Piano]");
fugue.add(reversedPattern);
fugue.add("V1 I[Synth_strings_1] R/0.637353");
fugue.add(mix.changeInterval(reversedPattern, -12));
fugue.add("V2 I[Blown_Bottle] R/0.637353 R/0.566652");
fugue.add(mix.changeInterval(reversedPattern, -17));
System.out.println(fugue);

mix.play(fugue);

try {
fugue.savePattern(new File("fugue.jfugue"));
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
remixBach();
}
}


//***Sample PatternTransformer***
class InvertPatternTransformer extends PatternTransformer {

private byte fulcrumNoteValue;

public InvertPatternTransformer(Note note) {
this.fulcrumNoteValue = note.getValue();
}

/** Transforms the given note */
@Override
public void noteEvent(Note note) {
doNoteEvent(note);
}

/** Transforms the given note */
@Override
public void sequentialNoteEvent(Note note) {
doNoteEvent(note);
}

/** Transforms the given note */
@Override
public void parallelNoteEvent(Note note) {
doNoteEvent(note);
}

private void doNoteEvent(Note note) {
byte noteValue = note.getValue();

if (noteValue > fulcrumNoteValue) {
note.setValue((byte) (fulcrumNoteValue - (noteValue - fulcrumNoteValue)));
getReturnPattern().addElement(note);
} else if (noteValue < fulcrumNoteValue) {
note.setValue((byte) (fulcrumNoteValue - (fulcrumNoteValue - noteValue)));
getReturnPattern().addElement(note);
} else {
// No change in note value
getReturnPattern().addElement(note);
}
}
}

So, what in this code is new?

In the code above, nothing. But it's just a cool feature that could be used more often in the context of people experimenting with this API to craft new music. However, in the next snippet, we do introduce something new. Recall the rhythm API that we opened the interview with. Well, in the new version of JFugue, you can mix music notes with your rhythms to create riffs, as you might hear, for example, in hip hop. In this code sample, we are banging on our computer keyboard, as before, but we're also using JFugue's new interval notation to specify notes that can be played along with the rhythm. We can then get a specific instance of this rhythm for a particular bass note, which causes actual notes to be created based on those intervals.

    public static void hiphopBeat()
{

Rhythm rhythm = new Rhythm();

// Each MusicString should have a total duration of an eighth note
rhythm.addSubstitution('j', "<1>s Rs");
rhythm.addSubstitution('k', "<6>s Rs");
rhythm.addSubstitution('l', "<8>s Rs");
rhythm.addSubstitution('m', "<9>s Rs");
rhythm.addSubstitution('n', "<4>s Rs");
rhythm.addSubstitution('o', "[BASS_DRUM]i");
rhythm.addSubstitution('*', "[HAND_CLAP]s Rs");
rhythm.addSubstitution('%', "[TAMBOURINE]s Rs");
rhythm.addSubstitution('.', "Ri");

rhythm.setLayer(3, "o...o...o...o...o...o...o...o...");
rhythm.setLayer(4, "..*...*...*...*...*...*...*...*.");
rhythm.setLayer(5, "...%...%...%...%...%...%...%...%");

rhythm.setVoice(1, "jjnnjjmlnnllnnlkjjnnjjmlkkklnnnk");
rhythm.setVoiceDetails(1, "I[CHOIR_AAHS]");

Pattern pattern = rhythm.getPatternWithInterval("Bb4");
pattern.insert("T95");

Player player = new Player();
player.play(pattern);

try {
player.saveMidi(pattern, new File("hiphopBb4.mid"));
} catch (IOException e)
{
e.printStackTrace();
}
}

Here we're banging the keyboard, but in the new part we can set up some non-percusive instruments to play along with that beat. Then we can get a pattern with a bass note, in this case, B flat with a 4th octave. And then, finally, we can save the freshly minted Midi file.

Thanks Dave and good luck with your session and BOF!

AttachmentSize
davek.jpg7.56 KB
music.zip1.09 KB
Published at DZone with permission of its author, Geertjan Wielenga.

Comments

Collin Fagan replied on Thu, 2008/05/08 - 4:41pm

@Dave WOW this is really cool. The keyboard mapping, can that be done at runtime? So that I hit space and get a [BASS_DRUM]i sound. If so how hard do you think it would be to map to game controller buttons instead of keyboard buttons? I ask because I have Rock Band and it has a USB drum set. I think it would be fun to map the drum pads to sounds.

David Koelle replied on Fri, 2008/05/09 - 3:09am

@cfagan - Yes, you can indeed to this at runtime.  You can create a StreamingPlayer, then implement a KeyListener that calls streamingPlayer.stream("[BASS_DRUM]i") whenever the space bar is pressed.

Game controllers probably wouldn't be much more difficult.  Look at the JInput project (https://jinput.dev.java.net/) for details on how to read game controllers.  Incidentally, I'd love to see something where one could create music with a Wii.  "Symphony Hero", perhaps?

Collin Fagan replied on Fri, 2008/05/09 - 6:02am in response to: David Koelle

Thanks Dave. I now have a new project. :)

Lukas Benedix replied on Tue, 2012/11/20 - 1:32am

 in Java 1.7 on Mac OS there is no hearable difference between different Rythm-'Notes'...



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.