Java Synthesizer, Part 18, Script Interface


Hooboy, where to begin…

My plan for a user-definable synth circuit design was premised on the idea of using C-style pointers to functions. If you look at the last entry, in the section on addBuffer(), I’d turned the module methods into simple calls passing the output of one method to the input of the next. In C, or C++, I’d make an array of pointers to those methods, and it would be a relatively simple matter of changing pin wiring dynamically via the array assignments. So I went to one of the Java forums and asked what the Java equivalent approach is. I got a total of two responses, the first telling me that Java doesn’t have anything like pointers to functions and he couldn’t help me without more details regarding exactly what I was trying to do. So I typed up an example pseudo code that was deliberately inelegant to make my request clear. The second responder simply complained about the formatting of the pseudo code. So, as I’d ranted in an earlier entry, the “professionals” are never of any help when I go to the forums.

With no other choice, I approached the user-entered script-based synth circuit design as something to do with arrays (actually, ArrayLists) and I returned to my hardware roots. Like all of the modules, the interconnects have an electronics hardware origin, this time the concept of a patch board, telephone operator’s switchboard, or circuit backplane.

The key now is the new allTypes class. Java doesn’t have an easy way to test if data is of a specific primitive type (boolean, int, double) or just a character string. Additionally, method overlays only look at the method signature, that is, the input parameters of each method. Methods that return boolean or double results but don’t take parameters just generate “duplicate method name” exceptions. For these reasons, I put members for each of the data types I need (boolean, int, double and string) together with overlay methods for reading and writing those members. To get around the “signature limitation” for reading data, I added a dummy input parameter to each implementation of read(). I don’t use the dummy, it’s just there to trick Java into using the correct overlay on a per module pin basis (where one pin needs a boolean and another needs an int, and both call allTypes.read() identically).

Once I had allTypes, I could create an ArrayList called connections. The first 30 items in connections are reserved for the jSliders and A-300 dial controls. Any new connections between modules (i.e. – osc1.out to adsr1.in) are added to the end of the ArrayList. In addition, I created a box class, which groups together the fixed elements of the synth, including keyboard gate out, keyboard note out, and speakers in. Because the VCF filter module needs all 320 samples pre-generated before performing the FFT, and the pan module operates on the VCF output, I included references in box to any vcf or pan object that the user creates. If either object is undefined by the user, the addBuffer() method just skips over them and simply sends the data to the next step, or directly to the sound engine.

In order for the modules to read and write to the backplane, I had to add a lot of extra code to each. The inpins[] and outpins[] arrays contain the names of both sets of pins as they will be specified by the user for making connections. The connectorInPins[] and connectorOutPins[] arrays contain the index number into the connections ArrayList based on the user wiring. That is if the user specifies that control 0 connects to osc1 frequency in, and osc1 out connects to ADSR1 in, then osc1 connectorInPins[0] = 0, osc1 connectorOutPins[0] = 30 (the first new connection added to the ArrayList) and adsr1 connectorInPins[0] = 30.

As may be expected, I had to completely rewrite the displayCircuitValues() and processScreenInputs() methods to either process data from connections, or from the module range members. Now, data from the jSliders, jTextfields and A-300 dials will either go to connections directly if the user is on the master screen (display screen 0), or to the module variables for the module-specific data entry screens (using the previous setStr() methods). updateFromKeyboard() had to be rewritten to address the new code for processScreenInputs() and to deal with commands from the drum pads for toggling the  osc.glide, adsr.gate, echo.onOff and vcf.onOff flags. Then, to actually communicate with the backplane, each module needed readConnector() and writeConnector() methods, using the connectorInPins[] and connectorOutPins[] arrays to link the module variable (freq, rate) to the connector index number for each wired pin.

As I moved on to addBuffer(), I realized that I needed one more module. Just as the mixer() object is designed to take a variable number of inputs and turn them into one simple pin for wiring purposes (connecting osc1.out, noise1, out and echo1.out to adsr1.in) I had to have a splitter that would output the same signal to more than one input pin. Specifically, splitting the gate signal (keyboard.gate + osc2.out -> mixer1.in) for adsr1.gateIn and noise1.gate; and the adsr1.out audio signal for vca1.in and echo1.in. The splitter class is really just an ArrayList of a variable number of allTypes objects that store whatever appears at the input pin. This way, when the splitter output is read, the same data goes to the backplane regardless of exactly which pin it is.

(Example of how the backplane works with the modules and controls.)

AddBuffer() got renamed to processSound(), and was greatly simplified. Now, it’s just a plain for-loop that reads the backplane (connections ArrayList) for each module (osc1, osc2, adsr1, vca1, etc.) and then writes new waveform data back out to it on a per-sample basis. Once the entire 320 samples are loaded into the buffer, it’s given to the vcf (if specified by the user) and then to pan (if specified by the user). Verifying the wiring of the user’s synth circuit is then left to the user.

private void processSound() {
ByteBuffer    byteBuffer;
ShortBuffer   shortBuffer;
byteBuffer  = ByteBuffer.wrap(audioBuffer[audioBufferPtr]);
shortBuffer = byteBuffer.asShortBuffer();

double [] waveform = new double[AUDIO_SLICE_SHORT];

connections.get(box1.keyBdGate).b = keyBoard.gateOut();
connections.get(box1.keyBdNote).d = keyBoard.noteOut();
for(int dataPointCnt = 0; dataPointCnt < AUDIO_SLICE_SHORT; dataPointCnt++){
for(objectNamePair onp : modulesList) {
if(onp.obj != null) {
((module) onp.obj).readConnector();
((module) onp.obj).writeConnector();
}
waveform[dataPointCnt] = connections.get(box1.fixed).d;
}
}

if(box1.haveVCF()) waveform = box1.vcfModule.applyFilter(waveform);

if(box1.havePAN()) {
box1.panModule.buildPan();
for(int i=0; i<AUDIO_SLICE_SHORT; i++) {
shortBuffer.put( (short) (waveform[i] * box1.panModule.panBufferR[i]) );
shortBuffer.put( (short) (waveform[i] * box1.panModule.panBufferL[i]) );
}
} else {
for(int i=0; i<AUDIO_SLICE_SHORT; i++) {
for(int j=0; j<AUDIO_CHANNELS; j++) {
shortBuffer.put( (short) waveform[i] );
}
}
}

System.arraycopy(audioBuffer[audioBufferPtr], 0, audioData, 0, AUDIO_SLICE_BYTES * AUDIO_CHANNELS);
audioBufferPtr = (audioBufferPtr == 0) ? 1 : 0;                             // Switch double buffer
gotData = true;                                                             // Tell sound engine to play sample
}

Which brings us to just exactly how the user enters the circuit.

I added a new textfield (jTextfield13) for command line input, with status messages and connection results going to jTextArea1.

parseCommands() – main string parser
parseCommandsSub() – used for recursion (Java doesn’t like recursion)
findModule() – new method for getting modulesList index number
addNewModule() – adds module to moduleList
addNewRange() – adds pin range data for each module

The new user commands are:

new – specifies a new module (i.e. – new osc osc1)
pin – gives the pin name and range (pin (50, 1000, 512, 256, Freq., freq))
connect – connects two pins (connect control.0 vca1.amp)
break – disconnects the specified pin (break vca1.amp)
grab – takes everything in jTextArea1 as batchfile input
killcircuit – deletes the existing circuit to start over (killcircuit)
List – lists command history, connections, controls and wiring.
help, ? – Lists the commands, special pin names, and individual command syntax

I don’t have patch file open or save implemented yet, so entering commands all the time to test the program got tedious fast. I added the grab command so that I can put the circuit wiring into a textfile and just copy it from notepad to jTextArea1 each time. Adding new menu bar items (open patch file, new circuit, list command history) will be trivial now because I can just pretend that most of the menu items are user input to the string parser.

A sample synth circuit:

new osc osc1 (100, 1, 0.5, true)
pin (50, 1000, 512, 256, Freq., freq)
pin (0,     4,   4,   0, Waveform, waveform)
pin (0.0, 1.0, 100,  49, Ratio, ratio)
pin (0,    50,  50,   0, Glide Smoothness, smooth)
pin (0,    50,  50,   0, Glide Width, width)
pin (0,     1,   1,   0, Enable Glide, enableGlide)
pin (0,     1,   1,   1, Gate, gate)

new vca vca1 (16000, 0.0)
pin (0, 16000, 512, 256, Amp., amp)

connect control.0 vca1.amp
connect control.1 osc1.freq
connect control.2 osc1.waveform

connect kbd.gate osc1.gate
// connect kbd.note osc1.freq  // Note used if A-300 keyboard not connected
connect osc1.out vca1.in
connect vca1.out box.fixed

The syntax for the pin range is the same as for the Java class:
(range min, range max. # steps, starting slider value, textfield string, pin real name)

“//” is used to comment out commands that I may want later. I can also use it for documenting the patch file to remind myself of what each module is for.

It took me 10 days to get this far. Easily the most time-consuming part of the entire app.

Raw formatted full Java listing here.

 

Advertisements
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: