JASS Wiring Patch Example


Time to wrap things up for a while. This will be the last entry for the JASS synth, until I come up with an excuse to write about it again. I’m hoping that someone else will look at the code and decide to make it more “Java compliant”, and maybe point out ways of making improvements to the FFT section, or overall playback.  Enjoy. In the meantime, here’s the “patch” for my equivalent of an old Moog synth.

// This is a wiring patch for the JASS (Java ADSR Software Synth) app.
// It contains:
//     3 VCOs (voltage controlled oscillators)
//     2 Benders, for shifting frequencies of VCO 2 and 3, based on the VCO 1 frequency
//     1 Gate oscillator, for driving the circuit if the A-300 keyboard isn’t connected
//     2 Noise generator (configured to run during the ADSR attack, decay and release phases)
//     1 ADRS
//     3 mixers for allowing multiple connections to 1 input pin
//     2 Splitters, for allowing multiple connections from one output pin
//     1 VCF (voltage controlled filter)
//     1 Pan module
//     1 echo module for reverb
//     1 VCA (voltage controlled amplifier) as the main sound amp
//     1 arp (arperggiator) module for running note patterns
//
// https://threestepsoverjapan.wordpress.com/
//
///////////////////////////////////////////////////////////////////////////////////

// Create the new modules.

new osc osc1 (100, 0, 0.5, true, 0)
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,   1, Enable Glide,     enableGlide)
pin (0,     1,   1,   1, Gate,             gate)

new osc osc2 (100, 0, 0.5, true, 0)
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,   1, Enable Glide,     enableGlide)
pin (0,     1,   1,   1, Gate,             gate)

new Bender bender1 (1.0)
pin (-2.0,  2.0, 400, 199, Offset, offset)

new osc osc3 (100, 0, 0.5, true, 0)
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,   1, Enable Glide,     enableGlide)
pin (0,     1,   1,   1, Gate,             gate)
new Bender bender2 (1.0)
pin (-2.0,  2.0, 400, 199, Offset, offset)

new osc gate1 (20, 1, 0.5, true, 2)
pin (0.1,  20, 100,   3, Freq.,    freq)
pin (0,     4,   4,   1, 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 Splitter freqSplitter (2, 0)

new mixer adsrMixer (4, 1.0, 0)
pin (0.0,   3, 100,  60, Avg. Comp., comp)

new vca mainAmp (16000, 0.0)
pin (0, 16000, 512, 256, Amp., amp)
pin (-2.0, 2.0, 100, 49, Offset, offset)

new ADSR adsr1 (4000, 4000, 0.4, 4000)
pin (0, 8000, 512, 255, Attack,   attack)
pin (0,  600, 100,  10, Punch,    punch)
pin (0, 8000, 512, 255, Decay,    decay)
pin (0,  1.0, 400, 200, Sustain,  sustain)
pin (0, 8000, 512, 255, Attack2,  attack2)
pin (0,  1.0, 400, 200, Sustain2, sustain2)
pin (0, 8000, 512, 255, Release,  release)
pin (0,    1,   1,   0, Invert,   invert)
pin (0,    1,   1,   1, Gate,     gate)

new mixer gateMixer (2, 1.0, 2)

new VCF vcf1 (512, 0, 500, 0.5)
pin (0,   512, 512, 512, Cutoff,   filterLevel)
pin (0,    2,   2,   0, Mode,      mode)
pin (0,    7, 512, 256, Threshold, threshold)
pin (0,  1.0, 100,  50, Mult.,     mult)
pin (0,    1,   1,   1, Filter On, filterOn)

new Splitter echoSplitter (2, 0)

new mixer echoMixer (2, 1.0, 0)
pin (0.0,   3, 100,  60, Avg. Comp., comp)

new Echo echo1 (32000, 0.5, 0.75, 0, 1)
pin (100, 32000, 512, 255, Loop Length, loop)
pin (0.0,   1.0, 100,  49, Delay Max.,  delay)
pin (0.0,   1.0, 100,  49, Decay,       decay)
pin (0,       1,   1,   0, Mode,        mode)
pin (0,       1,   1,   1, echoOn,      echoOn)

new mixer noiseMixer (3, 0.0, 3)

new noise noise1 (0, 10, 0.4)
pin (0,   3,   3,  0, Mode,          mode)
pin (0, 100, 100, 10, Density,       density)
pin (1, 100,  99, 49, Brownian Max., brownian)
pin (0, 1.0, 100, 20, Volume,        amp)
pin (0,   1,   1,  1, Gate,          gate)

new Pan pan1 (true, 0.5, 1.0, 0.5)
pin (0.1, 10, 100, 10, Pan Rate,     rate)
pin (0,    4,   4,  0, Pan Waveform, waveform)
pin (0,  1.0, 100,  49, Magnitude,   mag)
pin (0,  1.0, 100,  49, Offset,      offset)
pin (0,    1,   1,   1, panOn,       panOn)

new Arp arp1 (1, 2.0, 0.5, true)
pin (0.1,   10, 100,  10, Arp Rate,     rate)
pin (0.01, 1.0, 100, 100, Arp Ratio,    ratio)
pin (   1,   5,   4,   0, Note Steps,    step)
pin (   0,   1,   1,   0, Note Gate,    noteGate)
pin (   0,   1,   1,   0, Master Gate,  masterGate)
pin (0,    4,   4 ,  0, Pattern,      pattern)
pin (0,    1,   1,   1, Enable,      gate)

// The next section sets up the patch.
// Specify wiring to the fixed pins.

connect kbd.note osc1.freq
connect kbd.gate gateMixer.1
connect kbd.pitch osc1.pitchBend
connect kbd.touch osc1.touchIn
connect arp1.out kbd.arp

// Specify wiring between modules.

connect gate1.gateOut gateMixer.2
connect gateMixer.out adsr1.gate
connect osc1.out adsrMixer.1
connect osc1.getFreq freqSplitter.in
connect freqSplitter.1 bender1.freqIn
connect bender1.out osc2.freq
connect osc2.out adsrMixer.2
connect freqSplitter.2 bender2.freqIn
connect bender2.out osc3.freq
connect osc3.out adsrMixer.3
connect adsrMixer.out adsr1.in
connect adsr1.attackE noiseMixer.1
connect adsr1.decayE noiseMixer.2
connect adsr1.releaseE noiseMixer.3
connect noiseMixer.out noise1.gate
connect noise1.out adsrMixer.4
connect adsr1.out echoSplitter.in
connect echoSplitter.1 echoMixer.1
connect echoSplitter.2 echo1.in
connect echo1.out echoMixer.2
connect echoMixer.out mainAmp.in
connect mainAmp.out box.fixed

// Specify wiring from the sliders and dials to the module input pins.

connect Control.0 mainAmp.amp
connect Control.1 osc1.waveform
connect Control.2 bender1.offset
connect Control.3 bender2.offset
connect Control.4 adsr1.attack
connect Control.5 adsr1.decay
connect Control.6 adsr1.sustain
connect Control.7 adsr1.release
connect Control.8 echo1.loop
connect Control.9 echo1.delay
connect Control.10 echo1.decay
connect Control.11 osc1.smooth
connect Control.12 osc1.width
connect Control.13 osc2.waveform
connect Control.14 osc3.waveform
connect Control.15 noise1.density
connect Control.16 noise1.amp
connect Control.17 pan1.rate
connect Control.18 pan1.mag
connect Control.26 echo1.toggle
connect Control.27 vcf1.toggle
connect Control.28 noise1.toggle
connect Control.29 pan1.toggle

// Specify the module “pin” settings. This is part of the actual patch.
// Format: setting moduleName pinName sliderValue rawData

setting osc1 freq 51 144.63
setting osc1 waveform 0 0
setting osc1 ratio 54 0.54
setting osc1 smooth 10 10
setting osc1 width 10 10
setting osc1 enableGlide 1 1
setting osc1 gate 1 1
setting osc2 freq 198 417.38
setting osc2 waveform 0 0
setting osc2 ratio 49 0.49
setting osc2 smooth 10 10
setting osc2 width 10 10
setting osc2 enableGlide 1 1
setting osc2 gate 1 1
setting bender1 offset 141 -0.59
setting osc3 freq 256 525
setting osc3 waveform 0 0
setting osc3 ratio 9 0.09
setting osc3 smooth 10 10
setting osc3 width 10 10
setting osc3 enableGlide 1 1
setting osc3 gate 1 1
setting bender2 offset 170 -0.3
setting gate1 freq 0 0.1
setting gate1 waveform 1 1
setting gate1 ratio 95 0.95
setting gate1 smooth 0 0
setting gate1 width 0 0
setting gate1 enableGlide 0 0
setting gate1 gate 1 1
setting adsrMixer comp 60 1.8
setting mainAmp amp 419 13093.75
setting mainAmp offset 49 -0.04
setting adsr1 attack 113 1765.62
setting adsr1 punch 10 60
setting adsr1 decay 124 1937.5
setting adsr1 sustain 309 0.77
setting adsr1 attack2 0 0
setting adsr1 sustain2 0 0
setting adsr1 release 323 5046.88
setting adsr1 invert 0 0
setting adsr1 gate 1 1
setting vcf1 filterLevel 512 512
setting vcf1 mode 0 0
setting vcf1 threshold 287 3.92
setting vcf1 mult 39 0.39
setting vcf1 filterOn 1 1
setting echoMixer comp 54 1.62
setting echo1 loop 92 5832.03
setting echo1 delay 30 0.3
setting echo1 decay 11 0.11
setting echo1 mode 0 0
setting echo1 echoOn 0 0
setting noise1 mode 0 0
setting noise1 density 19 19
setting noise1 brownian 65 66
setting noise1 amp 4 0.04
setting noise1 gate 1 1
setting pan1 rate 12 1.29
setting pan1 waveform 0 0
setting pan1 mag 86 0.86
setting pan1 offset 7 0.07
setting pan1 panOn 0 0
setting arp1 rate 20 2.08
setting arp1 ratio 49 0.5
setting arp1 step 1 2
setting arp1 noteGate 0 0
setting arp1 masterGate 1 1
setting arp1 pattern 1 1
setting arp1 gate 1 1

Advertisements

Java Synthesizer, Part 23, Full Synth


It’s funny, how, as I try to verify new sections of the app, especially after connecting up the A-300, just how many new bugs I find. In some cases, it’s obviously a copy-paste error and I wasn’t careful enough in making all the changes needed. In others, I have no idea what I was thinking as I wrote something that obviously can’t work as-is. And occasionally, I simply didn’t realize that certain conditions could combine and never added checks for them.

I’m trying to get more interesting sounds out of the program, while also imitating existing old-style hardware synths, like the early Moog boxes. So, I figured that I’d make one big circuit and leave the plug-cable wiring for later when I wanted to experiment with a specific patch. This meant putting 3 oscillators, a gate osc., the ADSR, a VCF and the VCA amp into one file. However, the first circuit, a simple gated ADSR driven by three offset oscillators, didn’t work. In fact, it didn’t work for quite a few reasons.


(Full-blown synth circuit wiring diagram. osc2 and osc3 are driven by the frequency out of osc1 and offset via the benders. The ADSR generates separate gate events for each of the attack, decay, sustain and release modes, so I’m using the A, D and R events to gate the noise1 generator. The splitters just take whatever value is applied to the input and clone it to each of the splitter output pins. This allows a one-to-one pin wiring when making connections between modules. The mixers act in a similar fashion for the one-to-one pin assignments, but can be used for signal averaging or scaling, or as boolean AND and OR gates. While the wiring is user-definable for most of the modules, the keyboard, VCF (voltage-controlled filter) and pan are the exceptions; for these, I pretend to have fixed box inputs and outputs that the other modules can tie to.)

First was that as I increased the number of modules, the workload in the processSound() method started pushing the limits for the 44K sampling rate. I dropped back to 16K, then tried bringing the FFT array size from 1K to 2K, but the sound broke up again. Currently, sampling rate is 16K and the FFT array is at 1K, which still sounds pretty good.

Second, the code for saving and restoring the pin values (i.e., the range settings) behaved randomly, and the amplitude for the pan module kept going to 0. Took a while before I realized that if I programmed a specific slider or dial to a module input (i.e. – the input pin for that module), when I loaded the patch from file that the connector object was defaulting to 0.0 for each connected pin. That is, even though I was preloading the settings into the range objects, the connector object was overriding the settings with 0’s when processSound() started running. Copying the settings into the connector object for each pin connected to a control (dial or slider) fixed that problem.

Third, the drum pads on the A-300 keyboard were producing multiple button press events, and causing havoc with the menus. Before, I’d been tapping the pads fairly quickly, so I could mimic clicking on the Next and Previous buttons pretty easily. Now though, I was pressing the pads longer and skipping 3-4 screens at a time. I needed to modify the updateFromKeyboard() method to only send one .doClick() message per pad press.

Youtube link.

Fourth, the canned module code hadn’t kept up with the changes I was making to the command line parser. So, while I could create a new custom circuit by hand-entering the code from the command line, trying to do the same thing through the menu items was causing parser errors. So I had to double-check each line of the canned code and tweak that to match the parser syntax.

Fifth, the arp module was misbehaving. The patterns weren’t running, the steps between notes weren’t noticeable and the canned code didn’t include an option for wiring the arp module to the keyboard input pin. As a part of the fixes, I added a step option to allow the user to scale a “0 1 2 3” pattern to “0 2 3 6” or “0 3 6 9”. The bigger steps make for more noticeable changes and make it easier to tell if the pattern is running.

There were a number of other minor tweaks, and typos that I noticed in the comments that I also addressed. I started out with just one oscillator, and with each new circuit added one or two more modules, made the wiring connections and verified that those worked before incrementing to the next circuit. The final package has what I’d tried to do when I did the full Moog emulator a week ago.  The difference being that everything runs correctly now. There’s still noise and breakups when Windows does something in the background, so I’m thinking that running this app on a Linux box is the only way to go.

Full Circuit Patch textfile.

Java Synthesizer, Part 22, Arping


Just some observations regarding arpeggiating. On its surface, the idea of arpeggiating a note, or series of notes, is very straightforward. Essentially, it’s like automating someone standing at a keyboard and pressing keys (i.e. – like a sequencer). But, as I dig deeper through it, and spend more time implementing the ideas arising from the arp module, the more complex things become.

The simplest form is just one key turning on and off. Stand in front of the keyboard. Press one key. Let go. Press it again for the same amount of time. Let go for the same amount of time. Repeat. This is a gate arpeggiator. One approach is to take an LFO (low frequency oscillator) running a squarewave and connect it to the gate-in pin of the envelope generator (ADSR) or gate-in for an audio oscillator. The note out will depend on which key you press, and the rate and duty cycle (ratio of time on to total cycle time) will be based on the LFO rate and ratio settings.

(Imagine there’s a VCA between osc2 and osc1.)

The next type of arpeggiator switches between two notes to create a trill effect (usually two notes close together and switching between 10 to 50 times a second). An approach for this circuit is to connect the LFO outputting a squarewave to the input of a VCA (voltage-controlled amplifier). Because the LFO output only goes between -1.0 and +1.0, we need the VCA to create a signal that oscillates between our two desired frequency numbers. Set the VCA offset to 2.0, and amplitude to 100, to get a value of 100 ((-1.0 + 2.0) * 100) and 300 ((+1.0 + 2.0) * 100). Connect the output of the VCA to the setFreq pin of an audio oscillator and you get your trill between 100hz and 300hz. Rate and duty cycle still depend on the LFO frequency and ratio, while the upper and lower notes are a function of the VCA amplitude and offset settings.

Things get a little more tricky when dealing with patterns. Stand at the keyboard and press four adjacent keys in a rising sequence, and then loop them. This would be a “0 1 2 3” pattern. Start with a different key and do the same loop and it’s still “0 1 2 3”. Play the adjacent keys in a falling sequence for a “0 -1 -2 -3” pattern, etc. The easiest approach is to take a dedicated arp module and connect it to the keyboard hardware, adding the arp pattern value to the keyboard NoteOn number. So, if you press MIDI note number 50, the rising pattern would give you “50 51 52 53” in a loop, while the actual frequencies played by the audio oscillator would come from an array of piano frequencies (for example). However, there are now at least three more operating modes available from an arp module.

Mode 1: Switch between notes with no break between them. This would be like pressing one key of a monophonic (one note at a time) keyboard, then pressing the next without letting go of the first. Press the third without releasing the second, etc. As far as the envelope generator (ADSR) is concerned there was only one keypress, which started the attack phase, and no unpress, so no start of the ADSR release phase, until you actually let go of the last key.

Mode 2: Switch between notes with breaks. Press one key, let go. Press the next key in the pattern and let go. Etc. The envelope generator would trigger with each key, applying the attack, decay, sustain and release phases to each note. This is probably what most people think of when talking about arpeggiating. You press the key once, and as you hold it, the arpeggiator walks through the pattern, applying the envelope to each new note out.

Mode 3: Kind of like a cross between 1 and 2. There’s a break between notes, but the ADSR isn’t triggered each time. That is, the envelope starts when you press the key, and ends only when you let go. The notes sequence through the pattern as raw oscillator output as long as you hold the key down, with the dead space between notes based on the arp on/off ratio setting.

The rates of modes 1-3, and duty cycles of modes 2 and 3, are determined by an internal LFO within the arp module. Selection of the pattern to play is done through the arp module itself. The frequencies out are a combination of the keyboard key being pressed, and the step values specified in the pattern (i.e. – 0, 1, 2, 3 or 0, -4, 2, 6).

Why go into all this detail?

Each synth circuit provides slightly different audio experiences, and can be used in combination with the others.

A gate oscillator driving the ADSR provides a solid, stable pulse regardless of the waveforms being applied to the ADSR input.

An LFO driving another oscillator lets you select any two frequencies that you want. Contrast this with the arp module in mode 1 running a “0 1” pattern, which only lets you pick the fixed frequencies used by a piano keyboard.

Youtube Video

Arp module mode 1 makes for a nice, smooth trill, while mode 3 is very harsh and abrupt. Mode 2 is unlike any of the other setups because it treats each note in the pattern as a separate keypress for gating the ADSR (but similar to the gate oscillator if you’re just running a “0 0” pattern).

Combining circuits would let you have a trill effect and a “0 2 4 -6” pattern running to the input of the ADSR, which is being gated by a completely separate LFO.  Mind you, all this arpeggiation  is possible with just some oscillators, the VCA, the arp module and the ADSR. Throw in everything else, including the bender, the oneShot, filtering, pan, echo, mixers and splitters in any combination you like, and you can really start having fun.

Java Synthesizer, Part 21, Tidbits


Ok, now I’m just in final clean-up and last tweaks.

VCF – I wanted to see how far I could take the FFT filter before encountering performance issues. I’d started out with a 1K FFT array, and had hardcoded some of the buffer sizes in the vcf method, so the first step was to clean up the code and use variables everywhere. When that was done, it was a just matter of changing one constant (array size) and running a simple synth circuit (an oscillator with gated ADSR and vcf) to test it. Going to 4K immediately resulted in break up of the sound. Dropping down to 2K allowed for a clean sound, but there’s little real difference in the filtering effects between 1K and 2K.

At the end, I wanted to see what happens when I increase sampling rate from 16,000. I’d already softcoded the methods tied to sampling rate, so all I had to do was change the rate to 44,200 in one location. However, the impact on the simple oscillator circuit was really severe, with breakups in the sound playback. The code looked fine (meaning I hadn’t introduced a bug somewhere) and on a hunch I turned off the VCF module. The playback returned to normal, so I dropped the VCF array size back to 1K and the dropouts disappeared. Turns out that changing sampling rate makes a much bigger difference to the quality of FFT filtering than changing the FFT array size does. A lot of the hiss caused by rapid changes in volume and frequency has also disappeared with the higher sampling rate. I’ve decided to keep sampling rate at 44,200 and the FFT array size at 1K.

The next step was to try adding base boost. Normally, it’s a simple case of writing a value to a desired frequency bin and then running the inverse FFT on the frequency data. However, because of the minimal number of frequency bins and the fact that a lot of energy is spread out over several bins with the circuit I was using, the output sound was noisy and the base note got lost easily. So, I decided to instead just use a separate oscillator running at 50 hz, and that sounded pretty good. So, if I want more base along with the regular waveform, I’ll start with a second osc module first.

Which brings me back to the bender module. I started thinking about how to have two notes, where the second was a fixed fraction of the first. That is, if I played a 200 hz note, I’d also get something at 100 hz or 50 hz (anything less than 50 hz would either be inaudible or produce clicking from my laptop speakers). I could try making a new module that acts as a pure multiplier or divider, or I could just use a bender and hand-type 0.5, 0.25 or 0.125 into the offset text field. Or, I could set the bender offset range to go from 0.1 to 1.0 in 9 steps and set the osc2 frequency that way. The more I think about it, the harder it is to make a decision. I’d say that it really depends on the final sound I’m trying to create, but the base note probably won’t be user adjustable during play time, so a bender with a fixed offset feeding into osc2.setFreq() will be good enough right now.

pitchwheel: The code was already mostly in place for implementing the pitch wheel with the new circuit design. I just needed to add the “pitch” keyword to the command parser and the menuing system. However, I’d initially just adjusted pitch as part of the sine waveform generator, meaning that as the pitchwheel was moved I was getting loud clicking, and it didn’t work with the other waveforms, like the square and triangle waves. So I modified the oscillator setFreq() method to include pitch and channel pressure as part of the check for a change in incoming frequency. The advantages of this change are that both the pitch wheel and channel pressure (or aftertouch) work with all of the available waveforms, there’s very little clicking, and I can smooth the transitioning sound even further by tweaking the glide settings.

As a reminder, channel pressure is a MIDI option that allows the user to vary the current note out by pushing harder or softer on the key without releasing it. Channel pressure is an average of all keys pressed at one time, while after touch is per-key. It’s a question of what the manufacturer of your model of keyboard decided to implement. I’m using the Roland A-300 Pro, which has channel pressure. I hadn’t included it in the code of the A-300 class, so I had to go back and copy-paste the required lines from my K-Gater app, then track down the bugs I inadvertently added at the same time. Then, I more-or-less duplicated the existing code for the pitchwheel, and just changed the names for the new channel pressure function. I also aded a “touch multiplier” to mirror the pitch multiplier, to allow for varying the sensitivity of the keyboard key. However, even a multiplier of x1 produces a big frequency shift in the waveform output, so dropping down to x0.5 might be desireable in some cases.  On the other hand, both pitch mult and touch mult are hardcoded values, and are not user-changable right now. I’m going to leave them this way until it becomes more obvious that they should be user-accessable (would only take a few minutes to implement).

I am getting to the end of my “wish list” for new modules. Given that I put so much effort into K-Gater for user-selectable arpeggiator patterns, it seemed only reasonable to at least have some sort of similar functionality here. Back in the blog entry on oscillators, I gave several examples of how to use one oscillator to drive a second to get a trill effect, or to gate the first one on and off. Those were simple cases of arpeggiating. But, if you want more control over several sets of notes, especially if you’re ‘pegging the keyboard, then there has to be something more powerful to work with. That’s why I created the arp module. I could load the arp patterns from a file, but at the moment that’s overkill, and I can easily add more patterns to the ArrayList within Java whenever I run this app through Netbeans. Alternatively, I could add an “arp” command, and load the patterns dynamically via the command parser (which I may do, making the arp patterns part of the overall patch). In any case, the arp module allows the user to select one of the pre-coded patterns, set the play rate, and toggle the enable state. Output from the module is then an integer taken from the pattern based on rate, that is applied to a new “kbd.arp” input pin that I also created specifically for this case. The arp pattern value offsets the keyboard key currently being pressed, resulting in a different value being selected from the piano key frequencies array.

The last major, and definitely the most satisfying tweak, is note tracking. For simplicity’s sake, I’d written the keyboard noteOn() and noteOff() methods to only treat the most recent keypress as the master key. That is, if the user pressed note 1, the synth would set keyboard.gate to true and the on note as whatever key had been selected. Then, if the user pressed a second key, gate would be set to true again and noteOn would go to the new note number. When the user released either of the two keys, gate would get set to false and the sound would stop playing. Not ideal, but good enough during the testing phase. But, now that I want to play with glide, I really needed to go back and rewrite note handling to be smarter. To do this, I just made a keysList Array List for the keyboard class, and add each new note to keysList when noteOn() is called. When a key is released, noteOff() is called and I go through the array list to remove the specified entry. If the specified note is the last one pressed, I change the note played to the last item in the existing list. If the only remaining key is released, I set keysList to new ArrayList to avoid Java’s stupid null reference exception, and set gate to false. Now, I can easily play with glide between several notes at a time and the resulting effect is pretty cool.

One of the complications of going from hardcoding the synth circuit to making it user-definable, that I hadn’t discussed before, was that echo didn’t work right anymore. The original approach was to feed the input of the echo module with the output of the ADSR, and then loop the echo output to the input of the ADSR, which resulted in a desireable feedback loop and the echo output being reshaped by the envelope generator each time.

But, in the user-definable system, the echo died the second the user let go of the key and the ADSR release phase completed. That is, regardless of how long the echo decay was set for (2 seconds or more), if release ended (assume a quarter-second release setting for the ADSR) then the echo got cut short, too. While this is the correct behavior for a gated ADSR, it kind of defeats the point of having reverb in the circuit. Instead, my user-definable synth circuits now treat echo as a delay buffer between the ADSR output and the VCA amplifier input.

I lose the feedback loop into the ADSR, and the accompanying envelope shaping, but I gain extended reverb.  But, this is a trivial case, it’s just a question of how you want to wire the echo module into the circuit when using the connect commands.
I’m now at the point where I’m thinking more of circuit wiring than I am of new Java code. I’m kind of torn between making a set of pre-wired synths and loading them one at a time based on the sounds I want, or making one big master circuit with preset ranges and then specifying the inter-module wiring as if I had an actual hardware box. On the one hand, having specialized circuits would decrease patch load times. On the other, I have to consider my per-pin, per-module ranges code for each new circuit. With one big master circuit, the ranges would be coded once and I could focus more on which pins I want wired together.

Java Synthesizer, Part 20 – File Handling


Finally, I can save and load patch settings.

Actually, it took a bit of code to get to this point, too. Things really were speeded up with the introduction of the command line parser, since all I really needed to do was add code to call the parser from the top menu bar. And that’s where things got more messy (and/or fun, depending on your definition of “fun”).

I started out by simply adding a new menu item for listing command line instructions, modules, pin wiring, etc. – all the “list” commands already in the parser.

From here, it was a relatively simple step to add a second new menu item for creating new modules. I decided that it’d be easier all around to just hard code the constructor and range settings, then let the user tweak everything using the sliders within the GUI.

The third new menu item was “connections”, which allows for making and breaking the wiring between modules. So, in fact, the menu bar now has the same functionality for creating new synth circuits as the command line does, the only difference being that the menus use pre-coded range values, and the command line allows the user to specify everything themselves.

One bug that I ran into, that I’m not going to try to fix, is that the “pin” command needs to specify the pin ranges in the same order, and for each and every pin, as given in the inpins[] arrays for each module. It’s not really a *bug* per se, but more of my being lazy. Plus, you need to specify the range for each of the pins you want to vary from the GUI screens. I did put in a check for whether a control that you’re trying to wire has been specified with the “pin” command, but it’s easy to overlook if you’re hand entering commands through the command line.

Loading a circuit from an existing file is trivial. I just read the file one text line at a time and send it to the command line parser. This does create a long pause from when I click “ok” to when Java starts printing out parsing results in the jTextArea1 window, especially if it’s a big circuit. I think this is a limitation in Java itself, wanting to finish printing everything to the text area before displaying it to the user.  (Copying the patch from a textfile into jTextArea1 and using the grab command is several times faster than loading the same patch directly from a file.)

Saving the patch file is equally simple. First, I just write the contents of the instructions[] array, which contains the wiring commands entered by the user. Then I go through the ranges ArrayLists for each module and write a new “setting” line. I added “setting” to the command parser at the last minute. This takes the slider settings, and text field values for each module and pin, as previously changed by the user, and puts them in the ranges ArrayLists for each module. When the user saves the patch to file, the save method appends the “settings” statements to the end of the wiring commands. Then, when the file is loaded again later, the parser sees the “settings” statements and restores the user’s last synth patch settings.  This is really the key to making my program usable, because now I not only have user-configurable circuit wiring, but I can save and load the last slider settings (i.e. – what everyone calls the actual “patch”) as well. To avoid having multiple copies of the settings commands show up in the patch file with each “file save”, I strip them out of the instructions ArrayList during file save, and then save only the current range values each time.

While creating and saving various simple circuits for testing the command line parser, I made one that I called “simple osc and gated ADSR with echo and noise”. I decided to use it at random for testing the file load and save function, and suddenly the playback sound seemed strange to me. It was very hissy and had a bell-like component in the echo loop. Thinking that I’d screwed up the echo class when I tweaked it to make it compatible with the range setting function, I started digging into the in() and out() methods, then realized that I had indeed written the echo loop portion wrong. I was varying the loop buffer length, but not the time between the bufferReadPtr and bufferWritePtr for allowing the time from write to read to change.  So I ripped out the old code and put in what I’d originally planned for that class (varying loopLength, then having a variable going from 0.0 to 1.0 to set bufferReadPtr as a ratio of loopLength, lagging behind bufferWritePtr). Unfortunately, this didn’t fix the noise-bell issue, and I lost several hours until realizing that it was a natural side-effect of that particular circuit. Switching to “simple osc. with gated ADSR and echo” (and no noise) produced the kind of echo I wanted. But, the new echo loop code produces hiss when I change settings with the sliders. So, I figured “what the heck” and added a mode setting for selecting between the old version of the echo loop and the new one.

Not too much more to say about the synth from the Java side.  I still need to re-implement the pitch wheel, and I’m getting occasional “timer concurrent operations” errors when I load the patch via the parser (seems to be something with the timer class, but it doesn’t hurt anything so I’ll live with it until I figure out what the cause is). I also need to do more testing to verify that everything else works right. Plus, there is one extra feature I need to add at some point – allowing two or more keys to be pressed at the same time (I’ll retain the monophony restriction, I just don’t like having the keyboard turn off when the first key is released and the second is still pressed).  After that, I’ll make a youtube video for a quick demonstration of the app. It is still hissy, which bugs me, but that may be caused by either Java itself, the sound card hardware or the card drivers. At the moment, I have no idea how to remove the last of the hiss when changing slider settings, but at least the clicking isn’t as bad as when I first started out a month ago.

Raw formatted script file here (same as last blog entry).

 

Java Synthesizer, Part 19, Scripting


I am finally where I wanted to be when I started this synth project a month ago – with a working user-configurable synth program.  As I mentioned before, the biggest advantage to a software-based synth is that it’s almost infinitely configurable. The drawback is that there’s going to be a delay from when you press a key to when the sound comes out of the speakers. Depending on what Windows is screwing up in the background at any given moment, the delay is a tenth of a second up to 1 second. No idea how this app would perform on a Mac, but a Linux box has got to be an improvement. There’s also occasional clicking when a net browser is open. So, the best results will be with all other windows closed. Regardless, it’s a low-cost way to play with electronic sound.

A very simple circuit that still produces interesting results is the following: the ADSR generates a gate signal during each of the phases, and I’m using attackE (attack event) to provide a gate for the noise generator so there’s a brief burst of noise at the beginning of the envelope. osc1 creates the main tone, with the frequency set by the keyboard keys. ocs2 (also called the gate oscillator) creates a slow squarewave that is mixed with the keyboard gate signal to arpeggiate the adsr. The output of osc1 is mixed with the noise1 output and sent to the ADSR input. The ADSR output is split, with one line going to mixer 2 and the other to the input of echo1. The echo1 output is mixed in with the ADSR output and the result is sent to vca1 (which acts as volume control for the circuit). The amplified signal goes to the main box1 object fixed connection pin, which is invisible between vca1 and the filter, vcf1. The filter applies the FFT to the signal, which is sent to pan1 and then finally to the speakers.

This is the script file, which will eventually become a saved patch file:

///////////////////////////////////
//
// Simple ADSR with noise, pan, echo and filter.
//
// Uses: osc1      – Main tone.
//       lfo1      – Gating for ADSR.
//       mix1      – Boolean AND, taking keyboard and lfo1 signals for adsr1 gate in.
//       mix2      – Audio mixer, for osc1 out and noise1 out.
//       mix3      – Audio mixer, for echo1 out and adsr1 out.
//       split1    – Audio splitter, for signals to echo1 in and vca1 in.
//       noise1    – Noise generator, running out during ADSR attack phase.
//       adsr1     – Main envelope generator.
//       echo1     – Echo line, attached between adsr1 and vca1.
//       vca1      – Main signal volume control for ADSR output.
//       vcf1      – Filter.
//       pan1      – Panning effect just prior to the speakers.
//
/////////////////////////////////////

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 osc lfo1 (2, 1, 0.5, false)
pin (0.1,  20, 100,   2, Freq.,    freq)
pin (0,     4,   4,   1, 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 ADSR adsr1 (4000, 4000, 0.4, 4000)
pin (0, 8000, 512, 255, Attack,   attack)
pin (0,  600, 100,  10, Punch,    punch)
pin (0, 8000, 512, 255, Decay,    decay)
pin (0,  1.0, 400, 200, Sustain,  sustain)
pin (0, 8000, 512, 255, Attack2,  attack2)
pin (0,  1.0, 400, 200, Sustain2, sustain2)
pin (0, 8000, 512, 255, Release,  release)
pin (0,    1,   1,   0, Invert,   invert)
pin (0,    1,   1,   1, Gate,     gate)
new vca vca1 (16000, 0.0)
pin (0, 16000, 512, 256, Amp., amp)
new mixer mix1 (2, 0.0, 0.0, 2)
new mixer mix2 (2, 1.0, 1.0, 0)
pin (0.0, 1.0, 100, 100, Volume,     amp)
pin (0.0,   3, 100,  60, Avg. Comp., comp)
new mixer mix3 (2, 1.0, 1.0, 0)
pin (0.0, 1.0, 100, 100, Volume,     amp)
pin (0.0,   3, 100,  60, Avg. Comp., comp)
new splitter split1 (2, 0)
new noise noise1 (0, 10, 0.4)
pin (0,   3,   3,  0, Mode,          mode)
pin (0, 100, 100, 10, Density,       density)
pin (1, 100,  99, 49, Brownian Max., brownian)
pin (0, 1.0, 100, 20, Volume,        amp)
pin (0,   1,   1,  1, Gate,          gate)
new echo echo1 (32000, 10000, 0.75, 1)
pin (100, 32000, 512, 255, Loop Length, loop)
pin (10,  16000, 512, 255, Delay Max.,  delay)
pin (0,     0.9, 100,  49, Decay,       decay)
pin (0,       1,   1,   1, echoOn,      echoOn)
new vcf vcf1 (512, 0, 500, 0.5)
pin (0,  512, 512, 512, Cutoff,    filterLevel)
pin (0,    2,   2,   0, Mode,      mode)
pin (0,    7, 512, 256, Threshold, threshold)
pin (0,  1.0, 100,  50, Mult.,     mult)
pin (0,    1,   1,   1, Filter On, filterOn)
new pan pan1 (true, 0.5, 1.0, 0.5)
pin (0.1, 10, 100, 10, Pan Rate,     rate)
pin (0,    4,   4,  0, Pan Waveform, waveform)
pin (0,  1.0, 100,  49, Magnitude,   mag)
pin (0,  1.0, 100,  49, Offset,      offset)
pin (0,    1,   1,   1, panOn,       panOn)

connect control.0 vca1.amp
connect control.1 osc1.freq
connect control.2 osc1.waveform
connect control.3 lfo1.freq
connect control.4 lfo1.ratio
connect control.5 echo1.delay
connect control.6 echo1.decay
connect control.7 adsr1.attack
connect control.8 adsr1.decay
connect control.9 adsr1.sustain
connect control.10 adsr1.release
connect control.11 vcf1.filterLevel
connect control.12 vcf1.mode
connect control.13 vcf1.threshold
connect control.14 vcf1.mult
connect control.15 pan1.rate
connect control.16 pan1.waveform
connect control.17 pan1.mag
connect control.26 osc1.toggleGlide
connect control.27 echo1.toggle
connect control.28 pan1.toggle

connect kbd.gate mix1.1
connect lfo1.gateOut mix1.2
connect mix1.out adsr1.gate
//connect kbd.note osc1.freq
connect osc1.out mix2.1
connect noise1.out mix2.2
connect mix2.out adsr1.in
connect adsr1.out split1.in
connect adsr1.attackE noise1.gate
connect split1.1 mix3.1
connect split1.2 echo1.in
connect echo1.out mix3.2
connect mix3.out vca1.in
connect vca1.out box.fixed

/////////////////////////

Internally, osc1 has its glide feature, which provides a sliding up/down effect between keys. Combined with the filter, echo and pan, plus the little splash of noise and the ADSR envelope shaping, there’s a lot of fun to be had with this one arrangement. The only real complaint anyone may have is the amount of work involved in programming the slider controls. However, it’d be a simple matter to build up a library of module/pin ranges and then just copy-paste them into a new circuit. I’m debating adding menu options for implementing canned modules with the pin ranges predetermined, since the patch file could be hand-tweaked later.  Another option is to just build up a collection of circuits with different configurations and load the one I want to play with at the time.

Raw formatted script file here.

 

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.

 

Java Synthesizer, Part 17, Cleanup 2


If you’re a professional programmer, odds are that most of your code is coming out of a CAD system, so you don’t really need to write many lines from scratch. Even if you’re not using CAD, you still probably have a significant design phase where you do all your planning on paper first, and then the coding is just a matter of transcribing from the plan to the development environment. You don’t need to worry much about what you’re doing during the actual typing phase because everything’s already been laid out for you in advance.  Contrast this with the hobbyist, who is experimenting as they go along, learning with each mistake and spending hours on debug because they forgot something obvious.  The thing to remember about hobbyists is that most of what they create is for themselves and is never intended for a commercial market.  They don’t need the rigid rules and design requirements that “professionals” lean on. “Good enough” really is good enough.

I say this because I’ve been ripped by “professional” engineers accusing me of not knowing all the specs for something I’m working on, when what I really want is for someone to answer a question regarding implementation that they apparently themselves aren’t able to answer.  Be that as it may, I get the feeling that I’m trying stuff in Java that’s not really well documented (outside of some overpriced commercial training course), and that means I occasionally find myself rewriting different sections as I go along, without outside help. I either end up adding functionality that I hadn’t thought I’d need previously, or because I finally found a better way to get the job done.  Such is the case now.

I’m slowly working my way towards the user-programmable synth interface I’d mentioned before, and I’m trying to get the various existing modules ready for the Java equivalent of “pointers to a function”. I’m also implementing each of the earlier ideas I’d had for modules and module operating modes (such as attack2, sustain2 and invert for the ADSR). I’m now at a resting point before tackling a user-input script parser (I don’t know if I want to address a graphics-based schematic patch approach or not. Seems like overkill for my needs.)

So, what’s changed recently?

First, I turned the A-300 Pro MIDI parser section into an action listener. It doesn’t seem to have made any impact on the clicking from the sound engine, but it’s the first step to putting addBuffer() into its own listener, and it removes the workload on the timer method.

Second, I focused more on the circuit object. This is the object that contains pointers to each of the modules for determining which data entry screens I’m going to get for the various modules so I can adjust settings using the jSliders on the screen (or the MIDI controls on the A-300). Each new item in circuit represents a separate module (osc1, osc2, etc.) and a separate data entry screen. There’s one “main screen”, screen 0, where I can change the settings for the most important module items all in one place (i.e. – circuit volume, osc1 frequency and waveform and the ADSR attack, decay, sustain and release). circuit will be the starting point for the user wiring script parser.

Example:

k = 0;
circuits.add(new objectNamePair(osc1, “Main Panel”));
circuits.get(k).ranges.add(new range(0, 16000, 512, 256,    “Volume”,        “d”));
circuits.get(k).ranges.add(new range(50, 1000, 512, 256,    “Osc1 Freq.”,    “d”));
circuits.get(k).ranges.add(new range(0,     4,   4,   0,    “Osc1 Waveform”, “i”));
circuits.get(k).ranges.add(new range(0.1,  20, 512, 256,    “Osc2 Freq.”,    “d”));
circuits.get(k).ranges.add(new range(0.0, 1.0, 100,  49,    “Osc2 Ratio”,    “d”));
circuits.get(k).ranges.add(new range(-2.0,  2.0, 400, 199,  “Bender Offset”, “d”));
circuits.get(k).ranges.add(new range(0,    512, 512, 512,   “Filter”,    “d”));
circuits.get(k).ranges.add(new range(0, 8000, 512, 255,     “Attack”,  “d”));
circuits.get(k).ranges.add(new range(0, 8000, 512, 255,     “Decay”,   “d”));
circuits.get(k).ranges.add(new range(0,  1.0, 400, 200,     “Sustain”, “d”));
circuits.get(0k).ranges.add(new range(0, 8000, 512, 255,     “Release”, “d”));
int k++;
circuits.add(new objectNamePair(osc1, “osc1”));
circuits.get(k).ranges.add(new range(50, 1000, 512, 256, “Freq.”,            “d”));
circuits.get(k).ranges.add(new range(0,     4,   4,   0, “Waveform”,         “i”));
circuits.get(k).ranges.add(new range(0.0, 1.0, 100,  49, “Ratio”,            “d”));
circuits.get(k).ranges.add(new range(0,    50,  50,   0, “Glide Smoothness”, “i”));
circuits.get(k).ranges.add(new range(0,    50,  50,   0, “Glide Width”,      “i”));
circuits.get(k).ranges.add(new range(0,     1,   1,   0, “Enable Glide”,     “b”));
circuits.get(k).ranges.add(new range(0,     1,   1,   0, “Gate”,             “b”));
k++;

Third, I added a “module” class that all the other modules inherit from, for standardizing calls to .setStr(), .strVal(), .toggle() and .toggleGlide() for the osc, vca, adsr, etc. classes. Which means I also had to add those methods to each of the other classes.

abstract class module {
abstract void   setStr(int idx, String str);
abstract String strVal(int idx);
abstract void   toggle();
abstract void   toggleGlide();
}

=========

class osc extends module {

@Override void toggle() {
gate = (! gate);
}
@Override void toggleGlide() {
enableGlide = (! enableGlide);
}
@Override String strVal(int idx) {
String ret = “Invalid variable”;
switch (idx) {
case 0: ret = Double.toString(freq);
break;
case 1: ret = Integer.toString(waveform);
break;
case 2: ret = Double.toString(ratio);
break;
case 3: ret = Integer.toString(glideSmoothness);
break;
case 4: ret = Integer.toString(glideWidth);
break;
case 5: ret = Boolean.toString(enableGlide);
break;
case 6: ret = Boolean.toString(gate);
break;
}
return(ret);
}
@Override void setStr(int idx, String str) {
if(! str.trim().isEmpty()) {
switch (idx) {
case 0: setFreq(Double.parseDouble(str));
break;
case 1: waveform        = (int) Double.parseDouble(str);
break;
case 2: ratio           = Double.parseDouble(str);
break;
case 3: glideSmoothness = (int) Double.parseDouble(str);
break;
case 4: glideWidth      = (int) Double.parseDouble(str);
break;
case 5: enableGlide     = str2bool(str);
break;
case 6: gate            = str2bool(str);
break;
}
}
}
}
Fourth, I decided to embed a VCA module in the noise module. The reason for this is that noise is one of those features where you really don’t want it at 100% volume. Ever. So, since I’d be dedicating a VCA to attenuating the noise output anyway, it might as well be within the noise class itself. Another benefit is that it simplifies the circuit wiring section later. (The pan class also has its own vca.)

Fifth, as mentioned above, I went ahead and added attack2 and sustain2 to the ADSR, which activate when the user lets go of the keyboard key, prior to going into the release phase. As I started adding the logic for what to do if attack2 is 0, I realized that my earlier approach of tracking each phase to avoid introducing dead cycles and causing clicking was just giving myself more work. It’s easier to just use a “while(! done)” loop with a switch-case to walk through each ADSR phase with 0 length and stop at the beginning of the first non-zero phase (e.g., if attack, pitch and decay are 0, start at sustain with a count of 0, and set done = true). Now, I can very easily add a decay2 phase if I want and I don’t have to worry about remembering what my intended logic was. Anyway, I ripped out the old code and replaced it with the “while(! done)” loop. I followed this up with an invert toggle, which turns the sound envelope upside-down.

Sixth, I moved the VCF ifs and for-loops that had been in addBuffer() into the vfc class.  Again, this is for simplifying the addBuffer() code when I get to user-programmable synth circuits. I also wanted to add some new filtering techniques, so there are 2 new modes. One decays the FFT frequency bins based on magnitude thresholds (if magnitude < 50,000, decay that bin by 80%), and the second combines the existing frequency filtering with the new mode 1. Because some of the magnitudes can get really big, I converted the magnitude threshold check to log base-10. So, the threshold range is just 0 to 7.0.

Seventh brings me to the first really new piece of code – the mixer module. Unlike the Java system mixer (which I unsuccessfully played with in an earlier blog entry), this mixer is designed specifically to ease the synth design. There are certain places in the synth where two or more signals go to the same point, examples being keyBoard.gateOut and osc2.gateOut driving adsr1.gateIn; and osc1, echo1 and noise1 all going to adsr.dataIn. Rather than trying to figure how to softcode this from the GUI, I created the mixer class to take multiple inputs, average them and run them through a VCA before returning them from mixer.out.

(Example circuit showing audio-type mixer at ADSR input.)

One of the drawbacks to simply averaging signals is that you get amplitude loss. Say you want osc1 to output a sinewave from +/-1, the noise generator outputting +/- 0.4, and echo1 feeding back the signal at +/- 0.6. The final total signal will be +/- 0.66. If the ADSR outputs to vca1 with a maximum volume of 16,000 (when the input is 1.0), then the total averaged signal going to the sound engine will be 10,666, instead of the originally desired 16,000, and it sounds weaker. I could boost vca for a range from 0 to 24,000, but I’d be altering vca1’s range with every new circuit patch. It makes more sense to put a dedicated vca inside the mixer class to compensate for averaging regardless of the number of pins involved.

The mixer class also handles booleans for gate signals (I just have an AND function right now); and multiplying signals together for scaling the ADSR output with the pan waveform.

The last real change is to addBuffer(), which is starting to look like it contains the output from a code generator. This is an intermediary step in the process of turning everything into “pointers to functions”.

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

// Temp variable to store calculation results
double [] hold    = new double[AUDIO_SLICE_SHORT];

osc1.pitchBend = keyBoard.pitchWheelOut();
// Harmonic oscillator 3 relative to oscillator 1’s frequency
osc3.setFreq(bender1.out(osc1.getFreq()));

for(int pCnt = 0; pCnt < AUDIO_SLICE_SHORT; pCnt++){
osc2.nextSlice();  // Increment oscillator 2

mix1.in(0, keyBoard.gateOut());
mix1.in(1, osc2.gateOut());
adsr1.gateIn(mix1.outAndBool());  // Turn on ADSR via the keyboard or an oscillator

noise1.gateIn(adsr1.attackEvent());

mix2.in(0, osc1.nextSlice());
mix2.in(1, noise1.addNoise());
mix2.in(2, echo1.out());

hold[pCnt] = adsr1.nextSlice(mix2.out());
echo1.in(hold[pCnt]);
pan1.buildPan(pCnt);
vcf1.dataIn(pCnt, new Complex(hold[pCnt], 0));  // Add data to filter buffer
}

hold = vcf1.applyFilter(hold); // Do the actual FFT filtering
}

Raw formatted textfile of full app here.

 

Java Synthesizer, Part 16 – pan


As mentioned in the last article, I still had some cleaning up to do in the sound engine. Along with clicking that would crop up in the waveform at random, there’s a break up of the waveform right when the app starts running, and the engine only supports 1 channel (mono sound). The conversion to stereo wasn’t that difficult, it’s just that I wasn’t paying enough attention and I introduced a bug that took a couple hours to track down.  I also wanted to softcode the various audio parameters (mainly sampling time (10 ms or 20 ms) and the slice sizes (320 or 640 bytes).

The sound engine really didn’t need to change much. The one issue I still haven’t been able to resolve is the sound breaking up right after the app starts. I’m thinking its because of an uninitialized section of the echo buffer array, but it’s intermittent. I can’t guarantee that the problem will happen the exact same way each time, but it is exasperated by having the echo object in the loop.  The easiest work around is to just let the app sit for 20 seconds before trying to play music. I know it’s not an acceptable solution in the long term, but it’ll have to do until I can figure out what’s wrong. On the other hand, I discovered that the double buffering wasn’t starting out staggered properly, and by fixing that I caused a lot of the other random clicking (caused by Windows doing stuff in the background) to go away.

Converting to stereo is really just two steps: changing the number of channels in the audio format object, and doubling the size of the audio double buffers. The sound engine receives twice the data in one second for 2 channels compared to 1, so the buffers holding the waveform data need to increase, but otherwise it has no impact on the other modules. However, the addBuffer method needs to put data into the shortBuffer so that the right and left channels alternate in sequence. And this brings us to panning.

Once stereo is functioning correctly, adding a panning module is easy. However, it has to work in two steps. The first step is building up the volume multipliers for the right and left channels (usually, the 2 channels are 180 degrees out of phase), which can be done in the addBuffer for-loop. The second step is to wait until the VCF is done with the Fourier transforms and then apply the pan data to the filtered waveform data. This second step means that the pan data needs to be stored in an array, as does the waveform data coming out of the ADSR. Then the pan array can be applied to the filtered waveform array and directly .put() into the shortBuffer for transfer to the sound engine.

I’ll sidetrack a bit here. My goal is to have a way to build up synth circuits on the fly instead of hardcoding them, which I’d then be able to save as patches to disk. This means that whatever goes into the addBuffer() method for-loop in building up the waveform data has to be abstracted enough to allow me to just use the equivalent of pointers to objects and object method calls. I’ve been able to do that mostly with my circuit object, and the implementation of the module framework. Mostly.

There are 3 modules that don’t play well with each other. They are:

keyboard, which gets its data from the A-300 via a timer call.
VCF, the Voltage-Controlled filter, which operates on all 320 samples at once.
pan, which operates on the output from the VCF, just before the sound engine.

Essentially, keyboard, VCF and pan are attached directly to the synth inputs or outputs and need to be hardcoded as system functions. This means that while the user can set the filter frequency or the pan rate, they can’t choose to not have them in the overall design.  On the other hand, these three circuits may be bypassed or disabled if they’re not wanted. This is just a programming decision, and I may change my mind about hardcoding VCF and pan in the future.

Back to pan. The pan class is really just two 640 byte-long buffers, one for each speaker, plus an osc and VCA objects. The osc lets me choose between my various waveforms (if I deactivate pan, I just output 1.0’s for both channels), and the VCA offsets the osc output to run between 0.0 and 1.0. A balanced signal oscillates above and below 0.5. The osc output goes to the channel buffers.  Then, in the second step, the buffers act as multipliers for the ADSR output data as it’s being .put() into the shortBuffer. The left channel multiplier is just 1 – the right channel.

———–

The addBuffer() algorithm this time around is:

Read keyboard pitchwheel to osc1
For 0 to 319
Increment osc2 counter
ADSR gate is set to keyboard gate or osc2 output
noise1 gate triggered by ADSR attack event
noise1 is fed to vca3
The output of osc1 is the input to the ADSR
The output of the ADSR, vca3 and echo1 are averaged and input to vca1
The output of vca1 is stored to the hold buffer and written to the echo and filter buffers
Calculate the pan multiplier and store to the pan buffer
// End of for-loop

Apply the FFT filter and write to the hold buffer
Multiply the hold buffer data against the pan buffer and send to the sound engine

Raw formatted textfile here.

 

Java Synthesizer, Part 15 – Glide Redux


I’ve got just about all of the synth modules done that I want. Ignoring the GUI side, my main interests are in cleaning up the sound engine, removing the clicks when changing frequency if at all possible, and adding pan. Pan is a control that over time weights the sound coming out of one speaker higher than the other(s) (more than one in the case of quad systems, etc. For the laptop, there’s just the two speakers.) The sound data line can be defined as mono or stereo, and right now it’s just mono. If I go to stereo, I need to make the byte buffers twice as long, and that means messing with the sound engine. So, I might as well address pan at the same time I clean up the engine itself.

I wrote about trying to clean up the clicks in the sound waveforms before, but I decided to take a second stab at it. Any discontinuities in the waveform, or any changes that are too rapid, will result in clicks, even if the sample is only 16us long. If you are doing something like:

while(! done) {
waveform = Math.sin(cnt * (2 * Math.PI * freq) / sampleRate);
if(userChangedFreq) {
freq = newFreq;
userChangedFreq = false;
}
cnt++;
}

Then you have no way of knowing where in the old cycle you are changing from or where in the new cycle you’re changing to. Meaning you’re going to have a discontinuity, and that’s where the disproportionately loud click comes in.

One option is to look for where the sinewave crosses 0 from negative to positive and then change frequencies there.

if(userChangedFreq) {
if((Math.sin((c1) * 2 * Math.PI * freq / sampleRate) < 0) &&
(Math.sin((c1+1) * 2 * Math.PI * freq / sampleRate) > 0) {
freq = newFreq;
cnt = 0;
userChangedFreq – false;
}
}

There’s still clicking, but it’s not as strong because the discontinuity has been removed. It’s still there, though, because the waveform is “bent” during its upward slope and that distortion in the waveform is being picked up somewhere along the pipeline between the mixer and the speakers.

If we look at the sinewave, the rising slope has the greatest delta change right where the line crosses the x axis. Therefore, a second step that can be taken to reduce the impact of changing frequencies is to wait until the delta change is smallest – when the line is at its peak. This is also the point when the cosine wave is crossing 0 from positive to negative. Then, we would have to reset the cnt counter to a value that translates to 90 degrees for the sine part. The number of samples per cycle = samples per second / frequency. For a 200 hz signal, samples per cycle = 16000 / 200 = 80. The 0 degree point is at cnt = 0; the 180 degree point is 80 / 2, or cnt = 40; and the 90 degree point is cnt = 20.

if(userChangedFreq) {
if((Math.cos(c1 * 2 * Math.PI * freq / sampleRate) > 0) &&
(Math.cos((c1+1) * 2 * Math.PI * freq / sampleRate) < 0) {
freq = newFreq;
cnt = (16000 / freq) / 4;
userChangedFreq – false;
}
}

Well, there’s still clicking if you’re changing frequencies in larger than 10 hz steps, but it’s almost unnoticeable at 5 hz increments for low frequencies. If we look at the waveform now, we can see that the effect of changing frequency has been lessened, but there’s still a flattened area near the peak. This can be further minimized by changing the transition check from when the line is about to cross 0, to the point where it’s just finished crossing zero:

if(userChangedFreq) {
if((Math.cos((c1 – 1) * 2 * Math.PI * freq / sampleRate) > 0) &&
(Math.cos(c1 * 2 * Math.PI * freq / sampleRate) < 0) {
freq = newFreq;
cnt = (16000 / freq) / 4;
userChangedFreq – false;
}
}

Looking at the curve now, the transition has become almost seamless. The clicking has virtually disappeared for frequencies under 300 HZ, and has been greatly softened even up around 1 KHz, where the seam has more of an impact. This is as good as I’m going to get at the current (16,000) sample rate.

Now, once we have the 0-crossing test in place, it’s a simple matter to count the number of crossings, and then add variables for max. crossings; steps between old frequency and new; and max. steps. Add max. crossings and max. steps to the oscillator class strVal() and setStr() methods, and you now have glide.

Glide is definitely undesirable in gate arpeggiator and LFO oscillators, because it causes a “stutter” effect when you’re doing things like changing the frequency for gating the ADSR, or driving a second oscillator for frequency sweeps. Therefore, I added an enableGlide boolean parameter to the oscillator constructor. However, even with glide disabled, I still need to change frequencies as described above, so I just set the glide counters to glide max. to trick the nextSlice() for-loops.

Over all, the code for the osc class is getting ugly.  I may not be doing everything in an optimal way. But, the resulting sound from the synth is getting pretty good.  Next is to clean up the engine and add panning. From there, I’ll tackle the GUI again to abstract the module controls further to give the user the option of designing their own module wiring without having to hardcode it within the Java app.

Raw formatted textfile.