Roland Part 4


Ok, so we’ve run initMidiDevices(), which allowed us to obtain instances of MidiDevice(), and the receivers and transmitters for both the K-Pro and the A-300. We ran readSoundBank() to set up the software synthesizer. And setupA300() to init three software sequencers, one for each A-300 port, and to enable recording to three Tracks, one for each sequencer.

We’re finally ready to get some useful work done. One of the last things to happen in setupA300() was that I set kBoard.seqTmr = 0. I’ll ignore my kBoard object for now, except to say that it just has some of the variables I’d been using in K-Gater all along, such as keyboardBottomNote and KeyboardSpacing. If we look now at the master timer, we can see what seqTmr does;

class timerExec extends TimerTask {
public void run() {
if(met.tmr > -1) {
met.tmr++;
if(met.tmr >= met.bpmTotal) {
met.count();
} else if(met.tmr == met.flashCnt) {
met.resetButton();
}
}
if(arp.arpTmr > -1 && arp.keyPressed) {
arp.arpTmr++;
if(arp.arpTmr >= arp.arpTime) {
playArp(arp.calcNote);
}
}
if(kBoard.seqTmr > -1) {
kBoard.seqTmr++;
if(kBoard.seqTmr > 5) {
kBoard.seqTmr = 0;
readA300();
}
}
}
}

As before, I’m using it to run the metronome and the gate arpeggiator. Undoubtedly there’s a better way to do this, such as firing off action listeners, but this is good enough for hobbiest purposes. Now, every 5ms, I’m going to call the readA300() method. And here’s where the real secret lies in intercepting MIDI messages in real time. It’s kludgy and roundabout as all hell, but for the life of me I can’t find any other way to do this. As long as the sequencer is recording sequence data to the track object, track size is going to be “1” (meaning that we just have the required “end of track” marker). So, stop recording, and if the track size is greater than 1, we have new MIDI data. Since we’re checking so rapidly, the odds are that there will only be one message waiting, and if there are more, we’ll grab that on the next call to readA300(). Read the MIDI event to a temporary object, remove the event from the track, and restart recording. I also call my function parseMidi() with the port number (0-2) and the MIDI message. Remember to check both, or all three A-300 Pro MIDI IN ports.

private void readA300() {
for(int i=0; i<aProMaxPorts; i++) { seqcr[i].stopRecording(); if(track[i].size() > 1) {
MidiEvent me = track[i].get(0);
track[i].remove(me);
parseMidi(i, me);
}
seqcr[i].startRecording();
}
}

Now, one of the things that I will harp on about Java over and over is how stupid it is with regard to lists. If you have two identical items in a list, no matter what you do, removing an item will ALWAYS cause the first instance in the list to be deleted. If you have two lists that you’re trying to keep aligned. Java will break the alignment by removing something other than what you wanted. Fortunately, right now, we’re ok. MIDI NOTE_ON messages always include velocity data, and the odds of the user pressing the same note twice in 5 ms with EXACTLY the same velocity is very low. However, if we build up sequencer patterns later and we want to edit them, this will become more of an issue. The reason for mentioning this is that track.remove(MidiEvent) removes the FIRST matching event, so it’s important to keep the seqTmr max value low (5 ms or 10ms) to avoid getting more than 1 or 2 messages in the queue at any given moment. If the user does manage to somehow press and release the same key within that 5ms, we’d have 2 NOTE_OFF messages for it, and the first one would be deleted, which is what we want for a FIFO list, and again there’s no problems with this just yet. Ultimately, all that really matters is that we send NOTE_OFF messages the same number of times that we send NOTE_ON for each key.

The last really new step is to parse the MIDI messages.

As mentioned above, it would probably be better to trigger a listener in order to start a separate thread and to allow control to return directly back to the master timer. At the moment, the parser is fast enough that it isn’t noticeably messing up the timer. I could also use something like a switch statement instead of a whole bunch of else-ifs, but again, this is fine for hobbiest purposes.

The structure of a MIDI short message is “number”, “name or status”, “data 1” and “data 2”. Generally, for the purposes of K-Gater, the number and status bytes can be mostly ignored. I’m not concerned with the channel number because that’s being handled in my app as part of the instrument voice preset buttons. If I do want to change channels, it will be as part of a control message or a Java .doClick(). So, “channel 1 note on” is identical to “channel 8 note on” as far as I’m concerned. Instead, the contents of Data 1 is of more interest, because that tells me which control or button is being used, and Data 2 has the value of the control, 0 to 127. For the Roland A-300 keyboard controller, sliders and knobs go from 0 to 127. Buttons are “0” for Off and “127” for On.

I’ll just display the code for parsing port 1, which contains the NOTE_ON and NOTE_OFF data for the keyboard keys. You can look at the full app listing for the rest of the control parsing. And yes, I do care about the status byte for the NOTE_ON and NOTE_OFF events here.

private void parseMidi(int pNo, MidiEvent midiEvent) {
byte [] bbb = midiEvent.getMessage().getMessage();

if(pNo == 0) {
if(midiEvent.getMessage().getStatus() >= 128 && midiEvent.getMessage().getStatus() <= 143) { // Note off for channels 1-16
int h = bbb[1];
if(channelNo == midiPorts.kProChannelNo) {
h = clamp((bbb[1] – kBoard.bottomNote) * kBoard.spacing, 0, 127);
}
if(! arp.state) {
if(arp.state) {          // I just noticed this. I need to fix this
noteOff(arp.currentNote);
}
else {
noteOff(h);
}
}
arp.keyPressed = false;
}
else if(midiEvent.getMessage().getStatus() >= 144 && midiEvent.getMessage().getStatus() <= 159) { // Note on for channels 1-16
int h = bbb[1];
if(arp.makeNewPattern == 0) { // User wants to make new arp pattern using keyboard
arp.makeNewPatternBase = h;
arp.makeNewPatternString = “0 “;
jTextField6.setText(arp.makeNewPatternString);
arp.makeNewPattern = 1;
}
else if(arp.makeNewPattern == 1) { // Add each new key to pattern string
arp.makeNewPatternString += Integer.toString(h – arp.makeNewPatternBase) + ” “;
jTextField6.setText(arp.makeNewPatternString);
}
if(channelNo == midiPorts.kProChannelNo) {
h = clamp((bbb[1] – kBoard.bottomNote) * kBoard.spacing, 0, 127);
}
kBoard.key = h;
if(! arp.state) {
int v = clamp(bbb[2] * kBoard.volume / 64, 0, 127);
noteOn(h, v); }
kProY = bbb[2];
arp.keyPressed = true;
arp.calcNote = kBoard.bottomNote;
arp.patPtr = 0;
}
else if (midiEvent.getMessage().getStatus() >= 224 && midiEvent.getMessage().getStatus() <= 239) { // Pitchbend, all channels
if(channelNo != midiPorts.kProChannelNo) {
channels[channelNo].setPitchBend((bbb[2] << 7) + bbb[1]); // Can only pitch bend the software synth
}
} else {
if(showMidiCodes) jTextArea1.append(“Midi message from PRO 1: ” + midiEvent.getMessage().getStatus() + ” ” + bbb[0] + ” ” + bbb[1] + ” ” + bbb[2] + “\n”);
}
}
} // Add port 2 and MIDI IN port handling here

There’s a lot of extra stuff going on here. When I get a NOTE_OFF message, I have to worry about the gate arpeggiator, because the arp pattern may be changing the note being played, while the user could be holding down a completely different key. Right now, K-Gater supports polyphony when the arpeggiator is off, but becomes monophonic when the arp is on. This sounds jarring to me when I use the arpeggiator, and I need to add support code for keys being pressed and released as the arpeggiator does its thing.

I added a function called clamp(), which is used to ensure that a number is within upper and lower bounds.

In the section for NOTE_ON, I added the ability to create new arp patterns directly from the A-300. First, I need to look at whether recording has started, and then build up the pattern string to display in a text box. If recording has ended, add the new pattern to the arp pattern combo box. Otherwise, I use a different control to set the bottom note for the K-Pro and I need to ensure that bottom note + current key is < 128, so I use clamp() again. If the ‘pegger is off and if I’m connected to the K-Pro, I use the key velocity data for changing the K-Pro Y axis note. Otherwise, I combine the volume control with velocity as a pseudo-amplifer to alter the velocity sent to the software synth. I’ll also pass some arp settings to the ‘pegger, but the process of ‘pegging itself occurs in playArp().

If I’m driving the software synth, then I can pass pitch bend data from the pitch controller. This took a bit of screwing around to figure out. Bytes Data 1 and Data 2 only contain 7 bits of pitch bend information each, with Data 2 containing the upper byte. To convert to an int, I shift Data 2 seven bits to the left then add Data 1.

Finally, I use an else-statement to output any A-PRO 1 port messages that were otherwise missed. I process the controller sliders, knobs and switches in the section for port 2.

 

Advertisements
Previous Post
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: