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.

Leave a comment

Leave a Reply

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

You are commenting using your 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: