// Fragment 5 // 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); } } }