Menu

Programmer's Guide

Anonymous

Programmer's Guide

Last modified: 2014-10-20

Disclaimer: This document is work in progress and will be updated along with code changes being merged to trunk.

Go to JSynthLib User's Guide

Table of Contents

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.

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.

To ensure a coherent code style we use the Checkstyle tool. Its configuration file can be found in the codeanalysis/checkstyle-template.xml file directly under the root folder. Checkstyle can be run by using the following maven command: “mvn clean site -DskipTests”.

Considering the large amount of legacy code in this project we strive towards 0 checkstyle warnings. In order to reach our goal each delivery to trunk should contain equal or less checkstyle warnings as trunk has at that given time.

Using Javadoc

Add javadoc on important members (methods and fields). public and protected member must be documented in the core part of the jSynthLib code. For device drivers it is more important to comment what the code is supposed to achieve.

Proper Use of Access Control Modifier (for core developers)

This is especially important for code in the core parts of JSL. 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.

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.)

Logging

JSynthLib uses the log4j logging framework. To enable debug logs simply configure the log4j.properties file.

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.

Setup IDE

You can download 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.
JSynthLib is structured as a Maven project. You can thus import the project into any IDE that can handle such projects.

These are the requirements to develop jSynthLib:

  • Java JDK 8
  • Maven
  • Scene Builder 2.0

Setup:

  • Install Java JDK 8
  • From a command prompt run
java -version

and make sure the JDK 8 version is returned
Install Maven
From a command prompt run

mvn --version

and make sure the JDK 8 version is returned as java version
Install Scene Builder 2.0
You can optionally copy the jsynthlib-<version>-executable.jar file to %AppData%\Roaming\Scene Builder. Like that Scene Builder will automatically pick up the custom controls used by jSynthLib

Optional setup if you want to use Eclipse as IDE:

  • Install the m2e plugin
  • Install the e(fx)clipse plug-in as it provides a menu option to open FXML files in Scene Builder plus it eliminates some JavaFX compiler warnings that only Eclipse warns about
  • Install an svn plugin
  • Make sure Eclipse uses the jdk8 jre - Windows->Preferences->Java->Installed JREs
  • Import jSynthLib project to eclipse from sourceforge with svn
  • Run “mvn clean package -DskipTests” to generate necessary files and directories
  • Make sure target/generated-classes/xmlbeans is on your classpath (automatically done if you're using Eclipse)
  • Make sure target/generated-classes/test-xmlbeans is on your classpath (automatically done if you're using Eclipse)
  • Verify that jfxrt.jar is among the JRE System Libraries in Eclipse

Opening FXML files in Scene Builder

  • Launch Scene Builder 2.0
  • Open the menu displayed in the Stackoverflow answer: http://stackoverflow.com/a/20724166 and expand Custom Library Folder -> Reveal in Finder / Explorer.
  • Copy the jsynthlib-<version>-executable.jar jar file you generated previously with maven to this folder
  • Open your FXML file
  • To view the knobs you need to import the CSS file: Preview -> Scene Style Sheets -> Add a Style Sheet -> select the application.css file that resides in the src/main/java folder

Writing a Synth Driver

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.

Why XML drivers?

Here is a summary of what aimed to accomplish by straightening up the framework for devices and drivers. It will benefit the following stakeholders:

User

  • Get a more coherent feeling, functionality wise, between devices and drivers
  • The unique IDs for each parameter will in the future be great for providing easy to configure MIDI CC control to all parameters in the editors. I’m planning for a midi listener mode where you can select a control in an editor and then turn a knob on your HW MIDI controller and JSL will automatically pick up which MIDI number is sent and assign that automatically to the selected control. Like it works in Ableton Live.
  • Today it is quite unclear which drivers actually work and what is working for each driver. In a more controlled environment (that comes with a stronger framework) it will be easier to extract that information automatically and present it to the user in a centralized way
  • I think a good XML format could also potentially be used (possibly together with some XSLT) as documentation for the Sysex implementations of the devices we add to JSL. I have an old Korg DRV2000 which is impossible to find the manuals to on the Internet. Implementing a driver for that device would then be a reference of the sysex design

Developer

  • Today you have A LOT of freedom when implementing new devices. This is good in some cases but I would say that most of the time it’s better to have strict boundaries on how to implement a ”mainstream” device. This is what I am trying to accomplish with the XML format.
  • When you look at different drivers you can see that actually quite a lot of code has been copied over and over again to different drivers. One of my goals with the XML format is that drivers should reference implementations of certain interfaces e.g. ParamModel and ISender. I am also thinking of expand this to checksums and some other driver methods. By doing this several drivers can potentially use the same pieces of code to perform tasks that are today coded into each specific driver
  • I am also trying to build a stronger bond between the corresponding single and bank drivers. This is because naturally a lot of functionality is shared between them. This will as well decrease the amount of code the developer has to design and implement. Today there are multiple solutions implemented by different people. I will try to create a best practice that can be used in most cases.
  • If the developer still wants his/her freedom as before this is still possible. For a closer look at the class design please see the "Device Driver class overview" image below.
    As listed in that image you can still implement devices and drivers ”old style” but the methods that are discouraged to use will be marked as deprecated.

Maintainer

  • As stated above the JSL code is huge for what it does and there is a lot of code duplication. From a maintainer perspective this is a nightmare to handle. There are no unit tests and writing unit tests for duplicated code is quite discouraging. Therefore system tests have previously been written that verify each individual driver.

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.

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

  • 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.

  • 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.

  • You'll need a copy of the Java 8 (or higher) SDK. This is available for free from java.sun.com. This contains the various tools used to compile JSynthLib.

  • You'll need an IDE of your choice. Eclipse is the preferred IDE to use but if you prefer an other feel free to use it as long as you follow the coding guidelines.

  • 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.

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.

The Layout of JSynthLib

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 org.jsynthlib.synthdrivers directory where code pertaining to various synthesizers are kept. Under org.jsynthlib.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 org.jsynthlib.synthdrivers subdirectory, you will now see org.jsynthlib.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.)

Compiling jSynthLib is done by calling the following maven command:

mvn package -DskipTests

JSynthLib API Document

Javadocs can be found here: http://jsynthlib.sourceforge.net/apidocs/index.html.

If you are in need of a more up to date version of the docs you can generate them locally with this command:

mvn clean package -DskipTests

Once this command has terminated successfully you will be able to find the API docs here: target/apidocs/index.html

Synth Driver Structure

This section provides you a big picture of your Synth Driver. First
let's describe some concepts.

  • Device

    • A concrete device implementation extends {@link org.jsynthlib.device.model.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 implements {@link org.jsynthlib.device.model.IPatchDriver} interface 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 implements {@link org.jsynthlib.device.model.IBankDriver} 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 org.jsynthlib.device.model.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 Patch.edit method.

A Synth Driver communicates with JSynthLib core by using the following interfaces.

Device and driver class overview

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.

Writing a Device class

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

Create a File

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

Code Your Device Class

Take a look at the XML device/driver documentation for reference.

  <!-- Device specification document. If the xsd is correctly pointed out XML validation can be preformed in an editor e.g. Eclipse -->
  <xmlDeviceSpec xsi:schemaLocation="http://www.jsynthlib.org/xmldevice ../../../../../xsd/xmldevice.xsd">
    <manufacturer>Roland</manufacturer>
    <modelName>D-50</modelName>
    <inquiryId/>
    <infoText>Roland D50.
Single and bank drivers work together with editor.
Saving patches onto the D50 does not work though as this must be done by manually pressing buttons on the D50.</infoText>
    <authors>Pascal Collberg</authors>
        <!-- List of drivers and what type of patch they represent. -->
    <drivers>
        <xmlDriverDef>
            <driverClass>org.jsynthlib.synthdrivers.RolandD50.D50SingleDriver</driverClass>
            <driverType>Patch</driverType>
        </xmlDriverDef>
        <xmlDriverDef>
            <driverClass>org.jsynthlib.synthdrivers.RolandD50.D50BankDriver</driverClass>
            <driverType>Bank</driverType>
        </xmlDriverDef>
    </drivers>
</xmlDeviceSpec>

This XML snippet will provide all the necessary data about the device required by JSL. This data will be sent to the XMLDevice class which, in turn, extends the DeviceBase class.

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 :)

Keeping Persistent Parameters

Configuration parameters can be added in this document (see http://jsynthlib.sourceforge.net/docflex-xml/schemas/xmldevice_xsd/elements/preferenceDefs.html for details) which will generate a configuration pane in preferences dialog->Synth Driver->Right click on your device->Property...

Writing a Single Driver

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

Create a File

Copy a Single Driver XML from one of the other synthesizers e.g. Roland D50 and change its name to match your synthesizers. Note that you most likely will have to create both an XMl and a java file. They must have the same name and this is the name you should specify in the driver specification element of the device.
Go through that file and change all the references to that synthesizer to yours. I recommend using the D50SingleDriver as a starting point because it is one of the most simple drivers.
The XML file will contain static data about the driver and the patches it can handle. It can also contain the full list of parameters for patches managed by the driver. This parameter list is later used to generate a GUI stub for the patch editor.
The java file will contain a few methods for getting/setting patch data. These methods are descried in more detail below.

Patch Driver Editor

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.xml. 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.

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.

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.

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).

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.

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));
  }

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.

Once you have finished your driver XML file you can generate the FXML file using the scripts/generate_xml_files.bat script:

  • Launch the script
  • Answer 'n' to the first question
  • Enter the full java package name where you have saved the XML file for your new driver
  • As file name prefix you should give the same name as your XML file without the ending '.xml'. This way the script will name your FXML the same as your XML file which will make them easy to group.
  • The next step is when the script generates your FXML file
  • Finally it will ask you if it should move the generated FXMl file into your code structure or not

When the FXML file is generated and moved inside your code tree you can now edit it using Oracle's Scene Builder tool. See Opening FXML files in Scene Builder for how to configure the application to display all custom controls.

Note that all controls have unique IDs that also map them to their corresponding label (prefixed with lbl). This fx:id is used to map the GUI control to the parameters you specified in the XML file and must never be changed!

When you have opened the FXML in Scene Builder you can now move around all your GUI components and layout your editor in what ever way you want. You can move your controls into new containers, delete the generated containers and so on.

The only important thing you mustn't forget is to leave the fx:ids intact.

The Supported Sysex Controls:

This section describes the supported Sysex controls that can be used in your FXMl file.

  • Slider
  • ComboBox are drop-down list of choices that are best used for non-numeric data (such as LFO shape).
  • CheckBox are used for parameters that can be either on or off. Note that checkboxes don't get a corresponding label generated for them. Instead the checkbox has its own label.

  • Envelope 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.

As you see, the Envelope XML element takes a label, followed an array of envelopeNodeSpec elements. The parameters given to the envelopeNodeSpec have the following meaning:

  • 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.

  • Knob
  • TextField

Param Model and Sender

Param Model (Parameter Model) and Sender are objects which communicate data between the synth and the Sysex controls. For example every time a sysex control 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 sysex controls 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 org.jsynthlib.device.model.IParamModel} interface. The interface provides two method, {@link org.jsynthlib.device.model.IParamModel#set} and {@link org.jsynthlib.device.model.IParamModel#get}. They just read (get) and write (set) a parameter value.

All Param Model objects must comply with the Java Beans standard i.e. provide a default constructor and have public getters and setters for all of its fields.

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 EmuProteus2 Editor:

public class EmuParamModel extends ParamModel {

    public static final int ADDRESS_SIZE = 2;

    @Override
    public void set(int i) {
        if (i < 0) {
            patch.sysex[offset + 1] = (byte) 0x7F;
        }

        patch.sysex[offset] = (byte) (i % 128);
    }

    @Override
    public int get() {
        if ((patch.sysex[offset + 1] & 0xFF) == 0x7F) {
            return patch.sysex[offset] - 128;
        } else if ((patch.sysex[offset + 1] & 0xFF) != 0) {
            return patch.sysex[offset + 1] * 128 + patch.sysex[offset];
        } else {
            return patch.sysex[offset] & 0xFF;
        }
    }
}

This overrides the default get and set method to set the patch bytes correctly as this synth model uses two bytes for each parameter value.

You can also implement org.jsynthlib.device.model.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 org.jsynthlib.device.model.ISender} interface. The interface has only one method, {@link org.jsynthlib.device.model.ISender#send}.

All Sender objects must comply with the Java Beans standard i.e. provide a default constructor and have public getters and setters for all of its fields.

Here's an example of a Sender from the Roland D50 Editor. It extends {@link org.jsynthlib.device.model.AbstractSender} class which implements {@link org.jsynthlib.device.model.ISender}:

public class D50Sender extends AbstractSender {

    private static final int DEVICE_ID_OFFSET = 2;
    public static final int VALUE_OFFSET = 8;
    public static final int ADDRESS_OFFSET = 5;
    public static final int ADDRESS_SIZE = 3;

    // Base message containing one value
    public static final byte[] BASE_MESSAGE = new byte[] {
            (byte) 0xF0, 0x41, 0x00, 0x14, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
            (byte) 0xF7 };
    private static final int CS_START = D50Constants.SYSEX_HEADER_SIZE - 3;
    private static final int CS_END = BASE_MESSAGE.length
            - D50Constants.SYSEX_FOOTER_SIZE;
    private static final int CS_OFS = BASE_MESSAGE.length - 2;
    private final byte[] message;

    private final transient Logger log = Logger.getLogger(getClass());

    public D50Sender() {
        message = new byte[BASE_MESSAGE.length];
        System.arraycopy(BASE_MESSAGE, 0, message, 0, BASE_MESSAGE.length);
    }

    @Override
    public void setOffset(int offset) {
        super.setOffset(offset);
        int value = offset;
        for (int index = 2; index >= 0; index--) {
            message[ADDRESS_OFFSET + index] = (byte) (value & 0x7f);
            value >>= 7;
        }
    }

    void setMessage(int value) {
        message[VALUE_OFFSET] = (byte) value;
    }

    @Override
    public void send(IDriver driver, int value) {
        byte channel = (byte) driver.getDevice().getDeviceID();
        message[DEVICE_ID_OFFSET] = (byte) (channel - 1);
        SysexMessage m = new SysexMessage();
        try {
            setMessage(value);
            driver.calculateChecksum(message, CS_START, CS_END, CS_OFS);
            m.setMessage(message, message.length);
            driver.send(m);
        } catch (InvalidMidiDataException e) {
            log.warn(e.getMessage(), e);
        }
    }

    byte[] getMessage() {
        return message;
    }

}

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

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.

Writing a Converter

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

Testing Your Driver...

This section of the Documentation was written by Yves Lefebvre.

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" th 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.

Testing your Single Driver

  • Try to open a "clean" .syx file from JSynthLib and send it to the edit buffer of your synth. Try it to see if the name and sound seems OK. Do a dump (with external soft) and compare the file, they should be identical. Make sure that the patch is really in the edit buffer (and not written in memory) by changing patch on the synth and going back to the patch number it was before the send: the patch you just send should not be there (This may vary with some synth Model).

  • Try to do a single patch dump to JSynthLib. Check that the patch name is correct in JSynthLib. Export it to a .syx file and compare it (diff) with the original "clean" version. Should be the same. (Those steps will confirm that loading external file and receiving/sending a dump give the same result)

  • Try to store this patch at different location in the synth. At least, try to store in patch 0 or 1 and to the highest patch number of your synth. Do that in every user bank if possible. Verify that the patch appear in all the location you save it.

  • Create a new patch with JSynthLib and send it to your synth. Make sure the new patch didn't do anything wrong on the synth (if your new patch is all 0 with a name, some synth may react strangely to invalid combination of values). If you want to play safe, your createNewPatch method could construct a valid default patch for your synth: just do a dump of a simple patch and integrate this in the code.

  • Now, redo all those step with a different id number on the synth. Change JSynthLib accordingly to that new Id (channel number). Some time, it's possible you left a bug if you didn't test with a different ID (channel number).

Testing your Bank Driver

  • Open a "clean" .syx bank file with JSynthLib. Make sure that all individual patch name make sense.

  • Send one patch from the bank to your synth. Make a dump (from your synth) of that single patch with another software. Now, do a diff of this dump with a previously made dump of that same patch. This will make sure that extracting a patch from a bank and sending it to the synth gave the exact same thing. You should really do a binary compare of the file since just playing it is not enough. Even if some parameter are sent wrong, the patch may sound correct to your ear, so you need to do a real compare.

  • Erase all bank memory on your synth. Now, send the whole bank from JSynthLib to your synth. Do a dump from the synth to your external software. The .syx file should be the same than the original one. This is not the same thing than the previous step: extracting a single patch from a bank and sending a whole bank is very different. Note: one potential problem here is that some synth may need more time to "digest" a big bank dump from your PC. In those cases, you should put some delay in your code until you find a safe speed.

  • Try sending your bank to all possible banks in the synth and verify their integrity.

  • Now, you should redo some of those steps with a different channel number, just to make sure. Note that the bank .syx file will likely have the channel byte different so you need to extract a "clean" bank with the new channel first from your synth.

Testing your Editor

Note: I never write an editor but here are some suggestions:
* Usually, modifications done in the editor are sent in real-time to your synth. However, the editor must also make the same modification in the single patch that is edited. To test that it works correctly, you should made some modification in the editor and save the patch (do not send that patch to the edit buffer of your synth). Now, on your synth, the same patch should be in the edit buffer since all single edit will have send sysex for every parameter changed. Do a dump from your synth to PC and compare it to the patch JSynthLib just created. They should be identical. Normally, you should try each fader or knob in the editor to be sure they are controlling the correct parameter. Just move each one at random when creating the patch. If you do that some time (random edit, saving and comparing the file), chance are the editor behave correctly.

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

FAQ

How can I send a bug report?

Send the following information to Tracker on JSynthLib SourceForge site or jsynthlib-devel mailing list.

  • Edit the log4j.properties file to make it more verbose and invoke JSynthLib. This will output debug information including Java version, OS type, etc.
  • A specific way to reproduce the bug.
  • 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.

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
log4j loggers* instead.

*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:

  • ... viewcvs, the CVS browser that SourceForge uses.
  • ... Notepad, the default editor of Microsoft Windows.
  • ... Unix command line tools like cat, less, tail, ...

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.

Java Language

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

Java Sound Resources is a good resource of Java Sound.

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 <www.borg.com>.

Applications

  • Eclipse
  • Oracle's Scene Builder
  • MIDI-OX and MIDI Yoke for MS Windows
  • Midi Monitor for Mac OS X

Related

Wiki: User Guide