JSynthLib Programmer's Guide

Last modified: $Date: 2009-01-19 13:08:23 -0800 (Mon, 19 Jan 2009) $

Go to JSynthLib User's Guide.

Table of Contents

  1. Before we start...
    1. Coding Style
    2. Using Javadoc
    3. Proper Use of Access Control Modifier
    4. Code Sharing
    5. Debug/Error Message
    6. Exception Handling
  2. How to Compile Files and Check Out from SVN
  3. Writing a Synth Driver
    1. Introduction
      1. How hard is it to add support for a new synthesizer to JSynthLib?
      2. What do I need in order to add support for a new synthesizer?
      3. Who owns the copyright to the code I contribute to JSynthLib?
    2. The Layout of JSynthLib
    3. JSynthLib API Document
    4. Synth Driver Structure
    5. Writing a Device Class
      1. Create a file
      2. Code Your Device Class
      3. Keeping Persistent Parameters
      4. Tell the core program about your driver
    6. Writing a Single Driver
      1. Create a file
      2. Write the damn thing
      3. Implement calculateChecksum Method
      4. Implement storePatch and sendPatch Methods
      5. Implement createPatch and editPatch Methods
    7. Writing a Bank Driver
    8. Writing a Single Editor
      1. How to Add SysexWidgets
      2. The provided SysexWidgets
      3. Param Model and Sender
      4. Action Listener
    9. Writing a Converter
  4. Testing the driver
    1. Introduction
    2. Testing your Single Driver
    3. Testing your Bank Driver
    4. Testing your Editor
  5. FAQ
  6. Related Documentation and Links
    1. JSynthLib
    2. Java Language
    3. MIDI Specification
    4. Applications

I. Before we start...

First a few words about writing code which will be shared by many developers. This is a project to which many people are joining to. Readability of code is very important.

Even if someone want write just a driver for his/her synth, the code will be read by others too. Maybe some changes are necessary since an API change or another developer want take a look how a special issue is solved. Following some rules makes the life easier for all who contribute to JSynthLib.

I.a. Coding Style

During developing driver you will/have to read source codes in JSynthLib distribution. Other programmers will read your source code in the future. Use consistent coding style. Code Conventions for the Java Programming Language is a good reference. Sample codes in The Java Programming Language by Ken Arnold and James Gosling (creators of Java language) are good examples. We encourage you to follow the coding style in these documents especially you are editing source code in core package.

Important note: Please use an indentation level of 4 and a displayed tab width value of 8 (see FAQ for reasons).

I.b. Using Javadoc

Add javadoc on important members (methods and fields). public and protected member must be documented.

I.c. Proper Use of Access Control Modifier (for core developers)

This is especially important for codes under "core/". It would be much easier for programmers to write a driver if the methods and the fields were used properly. If private or protected is used properly, for example, there is no need to grep all files to see how a method or a field is used.

Especially we have to be very careful to make a public (or package) field. If you really need to access it, consider defining a getter or setter method (ex. getFoo() or setFoo()) or using protected.

I.d. Code Sharing

Share the code if possible. Use subclass (inheritance) or static method properly. If you copy a whole method, something wrong. If you divide a long method into several methods, the method would be more easy to be overridden. (More importantly a small method is easy to maintain and would have less bug.)

I.e. Debug/Error Message

Use ErrorMsg.reportStatus("message") for debug message. This way the message is only printed if JSynthLib is run in debugging mode (AKA java -jar JSynthLib.jar 2). Don't use System.out.println.

Use ErrorMsg.reportError("Title", "Message", e) for error message which users need to see.

I.e. Exception Handling

Don't hide Exception just to stop compile error.
    // BAD EXAMPLE
    try {
        ....
    } catch (Exception e) {
    }
This makes debugging difficult. Catch a specific Exception and put a comment, if you know that it is not an error.
    try {
        ....
    } catch (ASpecificException e) {
        // This is normal.
    }
If it is an error or you are not sure, show a debug message. (Of course, implement proper error handling code if possible.)
    try {
        ....
    } catch (ASpecificException e) {
        ErrorMsg.reportStatus(e);
    }
Again catch a specific Exception. Don't catch Exception without good reason.

II. How to Compile Files and Check Out from SVN

JSynthLib distribution includes source code. First extract files from the jar file.
    mkdir JSynthLib
    cd JSynthLib
    jar xf ../JSynthLib-XXX.jar
You can compile them by the following steps.
    # On Windows use semicolon (;) instead of colon (:).
    javac -classpath '.:groovy.jar' org/jsynthlib/*/*/*.java org/jsynthlib/*/*.java
    javac core/*.java
    javac synthdrivers/*/*/*.java synthdrivers/*/*.java
    javac *.java
You may also use the following method:
    find . -name '*.java' | grep -v midiprovider/ > js
    # On Windows use semicolon (;) instead of colon (:).
    javac -classpath '.:groovy.jar' @js
And Makefile and ANT build file are included. You may compile JSynthLib by simply typing;
    make all
or
    ant
If you are using Eclipse, enable the auto-compile feature. Eclipse compiles necessary files in background every time you save a file.

Next, open up a Command Prompt or shell set to 50 row display and set it to that directory. This is used to run JSynthLib for testing (the 50 row display helps keep error messages from scrolling off the screen too fast). Make sure that the java[.exe] and javac[.exe] executables are in your path for easy access.

Important: Once you've extracted the .jar file and made changes to the resultant files, you must launch JSynthLib using;

    # On Windows use semicolon (;) instead of colon (:).
    java -classpath '.:groovy.jar' JSynthLib -D 3
instead of java -jar JSynthLib.jar. Otherwise you just run the old .jar file and none of your alterations get used. The command line option '-D 3' gives you various debug messages from ErrorMsg class. The messages help you.

Although the source code for each version of JSynthLib is included in the JSynthLib distribution, you may wish to download the latest version of the source code to get any improvements made after the latest release. We sometimes have to change API to make JSynthLib better. Your driver developed for the released JSynthLib may be required some changes for the latest version. You can downloaded the latest source code via SVN from SourceForge SVN server.

    svn co https://jsynthlib.svn.sourceforge.net/svnroot/jsynthlib/trunk/JSynthLib JSynthLib
For more detail go to SVN page on JSynthLib SourceForge.net site.

III. Writing a Synth Driver

III.a. Introduction

Alright, so you've got JSynthLib and like what it does. The problem is that it doesn't support one of the synthesizers you own. Check the feature request (RFE) tracker page on JSynthLib SourceForge site and drop a line to JSynthLib mailing list to find out if anyone is working on adding support for that synth. You may find out that no one is. You decide to volunteer to add support. Triumphantly you let us know your intention by adding a message on the tracker and set down to hacking. This document is designed to help you to complete the task.

III.a.1. How hard is it to add support for a new synthesizer to JSynthLib?

The hardest part is simply becoming familiar with how JSynthLib works and how it's laid out internally. Spend some time looking through the driver code for other synthesizers and you'll basically pick it up by osmosis. Once your familiar with what you have to do, actually doing it shouldn't take too long. I've gotten librarian (not editing) support for synthesizers hacked up in under two hours. It depends of course, on the complexity of the synthesizer and the quality of the sysex specification. Adding editing support can be a little more time consuming, but is probably even more fun than writing librarian support. I've spent anywhere between 3 or 4 hours (working on the DR660 Editor) up to 5 days (working on the Matrix 1000 editor). If you run into any trouble, you can email JSynthLib mailing list for help.

III.a.2. What do I need in order to add support for a new synthesizer?

  1. At the very least, you need a sysex specification for your synthesizer. In most cases, the sysex specification is located in the back of the manual, but this is not always the case. Sometimes they are also available on the Internet if you look around enough.
  2. You probably also need the synthesizer you wish to add support for (for testing). While it might be possible to do it without the synthesizer, it would be pretty tough.
  3. You'll need a copy of the Java 1.4 (or higher) SDK. This is available for free from java.sun.com. This contains the various tools used to compile JSynthLib.
  4. You'll need a text editor in order to edit code. Anything will work. I wrote a significant part of the JSynthLib core code using EDIT.COM which comes with Windows. More recently, I've switched to jEdit, a Java based emacs-like programmer's editor with syntax highlighting and bracket matching.

    To be compliant with the JSynthLib core sources it is best if you use an editor which supports our code conventions (especially concerning the tab settings).

    Some of the most active developers of this project started using Eclipse (for setup have a look at the FAQ). It knows Java syntax well. I can search reference and/or definition of a member, compile files auto-magically, has integrated SVN support, etc.

  5. Finally, you'll need to be able to program in Java. If you've programmed in C++ before, you can probably pick it up in about an hour (I did). If you are coming from C++ the most important thing to know is that Java passes all objects by reference, not value.

III.a.3. Who owns the copyright to the code I contribute to JSynthLib?

You do. Unless you specifically assign your copyright to me, you retain ownership. Of course, you must release your code under the GNU General Public License since it is considered a work derived from JSynthLib, but in addition to releasing it under the GNU Public License, you can do whatever else you want with it. Put the following lines at the top of each file.
    /*
     * Copyright 20XX Your Name
     *
     * This file is part of JSynthLib.
     *
     * JSynthLib is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published
     * by the Free Software Foundation; either version 2 of the License,
     * or(at your option) any later version.
     *
     * JSynthLib is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with JSynthLib; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
     * USA
     */
Don't forget editing the first line.

III.b. The Layout of JSynthLib

Note: We are changing the package structure of JSynthLib. This section will be updated when the transition will be done. If you look at the JSynthLib directory, you'll see a number of files and directories. Since you are only adding support for a synthesizer, and not working on the core program, you don't have to worry about most of these. For you, the most important areas are the synthdrivers directory where code pertaining to various synthesizers are kept. Under synthdrivers directory, you see many directories, like KawaiK4. As you see the names of directories has a convention, manufacturer's name followed by model name. (Usually one directory has files for the Synth Driver for one synth model. But you may have synthdrivers for multiple synth models in one directory.) In addition to the synthdrivers subdirectory, you will now see core directory. Inside the core directory is all the code for the main part of JSynthLib. Feel free to look around. (Probably you have to read source files which your code extends.)

When you compile parts of JSynthLib, always do so from the main JSynthLib directory. For example, to compile the file synthdrivers\BossDR660Driver\BossDR660DrumkitDriver.java sit in the main JSynthLib directory and type

    javac synthdrivers\BossDR660Driver\BossDR660DrumkitDriver.java
. Don't enter that directory and just type;
    javac BossDR660DrumkitDriver.java # does not work

III.c. JSynthLib API Document

You can make API document by executing;
    javadoc -locale en_US -breakiterator -quiet -d doc/api_docs -use -author -version -overview doc/programming.html core/*.java
View doc/api_docs/index.html with your favorite browser. This document is on the top page with hyperlinks to class, interface, and member definitions.

III.d. Synth Driver Structure

This section provides you a big picture of your Synth Driver. First let's describe some concepts.
Device
A class extends {@link core.Device} class. It defines some informations for your synthesizer, for example synth model name, MIDI port assigned, etc. And it has a list of drivers (Single Driver, Bank Driver, and/or Converter) described below.
Single Patch
Single Patch is a set of MIDI system exclusive messages of a sound data, etc.
Single Driver
Single Driver provides routines to support a Single Patch.

A Single Driver extends {@link core.IPatchDriver} class and allows JSynthLib to detect a patch data for the synthesizer, to communicate with the synthesizer. Once this is written, JSynthLib will have Librarian support for your synthesizer.

A Single Driver optionally can have a Single Editor invoked by IPatchDriver.editPatch() method.

Bank Patch
Bank Patch (bulk dump patch) is a bank of single patches.

Bank Driver
Bank Driver provides routines to support a Bank Patch.

A Bank Driver extends {@link core.IPatchDriver} and allows JSynthLib to combine a single patch into a bank patch and to extract a single patch from a bank patch. While this functionality isn't strictly necessary, it is nice to have if your synth supports bulk dump patch.

A Bank Driver can have a Bank Patch Editor. But you don't have to take care of it because usually the default editor can be used.

Converter
A driver implements {@link core.IConverter} interface. This is a special driver. Converter simply converts a patch, which is imported from a file or MIDI input, into it's associated with to another format. Most of synthdrivers don't use this.
Single Editor
This is the fun part. This is a graphical representation of the synthesizers internals which allows parameters to be changed and edited. It is invoked by a ISinglePatch.edit method.
A Synth Driver communicates with JSynthLib core by using the following interfaces.
    {@link core.Device} class (required)

    {@link core.IDriver} interface
      {@link core.IPatchDriver} interface extends {@link core.IDriver} (required)
      {@link core.IConverter} interface (optional)

    {@link core.IPatch} interface
      {@link core.ISinglePatch} interface extends {@link core.IPatch} (required)
      {@link core.IBankPatch} interface extends {@link core.IPatch} (optional)
All what you have to do is to implement java classes which implements one of these interfaces. Of course you don't have to write whole code from scratch. By extending existing classes, you can implement your driver with reasonable effort. Most of the current synthdrivers extend the following classes;
    {@link core.Device} class

    {@link core.Driver} class implements {@link core.IPatchDriver}
    {@link core.BankDriver} class implements {@link core.IPatchDriver} (optional)
    {@link core.Converter} class implements {@link core.IConverter} (optional)

    {@link core.Patch} class implements {@link core.ISinglePatch} and {@link core.IBankPatch} class
Actually the interfaces described above was just introduced in the JSynthLib-0.20 release.

III.e. Writing a Device class

First you need to write a device file. This is very easy.

III.e.1. Create a File

Under synthdrivers directory, create a directory for your synth. Just copy an existing one, for example KawaiK4Device.java and change it to suit your needs.. The file name of the Device class must be *Device.java. This is the only rule for file names for a Synth Driver.

III.e.2. Code Your Device Class

All what you have to do is to define two constructors. Here is from KawaiK4Device.java.
    /** Constructor for DeviceListWriter. */
    public KawaiK4Device() {
        super("Kawai", "K4/K4R", "F07E..0602400000040000000000f7",
              INFO_TEXT, "Brian Klock & Gerrit Gehnen");
    }

    /** Constructor for for actual work. */
    public KawaiK4Device(Preferences prefs) {
        this();
        this.prefs = prefs;

        addDriver(new KawaiK4BulkConverter());
        addDriver(new KawaiK4SingleDriver());
        addDriver(new KawaiK4BankDriver());
        // ... other drivers
    }
The first constructor just defines various informations of your synth and authors of this Device class. This is used by the second constructor and DeviceListWriter described in the next section.

The second constructor is used for the actual work. It creates drivers and add them onto the driver list by using addDriver method.

III.e.3. Keeping Persistent Parameters

The second constructor has an argument, java.util.prefs.Preferences object prefs. By using this and overriding config() method, your driver can have persistent parameters for your synth driver. See actual code, for example RolandTD6Device, for more details.

III.e.4. Tell the core program about your Synth Driver

You have to let JSynthLib know about your driver. Create your Device class without addDriver() method call. Compile it, then execute the following command on your command shell,
    java core/DeviceListWriter
This recreates the synthdrivers.properties file.

Invoke JSynthLib, go to Window->Preferences...->Synth Driver, and click Add Device button. If all goes well, you see your Device name is on the list.

Now JSynthLib knows about your synth driver. Now the time to write drivers :)

III.f. Writing a Single Driver

This section describe how to write a Single Driver by extending {@link core.Driver} class which is for {@link core.Patch} class. Keep in mind that what you are doing is to implement methods of {@link core.IPatchDriver} interface and methods which {@link core.Patch} class requires.

III.f.1. Create a File

Copy a Single Driver from one of the other synthesizers and change its name to match your synthesizers. Go through that file and change all the references to that synthesizer to yours. I recommend using the KawaiK4SingleDriver as a starting point because it is one of the most simple drivers.

III.f.2. Write the damn thing

Your Single Driver will be a subclass of the {@link core.Driver} class. Take a look at the API document and open up the file Driver.java. All of the variables and functions are documented as to their purpose. Also look at your Single Driver code (which you copied from another synthesizer) Between these two files you should be able to figure out what to do.

First, you change your constructor to provide the correct information to the driver. This information is used by the functions in Driver class to manipulate the data. If your synthesizer were very simple, that would be just about all there was to writing the driver. However, most synths have their unique quirks and features that are impossible to describe using just data. Therefore you will probably have to override some of the functions in Driver class to perform for your synth.

III.f.3. Implement calculateChecksum Method

You may have to override the {@link core.Driver#calculateChecksum(Patch, int, int, int)} and/or {@link core.Driver#calculateChecksum(Patch)} methods.

{@link core.Driver#calculateChecksum(Patch, int, int, int)} actually calculate checksum. The default method uses the most common algorithm used by YAMAHA, Roland, and etc. It adds all of the data bytes, takes the lowest 7 bits, XORs them with 127 and adds one to get the checksum. If your synth uses a different algorithm, override this method.

{@link core.Driver#calculateChecksum(Patch)} is called by some methods in {@link core.Driver} class. If your synth does not use checksum, override it by an empty method. If the patch for your synth consists of only one Sysex Message, you don't have to override the method. If your synth uses a patch which consists of multiple Sysex Messages, you need to override the method. See, for example, RolandTD6SingleDriver.java.

III.f.4. Implement storePatch and sendPatch Methods

Looking at your driver code (which you stole from the KawaiK4 code like I told you to). You'll see that we had to implement two methods, {@link core.Driver#storePatch(Patch, int, int)} and {@link core.Driver#sendPatch(Patch)}. storePatch sends a patch to a buffer in your synth specified by a user, and sendPatch sends a patch to the editing buffer in your synth. This is common, since usually, slightly different sysex messages are used to send a patch to the editing buffer (and not overwrite a patch) or to a specific patch (and overwrite). Change these functions to match your synth. If your synth has no editing buffer, you'll need to overwrite the send method to treat a specific patch location on the synth as the edit buffer it (see the EmuProteusMPS Driver for an example of this).

III.f.5. Implement createPatch and editPatch Methods

There are two functions that will always need to be overridden if you wish to provide that functionality because there is no default version in core\Driver.java. These are both easy to implement.

One of these is the {@link core.Driver#createNewPatch} method which returns a new (blank) patch. You may use {@link core.DriverUtil#createNewPatch} for this method. See RolandTD6SingleDriver as an example.

The other is the {@link core.Driver#editPatch} method which opens an Single Editor window for the patch. You should be able to figure out how to write these by looking at the code for the KawaiK4.

There may be other functions in Driver.java that you will need to override for your synth. In general, spend time looking at that file, the drivers for all of the other synths, and API document to get a feel for how things are done.

Once your driver is working, JSynthLib now has Librarian support for your synth. Celebrate. And send in the code to us so we can include it in the next release of JSynthLib.

III.g. Writing a Bank Driver

Basically, write a Bank Driver the same way you did the Single Driver. Copy the BankDriver from the KawaiK4 or one of the other synths and edit it to fit your needs. Change all the data in the constructor to fit you synth. The Bank Driver subclasses {@link core.BankDriver}. I recommend you look at that file and figure out which functions you need to override.

You may want to use your Single Driver in your Bank Driver. You can pass your Single Driver via the constructor of your Bank Driver. Here is a part of RolandTD6Device Constructor;

    public RolandTD6Device(Preferences prefs) {
        ...
        // add drivers
        TD6SingleDriver singleDriver = new TD6SingleDriver();
        addDriver(singleDriver);
        addDriver(new TD6BankDriver(singleDriver));
    }

III.h. Writing a Single Editor

All right, so you've written a Single Driver and maybe a Bank Driver and now have Librarian support for your synthesizer. JSynthLib can load, save and play patches. Pretty neat. But the real trick is to add editing support for your synth.

Writing an editor is a little bit harder than writing a driver. It is harder because you have to write a Java Swing interface. Swing is the default graphical toolkit that comes with Java. It is the graphical interface which all of JSynthLib uses. If you don't know Swing at all you might want to get a Java book, but you can get away with just the Tutorial on java.sun.com and the API guide that you can download when you download the JDK.

The job of your Single Editor class is to set up the interface for your editor by using a number of functions and widgets provided by JSynthLib and a number of Swing features. It is invoked by {@link core.IPatch#edit()} method. It returns a {@link core.JSLFrame} object. What you have to do is create a class extending {@link core.PatchEditorFrame} class which extends {@link core.JSLFrame} class. (You don't have to extend {@link core.PatchEditorFrame}, if you want to create a JSLFrame object from scratch.)

I like to set up all my sliders and CheckBoxes and figure out where they go and what there ranges are first, get a nice pretty GUI on screen and then go back and put in the numbers to make it actually work. Basically you are going to create a number of JPanes and place controls inside them using the {@link core.PatchEditorFrame#addWidget} method. Then you insert these JPanes onto scrollPane (the background of the window). scrollPane is created by JSynthLib for your convenience.

If you want Tabs and such, you can implement them the same way the other editors do using the Swing feature. For a simple synth, you can get away with just putting all the JPanes directly onto scrollPane.

You can do like with the Single Driver and start out by copying an existing editor over and renaming the file, classes, constructor, etc. I recommend the YamahaTX81z Editor since it is one of the simplest Single Editor.

Make sure you've taken a look at how all the other Single Editors are set up, the code contained within them explains all better than I could cope with mere words.

III.h.1. How to Add SysexWidgets:

JSynthLib comes with several SysexWidgets I've created which automatically deal with sending sysex and whatnot. A SysexWidget subclasses {@link core.SysexWidget} class. All you have to do is tell them where to go and what values to send out. You create and layout them by using {@link core.PatchEditorFrame#addWidget} method like this:
    // example from the TX81z Editor
    addWidget(cmnPane,  // pane to put the SysexWidget on
              new ScrollBarWidget("Feedback", patch, 0, 7, 0,
                                  new ParamModel(patch, 100),
                                  new VcedSender(53)),
              1, 1,     // horizontal/vertical location
              3, 1,     // horizontal/vertical size
              18);      // fader number
The first parameter is the JComponent (pane) you wish to put the SysexWidget on. You would have created this pane previously as a JPane. Usually I use panes to break the interface into functional sections (such as LFO parameters, filter parameters, etc.).

The second parameter is the SysexWidget to create, in this case a {@link core.ScrollBarWidget}. Notice that the widget itself takes a few parameters, we'll get to that in a moment.

The next four parameters represent the location and size of the SysexWidget within the Pane (Read up on gridbaglayout in Sun's Java tutorial). They are (in order) the horizontal location, the vertical location, the horizontal size, and the vertical size).

The last parameter is the fader number. Each SysexWidget needs to have a unique fader number and they should go in order. label all SysexWidgets with positive numbers except for CheckBoxes which get negative numbers (buttons). So the sliders etc. would go 1, 2, 3, 4, 5.. and the CheckBoxes would go -1, -2, -3, -4, -5... etc.

III.h.2. The Provided SysexWidgets:

This section describes some of SysexWidgets provided. See API documents for other SysexWidgets and more details.

{@link core.ScrollBarWidget} contain a label, a slider, and a numeric readout of the value of the slider. They are the most common SysexWidgets. The first parameter to the SysexWidget should be its label. the second one will always be patch. The next two values are the minimum and maximum values on the slider. The fifth value is a value offset for the slider. This is usually zero, but gets used if the parameter in the synthesizer is (for example) 0 through 127, but should be displayed as -63 to +63). The last two parameters are a Param Model and Sender for this fader (we'll get to those after we deal with some other SysexWidgets.).

{@link core.ScrollBarLookupWidget} are just like ScrollBarWidget but they let the numeric readout contain values other than numbers. They are used rarely. For example if a parameter can take the values of OFF, 1,2,3,4,5,6 or 7, you might use one. You wouldn't want to use a ScrollBarWidget because the first value should be OFF, not zero. You could also use a ComboBoxWidget in this situation. The constructor is about the same as for a combo box.

{@link core.ComboBoxWidget} are drop-down list of choices that are best used for non-numeric data (such as LFO shape). Here's an example of setting up a ComboBoxWidget:

    addWidget(panel,
              new ComboBoxWidget("EG Shift", patch,
                                 new ParamModel(patch, 20 + i*5),
                                 new AcedSender(i*5 + 4),
                                 new String[] {"96db", "48db", "24db", "12db"}),
              0, 4, 1, 1, 5);
Parameters to the constructor are 1) a Label, 2) patch, 3) a Param Model, 4) a Sender, and 5) an array of all the values that it can contain.

{@link core.CheckBoxWidget} are used for parameters that can be either on or off. Heres an example:

    addWidget(lfoPane,
              new CheckBoxWidget("1", patch,
                                 new ParamModel(patch, 55 + 3*13),
                                 new VcedSender(3*13 + 8)),
              3, 6, 1, 1, -19);
Parameters to the Constructor are 1) a Label, 2) patch, 3) a Param Model, and 4) a Sender.

{@link core.EnvelopeWidget}, the final SysexWidget type, are vastly more complex than the others.They represent several parameters on the synth, such as the attack, decay, sustain, and release of a VCA envelope.

Note that the fader number you give to an envelope widget represents is its first fader number, and it will take as many as it needs starting at that one to represent all of it's parameters.

The constructor for an envelope widget takes a list of {@link core.EnvelopeWidget.Node}s. Each Node is one of the movable squares on the envelope. Some of these nodes are stationary, some contain two parameters on the synth and can be moved vertically and horizontally, and others contain only one parameter and can therefore be moved in only one direction. Here is an example:

    EnvelopeWidget.Node[] nodes = new EnvelopeWidget.Node[] {
        // origin
	new EnvelopeWidget.Node(0, 0, null, 0, 0, null, 0, false, null, null, null, null),
        // delay time
	new EnvelopeWidget.Node(0, 100, new K4Model(patch, 30 + i),
	        		0, 0, null,
	        		0, false, new K4Sender(34, i), null, "Dly", null),
        // atack time
	new EnvelopeWidget.Node(0, 100, new K4Model(patch, 62 + i),
	        		100, 100, null,
	        		25, false, new K4Sender(45, i), null, "A", null),
        // decay time and sustain level
	new EnvelopeWidget.Node(0, 100, new K4Model(patch, 66 + i),
				0, 100, new K4Model(patch, 70 + i),
				25, false, new K4Sender(46, i), new K4Sender(47, i), "D", "S"),
        // null node for constant length horizontal line
	new EnvelopeWidget.Node(100, 100, null,
	        		EnvelopeWidget.Node.SAME, 0, null,
				0, false, null, null, null, null),
        // release time
	new EnvelopeWidget.Node(0, 100, new K4Model(patch, 74 + i),
				0, 0, null,
				0, false, new K4Sender(48, i), null, "R", null),
    };
    addWidget(panel,
              new EnvelopeWidget("DCA Envelope", patch, nodes),
              0, 0, 3, 5, 33);
As you see, the Envelope Widget takes a label, followed by patch and then an array of EnvelopeWidget.Node objects. The parameters given to the EnvelopeWidget.Node constructor have the following meaning:
    public Node(int minx, int maxx, ParamModel pmodelx,
                int miny, int maxy, ParamModel pmodely,
                int basey,
                boolean invertx,
                SysexSender senderx, SysexSender sendery,
                String namex, String namey)
minx, maxx, miny, maxy
The minimum/maximum value permitted by the synth parameter which rides the X/Y axis of the node.
pmodelx, pmodely
The Param Model which provides reading/writing abilities to the sysex data representing the parameter.
basey
Sometimes you don't want zero on a Y-axis-riding-parameter to be all the way down at the bottom. This gives it a little bit of rise. basey will be added to all Y values. (This doesn't change the function of the EnvelopeWidget, but makes it look nicer and possibly be more intuitive to use.)
invertx
Sometimes on an X-axis-riding attribute 0 is the fastest, other times it is the slowest. This allows you to choose.
senderx, sendery
The Senders which send sysex messages to the synths when the Node is moved.
namex, namey
The names of the parameters riding each access.
**Using nulls for the Param Models and Senders and setting min to max means that a node is stationary on that axis and has no related parameter. **Using EnvelopeWidget.Node.SAME for miny means that the height remains at whatever the previous node was at.

I hope that made sense, if not, just take a look at the way EnvelopeWidget are used by various single editors.

{@link core.CheckBoxWidget}, {@link core.KnobWidget}, {@link core.KnobLookupWidget}, {@link core.LabelWidget}, {@link core.PatchNameWidget}, {@link core.ScrollBarLookupWidget}, {@link core.VertScrollBarWidget}, {@link core.SpinnerWidget}, and {@link core.TreeWidget} are also provided.

III.h.3. Param Model and Sender

Param Model (Parameter Model) and Sender are objects which communicate data between the synth and the SysexWidgets. For example every time a SysexWidget moves, its Param Model ({@link core.SysexWidget.IParamModel#set}) gets told and then Sender ({@link core.SysexWidget.ISender#send}) does.

We want to keep track of the changes to the patch so that when we next call up this patch the changes are there. We also want to be able to set the SysexWidgets to the correct values for a particular patch when the Single Editor is opened. This is what the Param Model (Parameter Model) is for.

Param Model is a class object which implements {@link core.SysexWidget.IParamModel} interface. The interface provides two method, {@link core.SysexWidget.IParamModel#set} and {@link core.SysexWidget.IParamModel#get}. They just read (get) and write (set) a parameter value.

The default Param Model (constructor ParamModel(Patch patch, int offset)) is used by the YamahaTX81z editor. Its constructor takes two parameters, the patch being edited and the offset into the patch of the value.

Sometimes the default Param Model can be used, other times it is either necessary or more convenient to subclass ParamModel to make your own Model. Here is a simple example from the KawaiK5000 Editor:

    class K5kSrcModel extends ParamModel {
        public K5kSrcModel(Patch patch, int src, int offset) {
            super(patch, 91 - 1 + offset + 86 * (src - 1));
        }
    }
This overrides only constructor to make the calculation of offset easier.

Here is a more complex example from the KawaiK4 Editor:

    class K4Model extends ParamModel {
        private int bitmask;
        private int mult;
        ...
        public K4Model(Patch p, int offset, int bitmask) {
            super(p, offset + 8);
            this.bitmask = bitmask;
            if ((bitmask & 1) == 1)
                mult = 1;
            else if ((bitmask & 2) == 2)
                mult = 2;
            else if ((bitmask & 4) == 4)
                mult = 4;
            else if ((bitmask & 8) == 8)
                mult = 8;
            else if ((bitmask & 16) == 16)
                mult = 16;
            else if ((bitmask & 32) == 32)
                mult = 32;
            else if ((bitmask & 64) == 64)
                mult = 64;
            else if ((bitmask & 128) == 128)
                mult = 128;
        }
    
        public void set(int i) {
            patch.sysex[ofs] = (byte) ((i * mult) + (patch.sysex[ofs] & (~bitmask)));
        }
    
        public int get() {
            return ((patch.sysex[ofs] & bitmask) / mult);
        }
    }

In the case of the KawaiK4, we couldn't just use the default ParamModel class, because some Kawai K4 parameters are bitmasks stored in the same byte as other parameters. The above class K4Model takes care of deciphering the bitmasks. MIDIboxFMModel.java is another example which uses bitmasks.

You can also implement SysexWidget.IParamModel directly if you don't need patch or ofs field. See AlesisDMProModel as an exmaple.

A Sender sends a Sysex message generated by Param Model to the synth informing it of the change. It is a class object which implements {@link core.SysexWidget.ISender} interface. The interface has only one method, {@link core.SysexWidget.ISender#send}.

Here's an example of a Sender from the Kawai K4 Editor. It extends {@link core.SysexSender} class which implements {@link core.SysexWidget.ISender}:

    class K4Sender extends SysexSender {
        private int source;
    
        private byte[] b = {
                (byte) 0xF0, 0x40, 0, 0x10, 0x00,
                0x04, 0, 0, 0, (byte) 0xF7
        };
    
        public K4Sender(int parameter, int source) {
            this.source = source;
            b[6] = (byte) parameter;
        }
    
        public K4Sender(int parameter) {
            this.source = 0;
            b[6] = (byte) parameter;
        }
    
        public byte[] generate(int value) {
            b[2] = (byte) (channel - 1);
            b[7] = (byte) ((value / 128) + (source * 2));
            b[8] = (byte) (value & 127);
            return b;
        }
    }
This sender has two constructors. One has a parameter source and the other does not.

Here is another example of RolandJV80SystemSetupEditor which uses {@link core.SysexSender#SysexSender(String)} constructor.

    static class JVSender extends SysexSender {
        int offset;

        // retrieve default from patch
        public JVSender(int offset) {
            super("F041@@461200000000**00F7");
            this.offset = offset;
        }

        protected byte[] generate(int value) {
            byte[] data = super.generate(value);
            data[JV80Constants.ADDR4_IDX] = (byte) offset;
            JV80Constants.calculateChecksum(data, 1);
            return data;
        }
    }

The constructor for a SysexSender takes a String parameter representing which parameter a particular instance of the Sender should control and creates a sysex message containing all necessary information except for the data value to be transmitted and the channel (Device ID) to transmit on. When the SysexWidget attached to this instance of the SysexSender moves, the generate method of the SysexSender gets called with the value to send. The generate function simply puts the value and channel into the message and returns it.

RolandTD6.RolandDT1Sender is an example which implements {@link core.SysexWidget.ISender} directly without extending {@link core.SysexSender}. It is little complexed to support generic Roland DT1 (Data Transfer) message.

Often a Single Editor will have one or more Param Model and/or Sender. Sometimes more than one is used because a synth may use more than one method to transfer the data.

Note how the Param Models and Senders are used in the various editors. A SysexWidget doesn't care if you use the default Param Model or if you implement your own. This makes the SysexWidgets much more extensible. They can be used without needing to know how exactly the data is supposed to be accessed. The Param Model and Sender takes care of that.

This all probably sounds more complex than it really is, just take a look at the editors for various other synths, try changing some things maybe to see how they work. It shouldn't be too hard to figure out, but I'm not too good at explaining.

III.h.4. Action Listener - Image Handling

Swing provides a construct known as an action listener which allows you to write a routine which is called whenever a SysexWidget is moved. You never need to use these for dealing with built in JSynthLib SysexWidgets because they take care of it themselves. It is useful however if you want to have a SysexWidget do something more than control the synth. The Yamaha TX81x editor, for example, uses one to change the picture of the algorithm every time the 'algorithm' slider is moved.

Note that the following code seems to work.

    // Bad Example : does not work with jar
    algoIcon[0] = new ImageIcon("synthdrivers/YamahaTX81z/1.gif");
But the code above does not work when the files are in a jar file. Instead use the following code.
 
    // Good Example
    algoIcon[0] = new ImageIcon(getClass().getResource("1.gif"));

III.i. Writing a Converter

!!!FIXME!!! Describe the situation where Converter is used.

IV. Testing Your Driver...

This section of the Documentation was written by Yves Lefebvre.

IV.a. Introduction

This is an attempt to do a test plan when writing a new driver for JSynthLib. It take little time to do and may help finding problem before the driver is available publicly.

First thing to do: you must do some single dump and some bank dump from your synth to your computer without using JSynthLib (I use Cakewalk to do that but there should be some simple freeware to do this). Save those dump in .syx format (binary). Remember, we are not using JSynthLib at his point in order to have something "clean" to refer to when testing the new driver.

One important note : It seems that some synth could have "bugs" in their factory patches. So if you resend those patch to the synth, they will be "corrected". To test this, you can simply resend the bank dump from your PC to the synth and do a new bank dump from synth to PC. If there are difference and the first bank dump and the second, this is probably the problem I mention. Normally, resending the second dump should be consistent after that. Any dump from synth to PC should be identical to the second dump since the synth has now "corrected" the original dump! I had this problem with some specific patch of my Nova after restoring original patches on the unit with the "Restore from ROM" command. I had spent some time figuring out the difference between dump so I'm warning you not to do the same mistake!

Now, redo the same thing (single dump and bank dump) but this time, change your device id number on the synth (sometime call global MIDI channel). Those new dump may have some byte different or not. In most case, single dump will be identical while bank dump may be different.

IV.b. Testing your Single Driver

IV.c. Testing your Bank Driver

IV.d. Testing your Editor

Note: I never write an editor but here are some suggestions:

Note: I suggest to move each fader at random because putting all of them to 0 or max is not a good idea since your editor may send knob info to the wrong place and you will not be able to detect it by comparing the .syx file!

Last step, send your new driver for integration in the next release of JSynthLib!

Yves Lefebvre
ivanohe@abacom.com
www.abacom.com/~ivanohe

V. FAQ

How can I send a bug report?

Send the following information to Tracker on JSynthLib SourceForge site or jsynthlib-devel mailing list.
  1. Output message in debug mode. Invoke JSynthLib with option '2'. This will output debug information including Java version, OS type, etc.
  2. A specific way to reproduce the bug.
  3. Any other useful 'fact', other than 'guess'.

Is there any tips for debug?

Of course, nothing ever works the first time. Its never as easy as it should be. If you have problems getting the synth to do what your editor or driver is telling it to do. here's two debugging tricks I use.
  1. This is pretty common, but just sprinkle System.out.println statements through out the troublesome code. Get a good idea of what values are what when and look for something that shouldn't be. Better yet, use ErrorMsg.reportError("ErrorMsg goes here") instead.
  2. Use a MIDI cable to connect your computer's MIDI out to MIDI in. Use build in MIDI Monitor Window or get a program (for example MIDI-OX for Windows) from the Internet to print out all incoming MIDI messages. Now you can see what messages your editor/driver is sending and you can check them for correctness.

Why shall I use an indentation level of 4 with a displayed tab width of 8?

For indentation level, any level is OK. But we need a standard to work in a team. We choose the coding style used in "Programming Language Java" out of respect for the creators of Java language as the standard. Therefore we recommend the indentation level of 4.

The reason for a displayed tab width of 8 is compatibility with the following essential tools:

Most tools support the settings above. An open source IDE that supports these settings is e.g. Eclipse. Text editors that also support it are e.g. jEdit and SciTE which are both open source and availabe for Linux, Mac OS X and Windows like Eclipse.

How to set up Eclipse for JSynthLib?

  1. Install Subclipse

    You need to install Subclipse, the SVN plugin for Eclipse. Follow the installation instructions at http://subclipse.tigris.org/install.html. The instructions are for Eclipse 3.0. If you have a newer version the process is slightly different:

    1. From the Help menu choose Software Updates.
    2. Selected the Available Software tab and click Add Site...
    3. Type the update URL from Step 4 of the Subclipse installation instructions and click OK.
    4. Expand the subclipse url and check Subclipse. You may also want to check some of the optional components, I installed them all.
    5. Click the Install button.
    6. Restart eclipse after installing the plugin.
  2. Create a Project
    1. From the File menu choose New then Project.
    2. Under SVN choose Checkout projects from SVN, then click Next.
    3. Select Create a new repository location and click Next
    4. Enter the url https://jsynthlib.svn.sourceforge.net/svnroot/jsynthlib/trunk and click next;
    5. You may get a warning because Eclipse doesn't understand SourceForge's wildcard http certificate. Click Accept Permanently.
    6. Select the JSynthLib folder and click Finish.
  3. Set up the Library Setting and Exclude MidiProvider

    (Note: These steps don't seem to be required using Eclipse 3.3)

    1. Right click (Ctrl-Click on a Mac) on the new project and choose Properties.
    2. Choose Java Build Path, then the Libraries tab.
    3. Click Add JARs....
    4. Click the arrow next to JSynthLib and select groovy.jar, then click OK.
    5. Click on the Source tab.
    6. Click the arrow next to JSynthLib, then Excluded: (None), then Edit....
    7. At the bottom of the window, next to the box that says Exclusion patterns, click Add Multiple....
    8. Choose midiprovider, then click OK three times to close all the Properties windows.
  4. Preferences->Java->Editor->Displayed tab width set to '8'. (Why?)
  5. Setting up Javadocs (Optional, but recommended)
    1. For using the Javadocs for the JDK (e.g. the "J2SE 5.0 Documentation") go to Preferences->Java->Installed JREs->Edit->Javadoc URL set to the directory where you installed the Javadocs. A short description is shown when moving the mouse cursor on a class or a member. You can see the full documentation by hitting Shift-F2 after selecting a class or member.
    2. Don't forget to generate the Javadocs for JSynthlib with Project->Generate Javadoc....
Congratulations. Now you're ready to start hacking.

VI. Related Documentation

VI.a. JSynthLib

VI.b. Java Language

For overviews, tutorials, examples, guides, and tool documentation, please see:

Java Home Page

Some selected documents:

Java Sound Resources is a good resource of Java Sound.

VI.c. MIDI Specification

The Complete MIDI 1.0 Detailed Specification isn't downloadable from the MMA Web site.
But you'll find a MIDI specification for example at

VI.d. Applications