Roland Part 2


I decided to modify the K-Gater program in several ways as I began implementing the code for the Roland keyboard controller. First, I split up the code for opening the receiver to the K-Pro from the rest of the initialization so I could make it more modular. This allowed me to add checks for whether a device implements the Sequencer or Synthesizer classes. (Because none of the external hardware I’m using do either, I’m not doing anything special with this new code.) The purpose of getMidiDevice() then is to just receive the name of the desired port as a character string, and return an instance of the MidiDevice class if the port name is found (i.e. – if the device is turned on and plugged into the computer.) I use the testMode variable to determine how much data to print out to the jTextArea1 box (1 = print name, description, vendor, version. 2 = only print the name. 0 = don’t print anything).

initMidiDevices() is where I make the calls to MidiDevice() with the names of each of the desired ports. Note that while the A-300 Pro has three ports (A-PRO 1, A-PRO 2 and A-PRO MIDI IN), only the first two transmit controller data right now. So, I use the aProMaxPorts variable to determine how many ports to connect to. Only using the first two can help speed the program up a little bit later on. Otherwise, this section works pretty much like the original K-Gater code did when I just had the K-Pro.

New methods:

private MidiDevice getMidiDevice(String deviceName, int testMode)
private void initMidiDevices()

As a recap, once we have the K-Pro Midi Device instance, we want to get a receiver and then open it, as shown in the code snippet.

MidiDevice kProDevice = null;
Receiver kProRcvr = null;

kProDevice = getMidiDevice(“KAOSSILATOR PRO 1 SOUND”, 0);
if(kProDevice != null) {
kProRcvr = kProDevice.getReceiver();
}

The difference with the A-300 is that we want to get a transmitter instead of a receiver.

MidiDevice [] a300Device = new MidiDevice[3];
Transmitter [] a300Xmtr = new Transmitter[3];
int aProMaxPorts = 2;
String [] aProPortNames = {“A-PRO 1”, “A-PRO 2”, “A-PRO MIDI IN”};

a300Device[i] = getMidiDevice(aProPortNames[i], 0);
if(a300Device[i] != null) {
a300Xmtr[i] = a300Device[i].getTransmitter();
}

———————– Raw code.

MidiDevice kProDevice = null;
Receiver kProRcvr = null;

MidiDevice [] a300Device = new MidiDevice[3];
Transmitter [] a300Xmtr = new Transmitter[3];
int aProMaxPorts = 2;
String [] aProPortNames = {“A-PRO 1”, “A-PRO 2”, “A-PRO MIDI IN”};

private MidiDevice getMidiDevice(String deviceName, int testMode) {
MidiDevice ret = null;
int holdNo = -1;

MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
for (int i = 0; i < infos.length; i++) {
if(testMode == 1) jTextArea1.append(infos[i].getDescription() + “\n” + infos[i].getName() + “\n” + infos[i].getVendor() + “\n” + infos[i].getVersion() + “\n\n”);
if(testMode == 2) jTextArea1.append(infos[i].getName() + “\n”);

if(infos[i].getName().contains(deviceName)) {
if(holdNo == -1) { // Only return first match
holdNo = i;
try {
ret = MidiSystem.getMidiDevice(infos[i]);
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Get Device:” + i, “Device Open Error”, JOptionPane.PLAIN_MESSAGE);
ret = null;
}
if(debug) {
if (ret instanceof Synthesizer) {
JOptionPane.showMessageDialog(this, deviceName + ” is a synthesizer!”, “Whee!”, JOptionPane.PLAIN_MESSAGE);
}
if (ret instanceof Sequencer) {
JOptionPane.showMessageDialog(this, deviceName + ” is a sequencer!”, “Whee!”, JOptionPane.PLAIN_MESSAGE);
}
}
if (! (ret.isOpen())) {
try {
ret.open();
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Open Device:” + ret.toString(), “Device Open Error”, JOptionPane.PLAIN_MESSAGE);
ret = null;
}
}
}
}
}
return(ret);
}

private void initMidiDevices() {
kProDevice = getMidiDevice(“KAOSSILATOR PRO 1 SOUND”, 0); // Try to open K-Pro
if(kProDevice != null) {
try {
kProRcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Open Receiver:” + kProDevice.toString(), “Receiver Open Error”, JOptionPane.PLAIN_MESSAGE);
}
initkProInstrumentList();
} else {
jComboBox1.setEnabled(false); // Deactivate K-Pro related buttons if no K-Pro
for(int i=0; i<11; i++) {
jBList.get(i).setEnabled(false);
}
}

for(int i = 0; i < aProMaxPorts; i++) { // Try to open A-300 Pro ports
a300Device[i] = getMidiDevice(aProPortNames[i], 0); // String: deviceName, testMode 1 = all midi name info; 2 = name only
if(a300Device[i] != null) {
try {
a300Xmtr[i] = a300Device[i].getTransmitter();
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Open Transmitter:” + a300Device[i].toString(), “Transmitter Open Error”, JOptionPane.PLAIN_MESSAGE);
}
}
}
}

 

A-Pro, Part 1


Ok, this entry is going to start out sketchy since I’m still in the WTF stage regarding communicating with the Roland A-300 Pro. As I get stuff to work, I’ll rewrite this entry as appropriate. If I’m lucky, I’ll be 100% functional by the time I have to publish this.


(Roland A-300 Pro)

I’d thought that opening up a transmitter to receive MIDI messages from a MIDI OUT device like a keyboard controller would be much like opening a receiver.  I admit that I wasn’t really paying attention to transmitters when I was reading the Java documentation on receivers, because if I’d had, I may never have gone out to buy the A-300.  Yes, the situation is even worse than what I faced when I tried to get the code to work with the K-Pro.

If you look at the Oracle Sound Tutorial, the discussion shows how to directly connect a transmitter to a receiver, and how to fork the transmitter to connect to both a receiver and a sequencer object.  There’s nothing about connecting the transmitter to some kind of an object that lets you see the MIDI messages as they come in and interpret them in some way.  And that’s exactly what I designed the K-Gater around as a midi mapper.  I wanted to set up a transmitter object to the A-300, play the A-300 keys, have the messages plop somehow into an array or some midi object, and then set up a switch-case to parse the messages and have them run jButton .doClick() operations for selecting instruments, changing the gate arp rate and ratio, and changing K-Pro volume.

Instead, the Transmitter object has NO METHODS WHATSOEVER for presenting MIDI messages from a MIDI OUT device to your Java app.  It’s like a great big hole in the ground.  Data goes from the keyboard controller straight to the software synth for direct playback, which is fine if you want the default software synth for the voices for your keyboard controller.  Not so fine if you want to change instrument voices with the controller. Or, pretty much if you want to do anything else. Further, there’s a 2 second latency from when I press the keys to when the notes get played (when I was using Sonar LE with the A-300 Pro, with the Windows software synth instruments, it was a 1/2 second delay).  I think this latency is built into the transmitter-receiver link, and isn’t simply a function of making sounds through software, like Roland claims.

The Oracle sound tutorial also shows that example of “forking”, where one transmitter drives two or more receivers.  The specific example is to have some random transmitter talking directly to the default software synth and the default software sequencer at the same time.

The clue to intercepting MIDI messages is in this fork code.  The sequencer needs a start and stop instruction for recording MIDI messages as a sequence.  My thought is that the only option is to start the sequencer record and check when the size changes, read the recorded message and parse that.  The sequencer uses “ticks”, the number of time slices between NOTE_ON and NOTE_OFF messages to enable the sequence playback.  But, since I want real-time control of the K-Pro, I can ignore ticks and just try to monitor the midi messages as they come in.  It’s still tricky, because you have to do the following:

Create the Sequencer object
Create a Sequence object
Create a track object
Open the Sequencer
Connect the Sequencer receiver to the A-300 transmitter
Enable the track
Assign the track object to the sequence, and record all channels to the one track
Start the Sequencer recording
Using a timer object, prepare to read the tracks every 10 ms to 100 ms
Stop recording
Check the track size and if it’s > 1, parse it
Zero the track
Start recording again

The above is for just one A-300 MIDI IN port.  There are three ports, so you have to do the above three times, with one sequencer-sequence-track combination per port.

Yuck.

You may have noticed that I started out using the term MIDI OUT for the A-300, then suddenly switched to using MIDI IN just now. You would think that if you connect a cable to the MIDI OUT jack of the keyboard controller, that this would be a MIDI OUT connection. You would be wrong. The second the data arrives at the computer, Roland refers to it as a MIDI IN port in their USB driver software running on your PC.

The Roland adds more of its own complications:
3 MIDI IN ports instead of 1
MIDI ports change names depending on the USB port you’re connected to
Most of the controls are reassignable

1) The Roland A-300 has LOTS of controls.  So many that it needs 3 MIDI IN ports to communicate them all, even though they’re still only going through one USB cable. Fortunately, with the default assignments, messages appear on only 2 of the three ports.

2) For some reason, Roland seems to think that we’ll be using more than 1 keyboard controller at a time.  If you plug the A-300 Pro into one USB port, the MIDI IN names are assigned as A-PRO MIDI IN, A-PRO 1 and A-PRO 2.  Plug the unit into a different USB port of the PC, and “2- ” or “3- ” get pre-pended to the names.  So, either you only use one specific USB port all the time, you strip out the pre-pended numbers, or you have to hard-code all 6 or 9 port name variants into your app.

2a) Roland doesn’t support USB splitter boxes. If you only have one USB port on your computer and it’s connected to your mouse, too bad.

3) You can program the MIDI number assignments of any control as you like.  It’s good from a flexibility viewpoint, but this makes parsing MIDI messages in your app much more difficult.  The A-300 Pro uses controller mapping files for specific applications, but I haven’t gotten far enough to figure out how to use those files for my own app.  In the meantime, I have to be careful to not screw things up when using the map for Sonar LE.

4) This one isn’t necessarily a complication, assuming that I can master controller maps.  But, the touch pads at the top right of the controller are initially mapped to the percussion instruments in MIDI channel 9.  If I want to use the pads for changing K-Pro instrument voices, I either need to turn them into control pads somehow, or I mask them out of the NOTE_ON/NOTE_OFF code (or both).

————————-

On a slightly different subject, I had been completely confused regarding the relationship between channels and ports.

channel: A MIDI channel is one of 16 communications “packets” that allows a controller to direct messages to a specific musical instrument.  These instruments can be external hardware like the K-Pro, or software simulators like the default software synthesizer that I’ve been using.  You identify which channel you want to control, put that channel number in the header of the “packet” and send the packet out (i.e. – via the hardware cable for an external device). Whatever device is set to that channel will then be the one that intercepts that packet and plays whatever notes you told it to.

port: A “port” is a connection that you talk to for communicating with part of the external instrument, usually through the software driver for that instrument. The port is independant of the MIDI channel, and is in fact, what you use to send MIDI data to the external hardware.  As an example, the A-300 has 5 ports: 3 for MIDI IN: “A-PRO 1”, “A-PRO 2” and “A-PRO MIDI IN”; 2 for MIDI OUT: “A-PRO” and “A-PRO MIDI OUT”.  Meanwhile, the K-Pro has two ports: “KAOSSILATOR PRO SOUND” and “KAOSSILATOR PRO PAD”. (I’m assuming that “PAD” is MIDI IN when using the K-PRO as a controller.  “SOUND” is used for controlling the K-PRO.)

The bottom line is that when you’re deciding which MIDI channel numbers to assign your instruments to, the number of ports the equipment has is irrelevant.  The K-PRO accepts one channel assignment, 1-16.  The software synthesizer has all 16 channels.  The A-300 Pro is the main controller for this system and doesn’t have MIDI channels assigned to it; instead, it sends MIDI message data via one of the 3 ports to specific channels of whatever external hardware or software devices you have plugged in and running.

 

K-Gater, Part 12


Ok, so that’s the K-Gater app. Time for a little recap.

The entire point of this exercise was to create a kind of midi mapper to drive the Korg Kaossilator Pro, which doesn’t support standard NOTE_ON, NOTE_OFF MIDI messages. And because I couldn’t find example code for talking to external hardware. All the tutorials and documentation start out discussing both external hardware and the default software synthesizer, but right at the most crucial point, they switch over to just talking about the software synth exclusively. I had to do a lot of trial and error to make that leap to a working external hardware connection.

Both start out the same way, by using the MidiSystem object. For external hardware, we need to know what’s plugged in and running, which will include the Windows synth support files if you’re on a Windows PC. We do this by getting an array of info objects. Note that it doesn’t matter to us if the devices are software-only, connected through MIDI cables or through the USB port.

MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();

MidiDevice just lets us display the vendor name, device name, description and version. If we want the instrument names or channel numbers, we need to open a Synthesizer object, if possible.

Next, loop through the info array and check if the vendor name or description matches the device we want. For the K-Pro, the name is “Kaossilator Pro 1 Sound”, where the “1” is the port number, if we enabled the “include port number in driver name” option. For the Roland A-300 Pro midi keyboard controller, there are actually 5 ports, for MIDI IN: “A-PRO 1”, “A-PRO 2” and “A-PRO MIDI IN”; and for MIDI OUT: “A-Pro” and “A-PRO MIDI OUT”. So, we need to check for all three MIDI IN ports in order to connect transmitters to them. (The following code fragment is for the K-Pro only.)

int kProDevice = -1;
for (int i = 0; i < infos.length; i++) {
if(debug) {
jTextArea1.append(infos[i].getDescription() + “\n” + infos[i].getName() + “\n” + infos[i].getVendor() + “\n” + infos[i].getVersion() + “\n\n”);
}
if(infos[i].getName().startsWith(“KAOSSILATOR PRO”)) {
kProDeviceNo = i;
}
}

If kProDeviceNo is -1, it hadn’t been plugged in or turned on at the time the app started. Since MidiSystem doesn’t seem to be refreshing properly on my computer, the only choice is to exit the app and try running it again in a few seconds, after making sure the K-Pro is on and connected. Otherwise, the next step is to create a MidiDevice object with the K-Pro info.

MidiDevice kProDevice = null;
if(kProDeviceNo > -1) {
try {
kProDevice = MidiSystem.getMidiDevice(infos[kProDeviceNo]);
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Get Device:” + kProDeviceNo, “Device Open Error”, JOptionPane.PLAIN_MESSAGE);
}
if (!(kProDevice.isOpen())) {
try {
kProDevice.open();
} catch (MidiUnavailableException e) {
JOptionPane.showMessageDialog(this, “Couldn’t Open Device:” + kProDeviceNo, “Device Open Error”, JOptionPane.PLAIN_MESSAGE);
}
}
initkProInstrumentList();
} else {
jComboBox1.setEnabled(false); // Deactivate K-Pro related buttons if no K-Pro
for(int i=0; i<11; i++) {
jBList.get(i).setEnabled(false);
}
}

Lots of exceptions being thrown here. Netbeans forces us to handle all of them, or it won’t run right. I think that some of the Java documentation assumes that the user is hand-coding Java and that the exceptions can be thrown without being caught. This is another issue that I have with the official documentation, which doesn’t always include exception handling.

Once we do the .getMidiDevice(info), we need to .open() the device. When we exit the app, we need to make sure we .close() it, or the link to the K-Pro will remain open after the app stops running. Same holds true for the A-300 Pro support.

To determine if the external device is a synthesizer or sequencer, we can query it with “instance of”. We can do this before doing .open();

if (kProDevice instanceof Synthesizer) {
JOptionPane.showMessageDialog(this, “K-Pro is a synthesizer!”, “Whee!”, JOptionPane.PLAIN_MESSAGE);
}

The K-Pro doesn’t support the synthesizer class, so I can’t get the soundbank from it. If it did, I could make a Synthesizer object, and use something like .getSoundBank(). Instead, I hand-typed the instrument names into an array, and I use my initProInstrumentList() method to copy the names from the array to a combo box to display to the user. The remaining code is just used to disable my combo box and K-Pro voice preset buttons if kProDevice is null.

The next part is what all of the official documentation skips. To actually talk to the external hardware, we need to open a receiver and/or a transmitter to it. If we’re sending MIDI messages to the device, we need a receiver. Which of course can throw another exception we have to catch. Note that I initially opened the receiver every time I wanted to send data to the K-Pro because that’s the way it’s shown in one of the examples. It’s inefficient, but the idea is that if the hardware can only support a limited number of receivers, you’ll allow another app to talk to the K-Pro in sequence this way. In fact, the number of receivers the K-Pro supports is unlimited, so I’m just going to open one receiver at the beginning and leave it open in the next version.

private void kProProgram(int no) { // Change K-Pro instrument
int bank = 0;
int nbank = 0;
int ch = 0;
ShortMessage myMsg = new ShortMessage();
Receiver rcvr = null;
long timeStamp = -1;
if(kProDevice != null) {
try {
rcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
}
try {
bank = (no < 127) ? 0 : 1;
nbank = (bank == 1) ? 0 : 1;
ch = (no < 127) ? no : no – 128;
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 0, nbank);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 32, bank);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.PROGRAM_CHANGE, midiPorts.kProChannelNo, ch, 0);
rcvr.send(myMsg, timeStamp);
} catch (javax.sound.midi.InvalidMidiDataException e) {
}
}
}

private void kProNote(int ch, int col, int row, int onOff) { // Change K-Pro note (x-y pad)
ShortMessage myMsg = new ShortMessage();
Receiver rcvr = null;
long timeStamp = -1;
if(kProDevice != null) {
try {
rcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
}
try {
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 12, row);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 13, col);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 92, onOff);
rcvr.send(myMsg, timeStamp);
} catch (javax.sound.midi.InvalidMidiDataException e) {
}
}
}

Talking to the external device is just a matter of building up a ShortMessage and using receiver.send(). The type of message you send depends a large part on whether it’s the software synthesizer or external hardware. For the software synth, messages follow the MIDI spec, with ShortMessage.NOTE_ON, ShortMessage.NOTE_OFF and ShortMessage.PROGRAM_CHANGE being the primary ones. For the Kaossilator Pro, most messages are ShortMessage.CONTROL_CHANGE messages. For more typical external instruments, the messages will be the same as for the software synth. It’s important to remember here that the K-Pro doesn’t implement the Synthesizer class and therefore we have to send the messages the hard way. In the section below, we get to see what happens if a device DOES implement the Synthesizer class.

And, that’s it for external devices.

——————————————

In contrast, let’s look at the one part that all of the documentation agrees on – using the built-in software synth. This starts out the same, using the MidiSystem. .getSynthesizer() returns a pointer to the default synth. That’s it, it’s just that easy. You can then get the default soundbank, or load one that you want from a file or from a URL. The soundbank contains bank numbers and voice numbers, which need to be loaded to an array for later reference. And, because the software synth can be assigned to all 16 channels, we need an array for the channel-to-voice relationship.

Soundbank soundbank = null;
Synthesizer synth = null;
MidiChannel [] channels = null;
Instrument[] aInstruments = null;

private void readSoundBank() { // Load the default Java software synth
try {
synth = MidiSystem.getSynthesizer();
synth.open();
soundbank = synth.getDefaultSoundbank();
synth.loadAllInstruments(soundbank);
channels = synth.getChannels();
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, “Couldn’t Open Soundbank:\n” + ex, “Soundbank Open Error”, JOptionPane.PLAIN_MESSAGE);
}
if (soundbank == null){
jTextArea1.setText(“no soundbank”);
} else {
aInstruments = soundbank.getInstruments();
for (int i = 0; i < aInstruments.length; i++) {
jComboBox3.addItem(aInstruments[i].getName());
}
}
}

Changing instruments takes place with the MidiChannel object .programChange() method:

channels[channelNo].programChange(aInstruments[voiceNum].getPatch().getBank(), aInstruments[voiceNum].getPatch().getProgram());

Turning notes on and off is just a matter of calling the appropriate Channel method. We just need to give the note number (0-127) and the keyboard velocity (i.e. – volume; 0-127) for note on.

channels[channelNo].noteOn(arp.currentNote, keyboardVolume);
channels[channelNo].noteOff(arp.currentNote);

The main point regarding this comparison is that if the external hardware implements the Synthesizer class, we can use these Synthesizer methods on it just as we do for the default synth. The difference being that we’d open a receiver to the hardware rather than using MidiSystem.getSynthesizer(). (I would give working example code for using an external hardware synthesizer, but I don’t own one.)

And that’s it for the Kaossilator Pro support.

K-Gater, Part 12


All that’s left are the east and south panels.  The south panel contains jTextArea1 and doesn’t use action listeners.  The east panel contains the components for setting keyboard volume, the metronome, base keyboard key value, key spacing, and the patch selector controls, most of which are similar to what I’ve discussed in the west panel section.

The metronome is based on beats per minute.  If you have 2 beats per second, in a “1 and a 2 and a 3 and a 4 and a” count, then BPM is 120.  For the purposes of our master timer, that’s 500 ms.  Right now, I just change the metronome from 1 to 4 in a loop, but if I want 1 to 3, then I need to hardcode the change.  I could make the metronome stand out a little more, by making the text font bigger, and adding an entry in the master timer to turn the button background color red for a quarter-second.

jSlider1StateChanged
User is changing the K-Pro volume, or the max default synth velocity value. I’ve noticed that when using the Roland A-300 Pro keyboard that velocity values tend to be no more than 70-75, which are kind of hard to hear with the software synthesizer. So, I’m using jSlider1 as an amplifier, running from 0.0 to 2.0 (output value from 0 to 127) and then clamping at 127 if necessary.

jButton12ActionPerformed
Turn the metronome on and off.

jTextField1ActionPerformed
User entered a new value for the metronome Beats per Minute.

jTextField1ActionPerformed
User changed the value for the keyboard bottom note.  If I don’t have a keyboard connected, I can use this field to select notes to play with the arpeggiator turned on.  I already have a keyboard (Roland A-300 Pro), and it has buttons for shifting up and down an octave. So, technically jTextField1 isn’t necessary anymore, except that when I’m driving the K-Pro (as opposed to the software synth) I’m finding that there are lower and upper ranges of notes that all sound the same. The K-Pro’s expressive range seems to be between 40 and 80, which may not align with the octave the keyboard is set at. To get around this, I still use jTextField1 for shifting the notes into the K-Pro’s range.

jTextField4ActionPerformed
User entered a new value of keyboard key spacing.  This allows the keyboard to cover more than one octave.

jButton35ActionPerformed
Adding new patch to the ArrayList.  Check for duplicate names or a blank name.

jButton36ActionPerformed
Deleting patch. If all of the patches are removed, we start getting null pointer exceptions, so force the user to keep at least one patch at all times.

jTextField7ActionPerformed
User entering new patch name.  Pressing Enter is the same as clicking the Add Patch button.

jComboBox4ActionPerformed
User selecting patch from combo box. Transfer the settings of the selected patch into all of the jButton and jTextField components.

——————

Formatted text file.

// Change synth volume.

private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider1StateChanged
ShortMessage myMsg = new ShortMessage();                // Changing volume with slider control
Receiver     rcvr  = null;

if(kProDevice != null) {
try {
rcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
}
try {
long timeStamp = -1;
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 94, jSlider1.getValue());
rcvr.send(myMsg, timeStamp);
} catch (javax.sound.midi.InvalidMidiDataException e) {
}
} else {
keyboardVolume = jSlider1.getValue();
}
}//GEN-LAST:event_jSlider1StateChanged

// User turned the metronome on or off.

private void jButton12ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton12ActionPerformed
if(met.state) {                                  // Turn metronome on and off
met.tmr = -1;
met.cntr = 1;
jButton12.setText(” “);
} else {
met.bpmTotal = 1000 * 60 / met.bpm;
met.tmr = 0;
}
met.state = (! met.state);
}//GEN-LAST:event_jButton12ActionPerformed

// User entered a Beats Per Minute value to the textfield for the metronome.

private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField1ActionPerformed
int timerState = met.tmr;

if(isInt(jTextField1.getText())) {
met.tmr      = -1;                                           // Change metronome BPM
met.bpm      = Integer.parseInt(jTextField1.getText());
met.bpmTotal = 1000 * 60 / met.bpm;
met.cntr     = 1;
jButton12.setText(” “);
met.state    = false;
met.tmr      = timerState;
} else {
jTextArea1.append(jTextField1.getText() + ” is not a number.\n”);
}
}//GEN-LAST:event_jTextField1ActionPerformed

// User entered a number for the bottom note of the keyboard.  Later, I’ll replace this function with a MIDI read from an actual
// keyboard.  For right now,it lets me play some rudimentary tunes with the gate arp turned on.  When either jTextField3 (base note)
// or jTextField5 change, update the contents of jTextField2, the gate arp base note field (which is display-only right now.

private void jTextField3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField3ActionPerformed
if(isInt(jTextField3.getText())) {
keyboardBottomNote = Integer.parseInt(jTextField3.getText());
jTextField2.setText(Integer.toString(keyboardBottomNote + keyboardKey * keyboardSpacing));
// bottomNote = Integer.parseInt(jTextField3.getText());             // Keyboard bottom note
// Right now, arpBottom note is being used to set the start point of the arpeggiator pattern.
// However, when a keyboard is added eventually, jTextField3 will be used to offset the base note of the keyboard
// and jTextField4 will be used to set the spacing between keys.  This will produced a “current note being pressed”
// which will then replace the arpeggiator bottom note text field.
} else {
jTextArea1.append(jTextField3.getText() + ” is not a number.\n”);
}
}//GEN-LAST:event_jTextField3ActionPerformed

// User changed the keyboard note spacing.  Update the gate arp base note display.

private void jTextField4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField4ActionPerformed
if(isInt(jTextField4.getText())) {
keyboardSpacing = Integer.parseInt(jTextField4.getText());                // Changing keyboard note spacing
jTextField2.setText(Integer.toString(keyboardBottomNote + keyboardKey * keyboardSpacing));
} else {
jTextArea1.append(jTextField4.getText() + ” is not a number.\n”);
}
}//GEN-LAST:event_jTextField4ActionPerformed

// User is attempting to add a new patch (all of the screen settings in one patch object) to the combo box.  Fail if a patch
// with the same name already exists.

private void jButton35ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton35ActionPerformed
patch newPatch = new patch();                                              // Clicked Add Patch
boolean isInList = false;

if(jTextField7.getText().isEmpty()) {
jTextArea1.append(“Please give a name for the patch.\n”);
} else {
for(patch p : patchList) {
if(p.name.equals(jTextField7.getText())) {
isInList = true;
}
}
if(isInList) {
jTextArea1.append(“Patch name already assigned. Please pick a new name.\n”);
} else {
newPatch.gatherPatch();
jComboBox4.addItem(newPatch.name);
patchList.add(newPatch);
suppressUpdate = true;
jComboBox4.setSelectedIndex(jComboBox4.getItemCount() – 1);
}
}
}//GEN-LAST:event_jButton35ActionPerformed

// User is attempting to delete the selected patch from the combo box.  Fail if there’s only one patch left.

private void jButton36ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton36ActionPerformed
int sel = jComboBox4.getSelectedIndex();                                  // Clicked Delete Patch

if(sel == -1) {
jTextArea1.append(“Please select a patch to delete.\n”);
} else {
if(patchList.size() > 1) {
patchList.remove(sel);
suppressUpdate = false;
jComboBox4.removeItemAt(sel);
} else {
jTextArea1.append(“Deleting all patches will cause a system error.  Please append at least one new patch first.”);
}
}
}//GEN-LAST:event_jButton36ActionPerformed

// User entered the patch name in the jTextField7 component.  Treat it as if they clicked the Add Patch button.

private void jTextField7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField7ActionPerformed
jButton35.doClick();                                                       // Pressing Enter in Patch Name field is same as Add Patch
}//GEN-LAST:event_jTextField7ActionPerformed

// User selected a patch from the combo box.  Load it and update all the buttons.

private void jComboBox4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jComboBox4ActionPerformed
int sel = jComboBox4.getSelectedIndex();                                   // Select a patch to load

if(suppressUpdate) {                                                   // Because Add and Delete Patch select a new patch in the
suppressUpdate = false;                                             // Combo Box, the Combo Box will try loading the
} else {                                                               // Patch as Select Patch.  Suppress this loading.
patchList.get(sel).loadPatch();
jTextArea1.append(“Patch ” + patchList.get(sel).name + ” loaded.\n”);
jTextField7.setText(patchList.get(sel).name);
}
}//GEN-LAST:event_jComboBox4ActionPerformed

50 Famous People – Steve Jobs


(All rights belong to their owners. Images used here for review purposes only.)

Of the people included in the “50 Famous People” collection, Steve Jobs is one of the most current. (He passed away in 2011, while Michael Jackson died in 2009, and Momofuku Ando, creator of cup ramen, went in 2007).  So, Steve is one of the easiest people to learn about, and there are photos of him everywhere, making it simple to compare the manga designs against the real thing.  If you don’t know his story, I recommend that you find a good book on him and start reading.

The intro manga has Merrino inventing a new device that combines a music player with a cell phone, and Youichi tells the sheep prince that Apple has already made something like this.  Merrino makes the screen bigger, and Mami points to the iPad.  In desperation, he creates the “iTongue”, a new interface device that changes flavors every day.  Youichi and Mami are impressed, until the iTongue goes berserk and slaps Merrino silly.


(“You screwed up. Please take responsibility and step down.”)

The main manga, by Tatsuyoshi Kobayashi (artist and producer), isn’t that bad, character design-wise.  Tatsu’s supporting characters look like typical manga versions of westerners (meaning, not very close), but you can at least tell that the main character is supposed to be Jobs.  This story has obviously been tailored for Japanese children, and anyone that really knows Jobs is going to gag on some of the glossing over.  The first couple pages establish Steve as a child that grew up with adoptive parents, to continue on to make one of the most powerful companies in the world.  As a teenager, he asks questions that inconvenience his teachers, and he develops an interest in electronics that confounds his friends.  Some of them introduce him to Steve Wozniak, and the two of them work together to produce the Apple I.  They form Apple Computers, and Jobs pressures Woz to make a more complete system that anyone can use (not just hobbyists), resulting in the Apple II.  Jobs goes big with the Macintosh, but sales are weak and the company pushes him to quit to “take responsibility” for the bad product) (in fact, John Sculley had been trying to replace Jobs for years as part of a coup, and the weak sales of the Mac drove the board of directors to side with Sculley in completing the ouster).  Jobs then started up NeXT Computers and Pixar Films.  In partnership with Disney, Pixar released “Toy Story” in 1995 and in 1996, Apple’s board came crawling to ask him to come back.  The manga states that Jobs wowed the world with hit after hit, from the iPod, to the iPhone and then the iPad.  While he was able to “see into the future” of technology, with his goals of “make it simple to use and make it look good”, he was unable to overcome the cancer that finally claimed him.

The textbook section contains photos of various products produced by Apple, pictures of Jobs with Woz, and a more complete written biography.  There’s a mention of his interest in zen, and his decision to become vegetarian.  There’s a sidebar on Bill Gates, mentioning him as Job’s main friend and rival.  The last two pages talk about the use of computers for producing CG films, with a mention of “Brave” (“Merida and the Scary Forest”, in Japanese) and “Titanic”.

The TCG cards include: Francis Drake, Akbar the Great, Elizabeth I of England, Henry IV of France, Matteo Ricci, Cervantes, Xu Guangqi, Francis Bacon and Injo of Joseon.

Summary: This particular issue is fairly weak on the details of Jobs’ life, with various spins that make more sense within the Japanese culture, but are very insulting to western sensibilities.  However, it has some nice photos, and it is worth the price.

 

K-Gater, Part 11


West panel.

There’s really not a lot left to talk about. I’ve pretty much covered everything about the gate arpeggiator, and there’s not much new in this section. Originally, I wanted to use an Array for holding the arp patterns, with the first entry specifying the number of notes in the pattern, and then a second array for holding pointers to the start of each pattern. But things got messy when I starting thinking about deleting specific patterns, and arrays don’t let you append more entries at the end. So, I was forced to learn more about ArrayLists, and that turned out to be a blessing in disguise, because they do everything I wanted, plus giving me extra flexibility in pattern lengths by letting me remove the length value from the beginning of the pattern.

What I absolutely hate about ArrayLists and combo boxes, though, is that if I accidentally hit the Add Pattern button, I’ll get a duplicate entry in the list, and if I try to delete it, Java will remove the first matching entry it finds. This isn’t as much of an issue with arp patterns, but it really messes up the relationship between the ArrayList and combo box for the patches (if I have two different patches with the same name). I tried including index numbers at the beginning of the combo box string items, but that only worked for arp patterns, not patches. I then ran into a problem when I did a removeAll() from the combo box prior to updating it – Java started throwing null pointer exceptions. I assume that by emptying the combo box, I set it up for garbage collection, or something. Eventually, I just settled on preventing duplicate entries (duplicated arp patterns or patch names), and requiring the user to keep a minimum of 1 patch or arp pattern at all times.

I don’t know how many times I tried running the gate arp without loading some patterns from a file first, so I put in an error check for that as well. And, there is an issue with getting the gate arp patterns wrong, such as by having a space between the minus sign and the number, or putting the minus sign behind the number, so I created the isInt() function to handle that. Patterns are space-delimited, although I could consider allowing commas.

One thing I dislike about the way K-Gater works right now is that when I change the arp ratio using the slider, it causes the sound to stop as long as the mouse button is held down. This is because the slider state has changed, causing the action listener to fire, but the state remains “changed” and the listener KEEPS firing. I think the only work around is to use the “left” and “right” jButtons, but to increase the step values from 1 to 5 or 10 to make the change in effect more noticeable more quickly.

One last comment, this one about the gate arp rate slider. I incorporated a format similar to what’s used by the K-Pro, and displayed the rate in fractions of seconds (2 sec., 1/2 sec., 1/3 sec.) on the screen, while keeping the actual number in milliseconds in a second array. I think it’s just more readable that way. I don’t have many choices for 1/3 (just 1/3 and 2/3’s) of a second. I could consider 1/6, 1/3, 2/3’s and 5/6’s, which would be easy enough to implement. I’d just need to remember to change the maximum value for the slider.

jButton13ActionPerformed
User turning the gate arpeggiator on and off.

jSlider2StateChanged
User changing the gate arp rate (0-10).

jButton29ActionPerformed
User selecting next slower rate. (Button to left.)

jButton30ActionPerformed
User selecting next faster rate. (Button to right.)

jSlider3StateChanged
User changing the gate arp ratio (1-99).

jButton31ActionPerformed
User selecting next larger ratio. (Button to left.)

jButton32ActionPerformed
User selecting next smaller ratio. (Button to right.)

jTextField2ActionPerformed
I made gate arp bottom note read-only. It’s just the total of keyboard bottom note + keyboard key * keyboard spacing.

jComboBox2ActionPerformed
The list of arpeggiator patterns is displayed in jComboBox2. User selected one of the patterns. Update the pointers to point to this pattern, and copy the pattern string to a textbox for editing.

jTextField5ActionPerformed
Gate arp pattern spacing. If the pattern is 0 1 2 3, then a spacing of 3 will turn it into 0 3 6 9.

jButton33ActionPerformed
User wants to add an edited pattern to the arp pattern list. Check for duplications and anything that is not a number. Pattern is space-delimited.

jButton34ActionPerformed
User wants to delete the current pattern. If we delete the last pattern in the ArrayList, we’ll start getting null pointer exceptions, so force the user to keep at least one pattern at all times.

——————————-

Formatted text file.

// User turned the gate arpeggiator on or off.

private void jButton13ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton13ActionPerformed
if(arpPatternsList.size() > 0) {
if(arp.state) { // Gate Arp ON/Off Button pressed
arp.arpTmr = -1;
jButton13.setBackground(Color.lightGray);
kProAllOff();
} else {
arp.arpTmr = 0;
jButton13.setBackground(Color.red);
}
arp.state = (! arp.state);
} else {
jTextArea1.append(“No apreggiator patterns loaded yet.\n”);
}
}//GEN-LAST:event_jButton13ActionPerformed

// User moved the slider for the gate arp rate setting.

private void jSlider2StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider2StateChanged
String s[] = {“4 sec.”, “2 sec.”, “1 sec.”, “3/4”, “2/3 sec.”, “1/2 sec.”, “1/3 sec.”, “1/4 sec.”, “1/8 sec.”, “1/16 sec.”, “1/32 sec.”};
int i[] = {4000, 2000, 1000, 750, 666, 500, 333, 250, 125, 62, 31};
jLabel2.setText(“Rate: ” + s[jSlider2.getValue()]);
if(arp.state) {
arp.arpTmr = -1;
kProAllOff();
}
arp.rate = i[jSlider2.getValue()];
arp.onOff = true;
arp.calc();
arp.arpTime = arp.onTime;

if(arp.state) {
arp.arpTmr = 0;
}
}//GEN-LAST:event_jSlider2StateChanged

// There are two buttons that supplement the gate arp rate slider, for single incrementing or decrementing the slider.

private void jButton29ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton29ActionPerformed
int x = jSlider2.getValue(); // Arp Rate L button
if(x > 0) {
x–;
jSlider2.setValue(x);
}
}//GEN-LAST:event_jButton29ActionPerformed

private void jButton30ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton30ActionPerformed
int x = jSlider2.getValue(); // Arp Rate R button
if(x < jSlider2.getMaximum()) { x++; jSlider2.setValue(x); } }//GEN-LAST:event_jButton30ActionPerformed // User moved the slider for the gate arp ratio setting. private void jSlider3StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider3StateChanged if(arp.state) { // Gate Arp Ratio arp.arpTmr = -1; kProAllOff(); } jLabel3.setText(“Ratio: ” + jSlider3.getValue() + “%”); arp.onOff = true; arp.ratio = jSlider3.getValue(); arp.calc(); arp.arpTime = arp.onTime; if(arp.state) { arp.arpTmr = 0; } }//GEN-LAST:event_jSlider3StateChanged // There are also two buttons that supplement the gate arp ratio slider, for single incrementing or decrementing the slider. private void jButton31ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton31ActionPerformed int x = jSlider3.getValue(); // Arp Ratio L button if(x > 0) {
x–;
jSlider3.setValue(x);
}
}//GEN-LAST:event_jButton31ActionPerformed

private void jButton32ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton32ActionPerformed
int x = jSlider3.getValue(); // Arp Ratio R button
if(x < jSlider3.getMaximum()) {
x++;
jSlider3.setValue(x);
}
}//GEN-LAST:event_jButton32ActionPerformed

// Gate arp bottom note. Display only (calculated from keyboard bottom note and keyboard spacing.

private void jTextField2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField2ActionPerformed
// keyboardBottomNote = Integer.parseInt(jTextField2.getText()); // Arp bottom note
}//GEN-LAST:event_jTextField2ActionPerformed

// User selected a gate arpeggiator pattern from the combo box. If the gate arp is on, play the pattern. Either way
// put the pattern into a text box to let the user edit it.

private void jComboBox2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jComboBox2ActionPerformed
arp.currentPat = jComboBox2.getSelectedIndex(); // Selecting arp pattern
arp.patLength = arpPatternsList.get(arp.currentPat).size();
String s = (String) jComboBox2.getSelectedItem();
}//GEN-LAST:event_jComboBox2ActionPerformed

// User changed the gate arp pattern step size in the text box.

private void jTextField5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField5ActionPerformed
if(isInt(jTextField5.getText())) {
arp.stepSize = Integer.parseInt(jTextField5.getText()); // Changing Arp Step Spacing
} else {
jTextArea1.append(jTextField5.getText() + ” is not a number.\n”);
}
}//GEN-LAST:event_jTextField5ActionPerformed

// User is attempting to add a gate arp pattern to the combo box. Fail if the pattern contains something other than integers
// (space delimited), or if the identical pattern already exists.

private void jButton33ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton33ActionPerformed
String s = jTextField6.getText(); // Add new pattern to Arp list
String [] str = s.split(” “);
ArrayList a = new ArrayList();
boolean goodSoFar = true;

for(int i = 0; i < str.length; i++) {
if(isInt(str[i])) {
a.add(Integer.parseInt(str[i])); // Build up new pattern as Integer ArayList
} else {
goodSoFar = false;
}
}
if(goodSoFar) {
for(int i=0; i < jComboBox2.getItemCount(); i++) { if(jComboBox2.getItemAt(i).equals(s)) { goodSoFar = false; } } if(goodSoFar) { arpPatternsList.add(a); // Add to the main patterns list jComboBox2.addItem(s); // Add to combobox jComboBox2.setSelectedIndex(jComboBox2.getItemCount() – 1); } else { jTextArea1.append(“Pattern already exists. Not added to list.\n”); } } else { jTextArea1.append(“Bad string. Contains Not a Number.\n”); } }//GEN-LAST:event_jButton33ActionPerformed // User wants to delete the currently-selected gate arp pattern. Fail if there’s only one pattern. Trying to delete the only // pattern in the combo box will throw an exception. private void jButton34ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton34ActionPerformed int curPatNo = jComboBox2.getSelectedIndex(); // Delete arp pattern int curSize = -1; String s = “”; if(arpPatternsList.size() > 1) {
arpPatternsList.remove(curPatNo);
jComboBox2.removeItemAt(curPatNo);
curSize = arpPatternsList.size();

if(curPatNo <= 0) { // If deleted first in list, select current first pattern jComboBox2.setSelectedIndex(0); } else if(curPatNo >= jComboBox2.getItemCount() – 1) { // If deleted last in list, select current last pattern
jComboBox2.setSelectedIndex(jComboBox2.getItemCount() – 1);
} else { // Select previous pattern instead
jComboBox2.setSelectedIndex(curPatNo – 1);
}

} else {
jTextArea1.append(“Can’t delete remaining patterns without getting a system error.\nPlease add a new pattern before deleting this one.\n”);
}
}//GEN-LAST:event_jButton34ActionPerformed

 

K-Gater, Part 10


Although there are a few extra voice presets for the default software synthesizer, they behave exactly the same way as for the K-Pro buttons in the north panel. This section is for the Middle Panel.

jButton26ActionPerformed
Go up one in the default synth instrument list.

jButton27ActionPerformed
Go down one in the instrument list.

jButton28ActionPerformed
Set the dSaveTo flag for saving the instrument selected in jComboBox3 to one of the 12 default voice preset buttons.

jButton14ActionPerformed
jButton15ActionPerformed
jButton16ActionPerformed
jButton17ActionPerformed
jButton18ActionPerformed
jButton19ActionPerformed
jButton20ActionPerformed
jButton21ActionPerformed
jButton22ActionPerformed
jButton23ActionPerformed
jButton24ActionPerformed
The calls to SetVoice() for the 12 default software synthesizer preset buttons.

——————————-

Formatted text file.

// User clicked on the down button to select the next instrument from the combo box for the Java default software synthesizer.

private void jButton26ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton26ActionPerformed
int sel = jComboBox3.getSelectedIndex(); // Built-in Synth instrument Down select
if(sel > 0) {
sel–;
jComboBox3.setSelectedIndex(sel);
}
}//GEN-LAST:event_jButton26ActionPerformed

// User clicked on the up button to select the previous instrument from the combo box for the Java default software synthesizer.

private void jButton27ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton27ActionPerformed
int sel = jComboBox3.getSelectedIndex(); // Built-in Synth instrument Up select
if(sel < jComboBox3.getItemCount() – 1) {
sel++;
jComboBox3.setSelectedIndex(sel);
}
}//GEN-LAST:event_jButton27ActionPerformed

// User clicked on SaveTo for the Java default software synthesizer preset buttons.

private void jButton28ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton28ActionPerformed
dSaveTo = true; // Built-in Synth instrument Save to
}//GEN-LAST:event_jButton28ActionPerformed

// Ok, the next 12 buttons are going to all be the same agin. This time, they’re for the preset buttons for the
// Java default software synthesizer.

private void jButton14ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton14ActionPerformed
// Selected default synth channel 1
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo, 14, 0, dSaveTo);
}//GEN-LAST:event_jButton14ActionPerformed

private void jButton15ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton15ActionPerformed
// Selected default synth channel 2
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+1, 15, 0, dSaveTo);
}//GEN-LAST:event_jButton15ActionPerformed

private void jButton16ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton16ActionPerformed
// Selected default synth channel 3
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+2, 16, 0, dSaveTo);
}//GEN-LAST:event_jButton16ActionPerformed

private void jButton17ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton17ActionPerformed
// Selected default synth channel 4
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+3, 17, 0, dSaveTo);
}//GEN-LAST:event_jButton17ActionPerformed

private void jButton18ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton18ActionPerformed
// Selected default synth channel 5
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+4, 18, 0, dSaveTo);
}//GEN-LAST:event_jButton18ActionPerformed

private void jButton19ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton19ActionPerformed
// Selected default synth channel 6
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+5, 19, 0, dSaveTo);
}//GEN-LAST:event_jButton19ActionPerformed

private void jButton20ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton20ActionPerformed
// Selected default synth channel 7
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+6, 20, 0, dSaveTo);
}//GEN-LAST:event_jButton20ActionPerformed

private void jButton21ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton21ActionPerformed
// Selected default synth channel 8
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+7, 21, 0, dSaveTo);
}//GEN-LAST:event_jButton21ActionPerformed

private void jButton22ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton22ActionPerformed
// Selected default synth channel 9
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+8, 22, 0, dSaveTo);
}//GEN-LAST:event_jButton22ActionPerformed

private void jButton23ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton23ActionPerformed
// Selected default synth channel 10
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+9, 23, 0, dSaveTo);
}//GEN-LAST:event_jButton23ActionPerformed

private void jButton24ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton24ActionPerformed
// Selected default synth channel 11
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+10, 24, 0, dSaveTo);
}//GEN-LAST:event_jButton24ActionPerformed

private void jButton25ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton25ActionPerformed
// Selected default synth channel 12
// (int voiceNum, String voiceName, ch, buttonId, int kProId, boolean voiceSave)
dSaveTo = setVoice(jComboBox3.getSelectedIndex(), (String) jComboBox3.getSelectedItem(), midiPorts.defaultChannelNo+11, 25, 0, dSaveTo);
}//GEN-LAST:event_jButton25ActionPerformed

 

K-Gater, Part 9


Code for the North panel. These components are all dedicated to the voice preset buttons for the Korg Kaossilator Pro. jComboBox1 contains the list of K-Pro instrument names, and it’s just used to display and select the instruments. I added “Up” and “Down” buttons to make it easier to scroll through the list, but all they do is add or subtract 1 from the combo box selected index (plus checking whether I’m at the beginning or end of the list to avoid illegal index exceptions). The SaveTo button sets the flag for saving the selected K-Pro instrument to one of the 8 preset buttons. When the user selects a K-Pro button, it’s either to play that instrument, or to set a new instrument to that button. So, the listeners for jButtons 4 through 11 just call setVoice(), passing the ID for that button, the button number, the instrument index number and voice name, and the value of the kSaveTo flag.

jButton1ActionPerformed
Scroll up one in the instrument list in jComboBox1.

jButton2ActionPerformed
Scroll down one in the instrument list.

jButton3ActionPerformed
Set the kSaveTo flag.

jButton4ActionPerformed
jButton5ActionPerformed
jButton6ActionPerformed
jButton7ActionPerformed
jButton8ActionPerformed
jButton9ActionPerformed
jButton10ActionPerformed
jButton11ActionPerformed
K-Pro Instrument preset buttons. Either select that instrument to play, or save the new instrument name to a preset button.

————————-

Formatted Text File.

// Select previous item in K-Pro instrument list combo box.

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
int sel = jComboBox1.getSelectedIndex(); // Go up one in the instrument list
if(sel > 0) {
sel–;
jComboBox1.setSelectedIndex(sel);
}
}//GEN-LAST:event_jButton1ActionPerformed

// Select next item in K-Pro instrument list combo box.

private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
int sel = jComboBox1.getSelectedIndex(); // Go down one in the instrument list
if(sel < jComboBox1.getItemCount() – 1) {
sel++;
jComboBox1.setSelectedIndex(sel);
}
}//GEN-LAST:event_jButton2ActionPerformed

// User clicked on SaveTo button for K-Pro preset buttons.

private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed
kSaveTo = true; // Save Instrument selection to pattern button
}//GEN-LAST:event_jButton3ActionPerformed

// The next 8 functions are all the same. The user clicked on one of the K-Pro preset instrument buttons. Call setVoice() to
// toggle the specified button and play that instrument.

private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed
// Selected K-Pro instrument button 1
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 4, 0, kSaveTo);
}//GEN-LAST:event_jButton4ActionPerformed

private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed
// // Selected K-Pro instrument button 2
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 5, 1, kSaveTo);
}//GEN-LAST:event_jButton5ActionPerformed

private void jButton6ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton6ActionPerformed
// // Selected K-Pro instrument button 3
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 6, 2, kSaveTo);
}//GEN-LAST:event_jButton6ActionPerformed

private void jButton7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton7ActionPerformed
// // Selected K-Pro instrument button 4
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 7, 3, kSaveTo);
}//GEN-LAST:event_jButton7ActionPerformed

private void jButton8ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton8ActionPerformed
// // Selected K-Pro instrument button 5
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 8, 4, kSaveTo);
}//GEN-LAST:event_jButton8ActionPerformed

private void jButton9ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton9ActionPerformed
// // Selected K-Pro instrument button 6
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 9, 5, kSaveTo);
}//GEN-LAST:event_jButton9ActionPerformed

private void jButton10ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton10ActionPerformed
// // Selected K-Pro instrument button 7
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 10, 6, kSaveTo);
}//GEN-LAST:event_jButton10ActionPerformed

private void jButton11ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton11ActionPerformed
// setPatternButtonColor(7); // Selected K-Pro instrument button 8
// (int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
kSaveTo = setVoice(jComboBox1.getSelectedIndex(), (String) jComboBox1.getSelectedItem(), midiPorts.kProChannelNo, 11, 7, kSaveTo);
}//GEN-LAST:event_jButton11ActionPerformed

 

K-Gater, Part 8


We’re now getting into the component action listeners.  I haven’t counted them, but I’d guess that there are close to 60 listeners, however most of them are only 1 or 2 lines long.  Unfortunately, with the way WordPress doesn’t handle formatting, and Netbeans inserts self-generated control statements, the following code is going to be a bit hard to read. For those of you not familiar with Netbeans, when it creates an action listener, it adds control text to the listener name to prevent the user from changing that line accidentally.  The control text takes the form of “//GEN-FIRST:event_formWindowClosing” and “//GEN-LAST:event_formWindowClosing”, in the source file.

I’m moving the listeners around from the way they’re positioned in the java file to give them more logical groupings, and I’ll break them up to match the components in each of the panels (north-west-middle-east). But I think that most of the code is pretty easy to figure out, so I’ll only comment on the bigger blocks.

I’ll start out with the listeners for the menu bar.

formWindowClosing
User is closing the app window.  Do clean-up before exiting.

jMenuItem1ActionPerformed
You may notice that there is no listener for jMenuItem1.  That’s because I don’t know if I should delete the item entirely or not.  I originally added File -> New Patch to the menus, but as I was writing the app, I realized that 1) there’s not much point in creating a clean patch setting, since it’s easy enough for the user to just make setting changes as desired; and 2) I can’t delete all of the arp patterns from the combo box without it throwing null pointer exceptions.

jMenuItem2ActionPerformed
User wants to read the patch objects from a file.  Simultaneously, load the associated gate arpeggiator patterns file associated with the first patch in the list.  This is buggy, I know.  There’s a good chance that the user may save the patterns to another file, adding or deleting them, and the patches won’t have the correct pattern file loaded. I have to figure out how to address this some day. Either way, I didn’t feel like making a separate method for this code, so I just left it in the listener.

jMenuItem3ActionPerformed
User wants to save the patch file.

jMenuItem4ActionPerformed
User clicked on SaveAs Patch File.  The code in the associated method got a little tricky, since I had to deal with the Approve and Cancel results from 2 sets of dialogs. First, the file dialog for selecting the filename, and second the warning message for overwriting an existing file.  It’s not elegant, but I think I got it right.

jMenuItem8ActionPerformed
User clicked on Read Arp File.

jMenuItem9ActionPerformed
User clicked on Save Arp File.

jMenuItem6ActionPerformed
Edit -> Change K-Pro Channel Number. Pass the string from the dialog box to the midiPorts object for error checking and saving to a variable.

jMenuItem10ActionPerformed
Edit -> Change Keyboard Channel Number.  Turns out that I completely misunderstood the difference between a port (used for connecting receivers and transmitters) and MIDI channels (used to assign channels numbers to instruments for use via the MIDI cables).  The Roland keyboard is used as a MIDI controller, so it doesn’t have a MIDI channel assigned to it. While it does have three ports, they don’t affect the MIDI channel numbering for the K-Pro or the default software synth.  So I’m going to delete this menu item in the future.

jMenuItem11ActionPerformed
Edit -> Default Software Synth Starting Channel Number.

jMenuItem12ActionPerformed
Edit -> Retry K-Pro.
I mentioned earlier that I wanted to allow the user to start the app and then turn on the K-Pro.  This listener would allow the user to re-read the MidiSystem settings to find the K-Pro driver and get a pointer to it.  Unfortunately, because System didn’t update after 5 minutes, my idea isn’t working and I just deactivated this option.

jMenuItem7ActionPerformed
About -> About.  Just display the About box.

———————-

Formatted Text File.

// User clicked on Close Window. Stop timer and close MIDi devices.

private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
masterTimer.cancel();
kProAllOff();
if(kProDevice != null) {
kProDevice.close();
}
synth.close();
}//GEN-LAST:event_formWindowClosing

// User clicked on Read Patch File

private void jMenuItem2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem2ActionPerformed
patch p;                                                                    // Read Patch file
JFileChooser fileRead = new JFileChooser();
FileFilter ft = new FileNameExtensionFilter(“Patch Patterns”, “ptc”);
fileRead.setFileFilter(ft);
int userChoice = fileRead.showOpenDialog(this);

if(userChoice == JFileChooser.APPROVE_OPTION) {
try {
BufferedReader reader = new BufferedReader(new FileReader(fileRead.getSelectedFile()));
String line = “”;
while((line = reader.readLine()) != null) {
if(! line.isEmpty()) {
p = new patch();
p.parseFromFile(line);
patchList.add(p);
}
lastSavedPatchFile = fileRead.getSelectedFile().getPath();
}
} catch (Exception ex) {
jTextArea1.append(“\nCouldn’t open |” + fileRead.getSelectedFile() + “|.\n”);
jTextArea1.append(ex.toString());
}

File f = new File(patchList.get(0).arpPatternFile);   // Read the arp patterns and pre-load the combo box.
readArpPatFile(f);

String s = “”;                                        // Add patches to combo box
for(patch patch1 : patchList) {
s = patch1.name;
jComboBox4.addItem(s);
}
jComboBox4.setSelectedIndex(0);                       // Pre-select patch 0 just to be on the safe side.
}
}//GEN-LAST:event_jMenuItem2ActionPerformed

// User selected Save Patch objects to a file.

private void jMenuItem3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem3ActionPerformed
savePatchFile(lastSavedPatchFile);                                         // Menu -> File -> Save Patch File
}//GEN-LAST:event_jMenuItem3ActionPerformed

// User selected SaveAs Patch file from the menu bar.

private void jMenuItem4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem4ActionPerformed
savePatchFile(“”);                                                         // File -> Menu -> SaveAs Patch File
}//GEN-LAST:event_jMenuItem4ActionPerformed

// User clicked on Read Arp Patterns.

private void jMenuItem8ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem8ActionPerformed
JFileChooser fileRead = new JFileChooser();                                  // File Menu Item ->Read Arp Patterns File
FileFilter ft = new FileNameExtensionFilter(“Arp Patterns”, “arp”);
fileRead.setFileFilter(ft);
int userChoice = fileRead.showOpenDialog(this);

if(userChoice == JFileChooser.APPROVE_OPTION) {
readArpPatFile(fileRead.getSelectedFile());
}
}//GEN-LAST:event_jMenuItem8ActionPerformed

// User clicked on Save Arp File.

private void jMenuItem9ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem9ActionPerformed
String s          = “”;
boolean done      = false;
JFileChooser fileSaveChoice = new JFileChooser();                                // Save arp file
FileFilter ft = new FileNameExtensionFilter(“Arp Patterns”, “arp”);
fileSaveChoice.setFileFilter(ft);
int userChoice = JFileChooser.CANCEL_OPTION;

while (! done) {
userChoice = fileSaveChoice.showSaveDialog(this);
if(fileSaveChoice.getSelectedFile().exists()) {
userChoice = JOptionPane.showConfirmDialog(this, “File already exists.\nOverwrite it?”, “Patch File Save”, JOptionPane.YES_NO_OPTION);
if(userChoice == JFileChooser.APPROVE_OPTION) {
done       = true;
}
} else {
done = true;
}
}

if(userChoice == JFileChooser.APPROVE_OPTION) {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(fileSaveChoice.getSelectedFile()));
for(ArrayList a : arpPatternsList) {
s = “”;
for(int i=0; i < a.size(); i++) {
s += ” ” + a.get(i);
}
writer.write(s.trim() + “\n”);
}
writer.flush();
writer.close();
JOptionPane.showMessageDialog(this, “File Saved.”);
selectedArpFile = fileSaveChoice.getSelectedFile().getPath();
} catch(IOException ex) {
System.err.println(“Couldn’t save file”);
JOptionPane.showMessageDialog(this, “Couldn’t Save File.\n” + ex);
}
}
}//GEN-LAST:event_jMenuItem9ActionPerformed

// User Clicked on K-Pro Channel

private void jMenuItem6ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem6ActionPerformed
String s = (String)JOptionPane.showInputDialog(this, “K-Pro MIDI Channel:”, “Channel”, JOptionPane.PLAIN_MESSAGE, null, null, midiPorts.kProChannelNo);
midiPorts.setKProChannel(Integer.parseInt(s));
}//GEN-LAST:event_jMenuItem6ActionPerformed

// User Clicked on Keyboard Channel.

private void jMenuItem10ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem10ActionPerformed
String s = (String)JOptionPane.showInputDialog(this, “Keyboard MIDI Channel:”, “Channel”, JOptionPane.PLAIN_MESSAGE, null, null, midiPorts.keyboardChannelNo);
midiPorts.setKeyboardChannel(Integer.parseInt(s));
}//GEN-LAST:event_jMenuItem10ActionPerformed

// User clicked on Default Synth Channel.

private void jMenuItem11ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem11ActionPerformed
String s = (String)JOptionPane.showInputDialog(this, “Default Synth Starting MIDI Channel:”, “Channel”, JOptionPane.PLAIN_MESSAGE, null, null, midiPorts.defaultChannelNo);
midiPorts.setDefaultSynthChannel(Integer.parseInt(s));
}//GEN-LAST:event_jMenuItem11ActionPerformed

// My original plan was to allow the user to turn on the K-Pro after starting the app, and then using the Find K-Pro option
// from the menu to see if the K-Pro driver was running yet.  However, the System object doesn’t seem to be refreshing
// properly.  According to the Java forums, refresh defaults to every 60 seconds, although it’s possible to set it shorter.
// But,after 5 minutes, System still hadn’t refreshed and the K-Pro driver wasn’t in the MIDI information list.  So I deactivated
// this option.

private void jMenuItem12ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem12ActionPerformed
boolean quit = false;                                                          // Menu -> Edit -> Retry K-Pro Read
int cntr = 1;
jTextArea1.append(“——————-\n”);
while(! quit) {
initMidiDevices();
jTextArea1.append(“——————- Attempt No.: ” + cntr + “\n”);
cntr++;
if(kProDevice != null) {
quit = true;
} else {
int n = JOptionPane.showConfirmDialog(this, “K-Pro still not found.\nTry again?”, “Find K-Pro”, JOptionPane.YES_NO_OPTION);
if(n == JFileChooser.CANCEL_OPTION) {
quit = true;
}
}
}
}//GEN-LAST:event_jMenuItem12ActionPerformed

// Display the About box.

private void jMenuItem7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem7ActionPerformed
JOptionPane.showMessageDialog(this, “Kaossilator MIDI Driver and Arpeggiator\nCopyright Curtis H. Hoffmann\n(c)2012”, “About K-Gator”, JOptionPane.PLAIN_MESSAGE);
}//GEN-LAST:event_jMenuItem7ActionPerformed

K-Gater, Part 7


Now that I think about it, the idea of getting a receiver for talking to the K-Pro every time I play a note or change a program voice is kind of silly. When the gate arp is cranked down to 1/32 seconds, I’m making calls to the K-Pro play note function over 100 times per second. I wrote the code this way originally because it kind of felt right from an object-oriented viewpoint. But, it wastes system resources and adds to the garbage collection workload. So, in a future version, I’ll move the receiver object up to where I open the K-Pro MidiDevice, and just assume that the receiver still exists whenever I need it. Since I’m not running any other MIDI programs, there shouldn’t be a problem with locking the K-Pro to just one receiver all the time. (Besides, in theory, the K-Pro can handle an infinite number of receivers…)

Some more function definitions:

private void playArp(int n)
This is the engine for the gate arpeggiator, and is called by the master timer when the count reaches the value set in arp.arpTmr.
This section is kind of calculation-heavy. The basic idea of an arpeggiator is to look at the key being pressed on the keyboard, and at a minimum, turn the associated note on and off. The first step is to determine the over-all rate of the ‘pegging. I’m using jSlider2 to select a number between 0 and 10, which I then use in an integer array to display the text string, from “4 sec.”, down to “1/32 sec.”, and a second array to get the rate in milliseconds. The second step is to set the on/off ratio. If the rate is 1 second, and the ratio is 60%, then the note will play for 600 ms, and be off for 400ms. Depending on where we are in time, the master timer has to know that we’re in the On period, or the Off period. The easiest way to do this is to precalculate the on and off times from rate and ratio, then put the appropriate value into arp.arpTmr, while keeping the on/off time in arp.onOff. For a more advanced ‘pegger, we use a pattern to change the played note. This presents a problem. While the K-Pro can only play one note at a time, the software synth is polyphonic. If we don’t turn the current note off first, it will keep playing as we toggle the next note. If the user plays two notes at a time, the ‘pegger needs to know about it and toggle all notes simultaneously. The best option is to only ‘peg one key at a time and check whether the notes are changing, and if so, turn the last note off before changing notes. As for which note to play next, we need to know what arp pattern we’re playing, and where in the pattern we are (looping from the beginning of the pattern). The note is then calculated as:
new = keyboardBase + currentKey * keySpacing + patternValue * arpSpacing

if keyboard base is 40, the current key is 6, key spacing is 1, the pattern is “0 1 2 3” and arp spacing is 2,
We’ll loop through the notes 46, 48, 50 and 52.
And, if the rate is 1 sec., we’ll play the pattern at 1 second per note.

Another feature of the gate arp is that it supports my voice presets. The K-Pro defaults to channel 0, and each new instrument will play using channel 0. But the software synth can use all 16 channels, and each voice is assigned to a different channel. playArp() needs to specify the software synth channel number as part of the MIDI NOTE_ON/NOTE_OFF message, or the wrong instrument will sound.

private void kProProgram(int no)
This is a simple function. We want to change the instrument voice that we’re playing. There are 200 K-Pro voices, and MIDI messages only go from 0 to 127, so we need to specify which bank to use. K-Pro bank switching occurs through CC messages. Writing a 1 with CC #0 selects the lower bank, and a 1 to CC #32 selects the upper bank. I get the instrument number from jComboBox1, and use the lower bank for 0-127, and the upper bank for 128 to 199. The voice number itself is passed using a MIDI PROGRAM_CHANGE message. The entire voice change operation requires 3 send message calls.

private void kProNote(int ch, int col, int row, int onOff)
The K-Pro uses a touch screen divided up into 128 rows and columns. To specify a given note, Korg opted to use 3 CC messages (and 3 MIDI send calls). CC #12 selects the row, CC #13 selects the column, and CC #92 turns the note on (1) or off (0). Currently, I’m grabbing a receiver to the K-Pro every time this function is called, which is inefficient.

private void kProAllOff()
As mentioned above, the K-Pro can only play one note at a time, and turning off any random note causes whatever note is currently playing to stop. So, kProAllOff() just tells note 0 to turn off.

private boolean setVoice(int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave)
setVoice() is intended to toggle the instrument voice buttons on and off between the K-Pro and the software synth, while updating the button text to whatever instrument the user selects from the jComboBox. I preloaded the jButton objects to an ArrayList for this purpose.
voiceNum – the item number selected from the combo box
voiceName – text string containing the instrument name to assign to a button, if any
ch – MIDI channel number, used by the software synth
buttonId – number of the jButton to update
kProId – number of the preset button for the K-Pro presets, used for changing the instrument the K-Pro is currently playing
voiceSave – True – assign a new voice to the preset buttons. False – Just switch voices

Change the button background color to red for the active button and light gray for all deactivated buttons.
Change instruments for either the K-Pro, or the software synth.

private boolean isInt(String s)
I don’t understand why there’s no built-in test for whether a string contains a valid integer value. The only option is to create my own function to try to convert the number and if it fails, catch the exception and return false.

private void savePatchFile(String fName)
I’ve mentioned before that I’ve never been able to make object serialization work for saving objects to a file. So, I’m doing this the brute-force way. Also, the file menu has “Save Patch” and “SaveAs Patch” options. A “patch” consists of all of the UI screen settings (channel numbers, instrument presets, arp patterns, volume) used to recreate the user’s set-ups. Each new patch is added to the patchList ArrayList. If the user read the patches from a file, I store that name to lastSavedPatchFile. If they’ve already saved the patches during this session, the selected filename is stored in lastSavedPatchFile. This way, if the user clicks on File->Save Patch, whatever name is in lastSavedPatchFile will be passed to us as fName. However, if fName is still empty, we want File->Save Patch to call the File Dialog. And, if the user selected SaveAs Patch, I’m passing “” to fName to force the file dialog box to open. Everything else is error handling, in case the user clicks on Cancel at some point. Finally, if the user does want us to save to file, I just loop through the patchList, convert each patch to a string, and write the string to file.

—————— Raw Code

Formatted text file.

// Even without an external keyboard plugged in, this program can make music by running arpeggiator patterns.
// Thus, playArp() is the heart of the app right now.

private void playArp(int n) { // Driver code for arpeggiating either default synth or K-Pro
arp.arpTmr = -1; // Turn off timer so don’t get double-noting
if(n != arp.currentNote) { // If playing a different note, force previous note off
if(channelNo == midiPorts.kProChannelNo) {
kProNote(midiPorts.kProChannelNo, arp.currentNote / 128, arp.currentNote % 128, 0);
} else {
channels[channelNo].noteOff(arp.currentNote);
}
arp.currentNote = n; // Load new note
}

if(arp.onOff) { // Arpeggiating when note is off based on rate and ratio
arp.arpTime = arp.offTime;
if(channelNo == midiPorts.kProChannelNo) {
kProNote(midiPorts.kProChannelNo, n / 128, n % 128, 0);
} else {
channels[channelNo].noteOff(arp.currentNote);
}
} else { // Arpeggiating when note is on based on rate and ratio
arp.nextNote(keyboardBottomNote + keyboardSpacing * keyboardKey); // Get the next note in the arp pattern based on current keyboard key pressed
arp.arpTime = arp.onTime;
if(channelNo == midiPorts.kProChannelNo) {
kProNote(midiPorts.kProChannelNo, arp.calcNote / 128, arp.calcNote % 128, 1);
} else {
channels[channelNo].noteOn(arp.currentNote, keyboardVolume);
}
}
arp.onOff = (! arp.onOff); // Switch from arp note on to off and vice versa
arp.arpTmr = 0; // Turn arp timer back on
}

private void kProProgram(int no) { // Change K-Pro instrument
int bank = 0;
int nbank = 0;
int ch = 0;
ShortMessage myMsg = new ShortMessage();
Receiver rcvr = null;
long timeStamp = -1;
if(kProDevice != null) {
try {
rcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
}
try {
bank = (no < 127) ? 0 : 1;
nbank = (bank == 1) ? 0 : 1;
ch = (no < 127) ? no : no – 128;
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 0, nbank);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 32, bank);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.PROGRAM_CHANGE, midiPorts.kProChannelNo, ch, 0);
rcvr.send(myMsg, timeStamp);
} catch (javax.sound.midi.InvalidMidiDataException e) {
}
}
}

// The Korg K-Pro uses an x-y touchpad for playing music. In order to control it via software, we need to
// send 3 MIDI CC (change control) messages. One to set the row, one to set the column, and one to specify the
// note as on or off (1 or 0).

private void kProNote(int ch, int col, int row, int onOff) { // Change K-Pro note (x-y pad)

ShortMessage myMsg = new ShortMessage();
Receiver rcvr = null;
long timeStamp = -1;
if(kProDevice != null) {
try {
rcvr = kProDevice.getReceiver();
} catch (MidiUnavailableException e) {
}
try {
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 12, row);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 13, col);
rcvr.send(myMsg, timeStamp);
myMsg.setMessage(ShortMessage.CONTROL_CHANGE, midiPorts.kProChannelNo, 92, onOff);
rcvr.send(myMsg, timeStamp);
} catch (javax.sound.midi.InvalidMidiDataException e) {
}
}
}

private void kProAllOff() { // Brute-force method to ensure current note is turned off
kProNote(midiPorts.kProChannelNo, 0, 0, 0);
}

// When the user selects an instrument from the screen, there’s two choices. They can either click “SaveTo” first, to save
// an instrument from the pull down combo box to one of the voice preset buttons, or they can just click the preset and
// play the voice previously assigned to that button. setVoice takes the item number from the combo box, the name of the
// instrument, the channel number, the K-Pro instrument number (if any) and a flag indicating whether SaveTo was selected
// first. If the Java software synth is being played, there’s no K-Pro ID number, just a channel number (0-11). If it’s
// the K-Pro, then we’ll get a number 0-7 for the desired preset button.
//
// setVoice returns the SaveTo flag value.
// Regardless of the purpose, the idea is to toggle one of the preset buttons on and off, change the background color
// of the button, possibly change the button text to display the instrument name, and to select the channel to play.

private boolean setVoice(int voiceNum, String voiceName, int ch, int buttonId, int kProId, boolean voiceSave) {
boolean retSave = voiceSave;
channelNo = ch;

jBList.get(lastButtonSelected – 1).setBackground(Color.LIGHT_GRAY); // Return previous button to Gray.

lastButtonSelected = buttonId;
jBList.get(buttonId – 1).setBackground(Color.red); // Turn current intrument button red.

if(voiceSave) {
retSave = false;
jBList.get(buttonId – 1).setText(voiceName); // Display instrument name on button
if(ch == midiPorts.kProChannelNo) {
kProVoices[kProId] = voiceNum;
kProProgram(kProVoices[kProId]);
} else {
defaultVoices[ch – midiPorts.defaultChannelNo] = voiceNum;
channels[channelNo].programChange(aInstruments[voiceNum].getPatch().getBank(), aInstruments[voiceNum].getPatch().getProgram());
}
} else {
if(ch == midiPorts.kProChannelNo) {
kProProgram(kProVoices[kProId]);
}
}
return(retSave);
}

// Java doesn’t have a built-in function for testing if a string contains a valid integer. So, try to convert the string
// to an integer and return false if it throws an exception.

private boolean isInt(String s) {
boolean ret = false;
try {
Integer.parseInt(s);
ret = true;
}
catch(NumberFormatException nfe) {
}
return ret;
}

private void savePatchFile(String fName) {
JFileChooser fileSave = new JFileChooser(); // Save arpeggiator patch file
FileFilter ft = new FileNameExtensionFilter(“Patch Patterns”, “ptc”);
fileSave.setFileFilter(ft);
boolean done = false;
int userChoice = JFileChooser.APPROVE_OPTION;
File fileSaveName = null;

if(fName.trim().isEmpty()) {
while (! done) {
userChoice = fileSave.showSaveDialog(this);
fileSaveName = fileSave.getSelectedFile();
if(userChoice == JFileChooser.CANCEL_OPTION) { // User cancelled save option
done = true;
} else {
if(fileSave.getSelectedFile().exists()) { // User selected file to save to.
userChoice = JOptionPane.showConfirmDialog(this, “File already exists.\nOverwrite it?”, “Patch File Save”, JOptionPane.YES_NO_OPTION);
if(userChoice == JFileChooser.APPROVE_OPTION) { // User wants to overwrite existing file.
done = true;
}
} else { // Writing to new file.
done = true;
}
}
}
} else { // Saving changes back to previous file.
fileSaveName = new File(fName);
}

if(userChoice == JFileChooser.APPROVE_OPTION) {
for(patch p : patchList) {
String s = p.makeString();
}

try {
BufferedWriter writer = new BufferedWriter(new FileWriter(fileSaveName));
for(patch p : patchList) {
writer.write(p.makeString() + “\n”);
}
lastSavedPatchFile = fileSaveName.getPath();
writer.flush();
writer.close();
JOptionPane.showMessageDialog(this, “File Saved.”);
} catch(IOException ex) {
System.err.println(“Couldn’t save file”);
JOptionPane.showMessageDialog(this, “Couldn’t Save File.\n” + ex);
}
}
}