Java Synthesizer, Part 7 – Trigger

One of the more interesting things about the old-style analog synths was that the connections between modules were made using patch cables. As long as a particular feature of any given module had a jack, some other feature from some other module could be connected to it (matching up inputs with outputs, obviously). With more modern synths, the connection combinations have been greatly simplified, in part because few musicians need every single one, and partly to just keep the circuit designs simple. But with software, there’s no particular reason not to go back to the old approach, and allow the user to decide which permutations to make use of at any given moment.

So, I went back to my hardware schematic and said to myself “self, let’s feed a sawtooth wave into the ADSR sustain input”.

(200 hz tone; Attack = 10 ms; Decay = 10 ms; sawtooth sustain one-shot with 10ms rise time.)

This turned out to be a lot harder than I’d expected, because what I really wanted was a single cycle of the sawtooth. I could try matching up one cycle of an oscillator to the time I want the sustain level to change and then try to trigger the ADSR release phase, but it makes more sense to expand the circuit toolkit by adding a new module – the oneShot.

In electronics, there is a circuit called a flip-flop. It can be configured to free-run as an oscillator, or to trigger one time and after a preset period to reset. This latter operating mode can be called a “one-shot” (i.e. – it flips then flops once). And that’s what I wanted.

However, as I was building up the oneShot class, I realized that there was no way to reset it automatically at the end of the ADSR cycle, if my plan also included gate arpeggiating the ADSR. That is, if oscillator 1 creates my tone, and I run that into ADSR 1, and I add oscillator 2 to gate the ADSR, my trigger circuit would run once, stop, and then not run any more because the cnt member wouldn’t be zeroed with each cycle of oscillator 2.

The difficulty stems from the osc class not having a “gate edge” event output. Determining whether the oscillator is on the rising edge or falling edge is easy as long as you save the last gate state within the class, but I wasn’t doing that, either. So, I had to go back to the osc class and replace the gateOut() method and add the lastLevelState and lastEdgeState variables. With these, the oscillator can output a risingEdge or fallingEdge event.

The oneShot class is essentially the osc class, with the addition of starting and stopping values, and an inverted triangle waveform.

What I really want the oneShot to do is take over the last value from decay, ramp up or down during sustain, and then maintain the last value of sustain in order to use it for setting the value to use during the release phase. In the case of the sawtooth and reverse sawtooth waveforms, I’m actually outputting a rising or falling ramp wave. If I still want to use a sawtooth, then I can go back to the osc class and use that instead.

I’m starting to make use of constants, for soft-coding sampling rate and waveform numbers, too.

I’m still getting some clicking in the output audio, but through experience I’m realizing that it’s almost always because I’m doing something wrong to insert a cycle that either changes too fast, or goes to zero between operations for some reason (as when changing ADSR phases). But, I am slowly learning, and modifying my code to fix that as I go along.

The next example uses a oneShot to change the ADSR sustain level over time, and a second oscillator to gate arpeggiate the ADSR. vca2 is used for adjusting the max volume from osc2 between 0 and 1.0 for controlling the sustain times.

final private static int WAVEFORM_SINE     = 0;
final private static int WAVEFORM_SQUARE   = 1;
final private static int WAVEFORM_SAW      = 2;
final private static int WAVEFORM_REVSAW   = 3;
final private static int WAVEFORM_TRIANGLE = 4;
final private static int WAVEFORM_INVTRI   = 5;

osc      osc1     = new osc(200,   WAVEFORM_SINE, 0.5);                            // double freq, int waveform, rate
osc      osc2     = new osc(0.5,     WAVEFORM_SQUARE, 0.5);
oneShot  trigger1 = new oneShot(4000, WAVEFORM_INVTRI, 0.5);
ADSR     adsr1    = new ADSR(8000, 4000, 0.4, 8000);                              // double attack, double decay, double sustain, double release
vca      vca1     = new vca(16000, 0.0);
vca      vca2     = new vca(1, 0.0);
addBuffer() snippet:
for(int pCnt = 0; pCnt < 320; pCnt++){
adsr1.sustain = vca2.out(trigger1.nextSlice());
if(osc2.gateRising()) trigger1.resetTimer();
shortBuffer.put( (short)( vca1.out( adsr1.nextSlice(osc1.nextSlice() ) ) ) );

Raw formatted textfile.

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: