Introduction

This was my final project for COEN 275 Object-Oriented Analysis, Design and Programming course.  The class it self was pretty straight forward, felt like more of a beginning Java course than a masters course.  I thought there would be more analysis and design patterns then just re-learning Java and the OOP paradigm that I had learned in other courses, but that is for another post or blog.  That being said we had the option to choose our final project for this class.  It was a week of planning and I think just under two weeks of coding, followed by presentations.  Looking back at this code now it is a really simply project with a majority of the time spent reading up on the Java Sound API.  The whole project hinged on understanding the API and what it could and couldn’t do.  Overall this was a fun coding project and one I had always wanted to do, but didn’t have time with classes and other projects.  So it was nice to have time to focus on this as a “class” project, but also know that is was partly a passion project.

Why The Virtual Synthesizer

I love audio!  Always have always will.  I used to be involved in sound for video games, but that industry is a gamble and projects come and go, without the comfort of stability for most.  That was a large factor in me leaving the gaming industry.  My inquisitive mind always wanted to know how the tools I was using to create audio worked, so it became an obsession to learn what made these things tick.  Having always wanting to build my own virtual synthesizer, but not really knowing how I stumbled across the Java Sound API and the MIDI synthesis and sequencing functionality that is built into library.  This was a great place to start, and an easy way to get my feet wet with the API.  So I pressed forward reading about all aspects of the API, from the low level audio manipulation to the MIDI capabilities.  Once I felt comfortable with how the library worked and looking up multiple examples online I started coding.  The next section will talk about the main parts of the synth I built and what all Java synths need to work properly.  All code can be found at my GitHub repo.

Project Layout

Because this was an OOP class I wanted to abstract everything down as far as I could.  So if you look at the picture to the right you will see how I laid out the project and the different classes I used.  There is the main function class that is the entry point the the synth, which calls the MidiSetup class to initialize the MIDI functionality of the API.  Your probably noticing a bunch of handler classes.  These do what the class name states handle some part of the synthesizer.  For example there is the pan handler that updates which speaker is getting the audio signal.  Then there is the reverb, tremolo and chorus handlers that toggle on and off those specific audio effects.  The two main pieces to this whole synthesizer is the InstrumentHandler class and the NoteHandler class.  These two class do all the heavy lifting once the MidiSetup class initializes the synth.  I will go into more detail about all three of the mentioned classes and how all the pieces connect to each other.  Finally there are two last classes the JKnob class and the InstrumentComboBox class.  The JKnob class was written by Grant William Braught, and is a great piece of code that made my synth UI look slick.  It was a little hard to work with initially, but once you wrap your head around how it functions it was easy to tie into the UI and interfacing with the PanHandler class.  The InstrumentComboBox class is pretty straight forward a combo box with all the available instruments in a drop down list for selection.  In the next couple sections I’ll go over the KeyButton class and how it interfaces with the NoteHandler class, as well as the other MidiSetup and InstrumentHandler classes.

MidiSetup Class

This is the main class of the entire program.  Without this class the synth couldn’t function because this is where we initialize the MIDI device and instruments that make up the synthesizer.  This is were most of the calls to the Java Sound API are made.  There are a couple other places, but without this class those calls would be meaningless.  So lets walk through the code below.  The most important part of the MidiSetup class is this little snippet of code.


// initialize the midi synthesizer
MidiSetup(){
try {
synth = MidiSystem.getSynthesizer();
synth.open();
mc = synth.getChannels();
instr = synth.getDefaultSoundbank().getInstruments();
synth.loadInstrument(instr[0]);
} catch (MidiUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

The above method creates the Java synthesizer midi object.  The snippet is pretty straight forward.  Create the synthesizer, then open the synth.  Once opened make a call to get the available midi channels.  This call returns an array of available channels.  According to the Java documentation the MIDI 1.0 specification allows for up to 16 channels.  Once the array of channels is obtained, the synth makes a call to get the default sound bank that is part of the Java Sound API, for more information follow this link.


private Synthesizer synth;
private MidiChannel[] mc;

We can see that there is a private synthesizer variable that is protected in this class, and a lot of the methods in the class are just getter and setter methods for both the private Synthesizer and MidiChannel variables shown above.  All the previous methods we talked about rely on these two variables.  Below is the full MidiSetup class code.

import javax.sound.midi.*;
import javax.sound.midi.MidiDevice.Info;

/**
 *	@author Anthony Harrell
 * 	@author Santa Clara University
 *	@version 3/2015
 */

public class MidiSetup {

	private Synthesizer synth;
	private MidiChannel[] mc;
	Instrument[] instr;

	// initialize the midi synthesizer
	MidiSetup(){
		try {
			synth = MidiSystem.getSynthesizer();
			synth.open();
			mc = synth.getChannels();
			instr = synth.getDefaultSoundbank().getInstruments();
			synth.loadInstrument(instr[0]);
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	// load requested instrument in the synthesizer
	// unload previous instrument from the synthesizer
	public void setInstrument(int nInstr){
		int p = mc[1].getProgram();
		synth.unloadInstrument(instr[p]);
		synth.loadInstrument(instr[nInstr]);
		mc[1].programChange(nInstr);
		mc[0].programChange(nInstr);
	}

	// return currently active midi channel
	public MidiChannel[] getMidiChannel(){
		return this.mc;
	}

	// returns current synthesizer loaded
	public Synthesizer getSynth(){
		return this.synth;
	}

	// returns current instrument loaded
	public Instrument[] getInstrument(){
		return this.instr;
	}

	// create array of instrument names for user selection
	public String[] getInstrNames(){
		String[] instrArray = new String[this.instr.length];

		for(int i=0; i < this.instr.length; i++){
			instrArray[i] = this.instr[i].getName();
		}
		return instrArray;
	}

	/* helper function to get midi information 
	 * only for debugging purposes
	 * should remove if ever released */
	public void getMidiInfo(){
		Info[] info = MidiSystem.getMidiDeviceInfo();
		try {
			for(int i=0; i < info.length; i++){
				System.out.println(MidiSystem.getMidiDevice(info[i]));
				//System.out.println(info[i]);
			}
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

KeyButton & NoteHandler Classes

The KeyButton class inherits from the JButton class using the Java “extends” keyword.  I wanted to extend JButton because of its built in functionality to detect interaction.  I didn’t feel like reinventing the wheel in such a short amount of time, and there was really no need to.  All I needed was the action listener, which is built into the JButton class.  If we look at the below code, there isn’t much to it.  I have a default constructor, and then a standard constructor that takes and int to assign to the midiNote, and a String for the noteName which is a musical note of A, B, C, D, E or F.  The number corresponds to the midi note of a keyboard and is between 0 and 127.  We can see that I add 60 to any incoming number so that it includes middle C as the first note.  Currently the synth only supports one octave.  I want to add support to make it more like other synths that let you select the octave you want to be in.  For the purpose of getting this done for my class I just stuck with the one octave and included middle C, like most synth default to.  The rest of the code is just getters to relay information back to the noteHandler class that we will be looking at next.  The main purpose of extending the JButton class was to create individual keyButton objects, as this gave more control to the overall application and makes it easier for updating the code later.  Also, this course was an OOP analysis and design course, so I wanted to encapsulate and abstraction as much as possible.

import javax.swing.JButton;

public class KeyButton extends JButton{
    /**
     *     KeyButton.java - 
     *   Creates key for the keyboard layout 
     *   which functions as a button by extending JButton
     *   using this as so an event handler can detect interaction
     *
     * @author Anthony Harrell
     * @author Santa Clara University
     * @version 3/2015
     */
    private int midiNote;
    private String noteName;

    // default constructor
    KeyButton(){

    }

    // constructor
    KeyButton(int num, String name){
        this.midiNote = num;
        this.noteName = name;
    }

    // set note number add 60 so middle C is in the keyboard
    // key numbering based on the 0 - 127 MIDI count
    public void setNoteNum(int n){
        this.midiNote = n + 60;
    }

    // set note name (A, B, C, D, E, F)
    public void setNoteName(String s){
        this.noteName = s;
    }

    // returns the note number of the key
    public int getNoteNum(){
        return this.midiNote;
    }

    // returns the name of the key
    public String getName(){
        return this.noteName;
    }
}

To get the KeyButton class to detect user input I needed to implement the NoteHandler class which is shown below.  This class implements the needed ActionListener and the overridden method actionPerformed  that is needed .  First I initialize an instance of the object and assign an instance of the MidiSetup object to this instance, so we can make calls the the synthesizer and the built in Sound API.  We can see that all this class does is listen for the user input then calls the “noteOn” and “noteOff” methods of each KeyButton‘s MidiSetup object.  One thing we can see is that the “actionPerformed” method requires a try and catch call to catch exceptions for the “Thread.sleep” call.  This call is only for certain instruments which don’t have a decay built in.  For example any of the organs.  If any note is played it will continue forever or until the instrument is switched.  The sleep only lets it play for half a second before sending the “noteOff” call.  This is another issue I want to look into, but I’ll touch on all these issue in a later section.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 *	@author Anthony Harrell
 * 	@author Santa Clara University
 *	@version 3/2015
 */

public class NoteHandler implements ActionListener{
	private MidiSetup midiObject;
	private KeyButton key;

	NoteHandler(MidiSetup mO){
		this.midiObject = mO;
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		if(e.getSource() instanceof KeyButton){
			key = (KeyButton)e.getSource();

			//midiObject.getMidiChannel()[0].noteOn(key.getNoteNum(), 127);
			midiObject.getMidiChannel()[1].noteOn(key.getNoteNum(), 127);

			try {
				Thread.sleep(250);
				midiObject.getMidiChannel()[1].noteOff(key.getNoteNum(), 0);
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

		}
	}

}

Conclusion & ToDo's

Overall this was a fun project to get to do for class.  I’ve always wanted to build something like this and to get the opportunity to do so in class was great.  That being said this was done fairly quickly and there are plenty of optimizations and features that are missing.  I leave it to you to add anything you would like or use any of the code for a project you are doing.  If you want to fork the repo and build out this synthesizer more feel free to send my pull requests on new features that you have added.  There are plenty of things that I want to change, but we’ll see if I get around to it.  Below is a list of features that I hope to one day add.

  • Interface with external MIDI controller
  • Implement more MIDI controls, i.e. pitch bend, sustain, modulation, etc…
  • Implement full piano keyboard when the JFrame window is expanded, it is currently locked on two octaves which include middle C
  • Update to Applet so it can be used in a web browser
  • Add control for sustain duration that interacts with the thread sleep in ActionListener
  • Whatever the community wants to add to make it a more robust MIDI synthesizer