jPatch - Codecs
Introduction
The jPatch API not only has to deal with the in-memory representation of the patch object model but also has to handle different representations of a patch and the necessary transformations.
Requirements
preliminary note: the transformations can be subdivided into two tasks corresponding the tasks of a decoder and an encoder.
- transformation from an unknown source to the in-memory patch model
- transformation from the in-memory patch model to a unknown target
usage requirements:
- an application should be able to handle different source/target representations without knowledge of implementation details.
-
a common case is that the application has to deal with different
file formats (or different midi message formats) in both
directions (import/export).
Thus it should be possible to add new encoder and decoder implementations to an application without the need of recompiling the application. This also requires the managment of installed encoders and decoders. - extension management: encoders/decoders have to provide information that allowes an application to decide which one can/should be used with a specific synthesizer model/version and patch format/version.
technical requirements:
- some data sources (especially when the data source are midi messages) only provide portions of the required information. Thus it should be possible to handle the case that not all data is available at once.
Solution
The solution we provide for the previously described requirements uses the mentioned
- decoder - for the transformation from a specified source to the in-memory patch model
- encoder - for the transformation from the in-memory patch model to a specified target
Note: it is not necessary to provide a complete codec (encoder/decoder pair).
For representing the source and the target we provide the Source and the Target interface.
known implementations
interface Source | interface Target | comment |
---|---|---|
FileSource | FileTarget | for reading/writing files |
BitStreamSource | BitStreamTarget | for reading/writing bit streams (Nord Modular implementation) |
The decoder/encoder interfaces handle the specified Source/Target implementations:
public interface PatchDecoder { void decode(Source source) throws UnsupportedSourceException // specified source not supported PatchDecoderException // an exception while decoding happened ; Patch getPatch() throws // returns the resulting patch PatchDecoderException // probably the result is not available ; ... } public interface PatchEncoder { void encode(Target target) throws UnsupportedTargetException // specified target not supported PatchEncoderException // an exception while encoding happened ; ... }
Encoder
The encoder creates a new representation of a patch for a specified target. The target is represented by the interface net.sf.nmedit.jpatch.io.Target.
developing a custom encoder
A custom encoder requires the implementation of
- net.sf.nmedit.jpatch.spi.PatchEncoderProvider
- net.sf.nmedit.jpatch.io.PatchEncoder
and a text file named 'net.sf.nmedit.jpatch.spi.PatchEncoderProvider' in the 'META-INF/services' directory containing the class names of the custom PatchEncoderProvider implementations.
See also:
- JAR File Specification (Keywords: The META-INF directory, Service Provider)
The jar package file hierarchy:
src/ META-INF/services/ net.sf.nmedit.jpatch.spi.PatchEncoderProvider packagename/encoder/ MyPatchEncoder.java spi/ MyPatchEncoderProvider.java
text file 'net.sf.nmedit.jpatch.spi.PatchEncoderProvider':
# my patch encoder provider test.package.encoder.MyPatchEncoderProvider
MyPatchEncoder.java:
package packagename.encoder; import net.sf.nmedit.jpatch.io.PatchEncoder; public class MyPatchEncoder implements PatchEncoder { // see javadoc for implementation details }
MyPatchEncoderProvider.java:
package packagename.encoder.spi; import net.sf.nmedit.jpatch.spi.PatchEncoderProvider; public class PatchEncoderProvider extends PatchEncoderProvider { // see javadoc for implementation details }
Example: file reading
import java.io.FileReader; import java.io.Reader; import net.sf.nmedit.jpatch.io.Source; import net.sf.nmedit.jpatch.io.FileSource; import net.sf.nmedit.jpatch.io.PatchEncoder; import net.sf.nmedit.jpatch.spi.PatchImplementation; import net.sf.nmedit.jpatch.Patch; import net.sf.nmedit.jpatch.io.Target; public Patch load(String file) throws PatchDecoderException, // error while encoding UnsupportedSourceException, // source not supported IOException { String patchFormat = "Clavia Nord Modular Patch"; String patchVersion = "3.03"; PatchImplementation pImpl = PatchImplementation.getImplementation(patchFormat, patchVersion); // get encoder PatchDecoder decoder = pImpl.createPatchDecoder(FileSource.class); // create file reader FileReader fr = new FileReader(file); // create file source Source source = new FileSource(fr); // read patch file and create patch decoder.decode( source ); // return patch return decoder.getPatch(); }
Decoder
The decoder creates a new patch from a specified source. The source is represented by the interface net.sf.nmedit.jpatch.io.Source.
developing a custom decoder
Similar to the 'developing a custom encoder' description - just substitute encoder with decoder.
Example: file writing
import java.io.FileWriter; import java.io.Writer; import net.sf.nmedit.jpatch.io.Target; import net.sf.nmedit.jpatch.io.FileTarget; import net.sf.nmedit.jpatch.io.PatchEncoder; import net.sf.nmedit.jpatch.spi.PatchImplementation; import net.sf.nmedit.jpatch.Patch; import net.sf.nmedit.jpatch.io.Target; public void save(String file, Patch patch) throws PatchEncoderException, // error while encoding UnsupportedTargetException, // target not supported IOException { // create encoder for FileTarget PatchEncoder encoder = patch .getPatchImplementation() .createPatchEncoder(FileTarget.class); // set source patch encoder.setSource(patch); // create writer Writer writer = new FileWriter(file); // create target Target target = new FileTarget(writer); // write file enc.encode(target); }