Simple Soft Synth

Here’s a little polyphonic soft synth I’ve been hacking on, it might be useful to give a few pointers for people wanting to work with audio in Java. There’s no filter (yet?), but it does still have some nice bass/string sounds.

http://geocities.com/sunet2000/liquinth-a20.jar

If you can get it working you can either use MIDI or the keyboard to play notes. If using the keyboard make sure one of the sliders has focus first, use F1-8 to select the octave, and hit space or enter to release any stuck notes.

I know it’s a bit crappy, but it didn’t take too long to write, and I’m really happy with it. It has two saw/square oscillators per voice, and 8 voices. The oscillators themselves are not antialiased, but it does have 4x oversampling on the output, and the sound is pretty good.

Cheers,
Martin

Well that’s a good job keep it up. I only didn’t consider Java for sound because of the latency. Without ASIO and other low level drivers you don’t get the benefit being able to sustain 12ms through a full arranged song.

Yeah, this one is set up for 20ms, which is just about adequate. It still stutters a little bit on my machine.

Here’s a version with a Moog filter:

http://geocities.com/sunet2000/liquinth-a21.jar

Cheers,
Martin

That filter is HOTT; maybe you’d want to limit the pitch bend to one tone. And moving the filter with my modulation wheel sent some serious spikes out to the amp!

Sorry! Should have warned you about that :slight_smile:

Probably best to keep the overdrive cranked up when using large amounts of resonance. I thought about interpolating the lowpass sweep, but it would make the filter inner-loop a bit less efficient. I’ll see what I can do…

Cheers,
Martin

I’d say just implement some of your own hard+soft clipping on the output; that way nothing will every cause too much damage.

It actually has hard clipping already, so the spikes are never more than full-scale. Turning up the overdrive lessens the difference between the spikes and the audio, so they aren’t as annoying.

I’ve got a couple things I want to try to limit this. I think the cutoff should be on a logarithmic scale, which should improve things quite a bit, and make things more controllable. Most of the other controls have log scales already.

Another one would be to experiment with limiting the maximum change in cutoff frequency every 20ms, so any large changes are handled in smaller steps, but without the complexity of having to interpolate the lowpass (and recalculate a bunch of parameters) every sample.

Cheers,
Martin

I’ve added a log scale to the filter, now the interesting bits near the bottom are much easier to get to!
I’ve also set the maximum pitch bend to a semitone, as suggested :slight_smile: No other changes yet.

http://geocities.com/sunet2000/liquinth-a23.jar

Cheers,
Martin

All I can say is that the waveforms sound really nice; they are very rich. Although it was a tone I suggested, but thanks for taking it on board. It makes it much easier to do interesting pitch bends with it when it’s not so high.

So where are you going with this? All you need is sequencing and a little sampler and you’ve got yourself a mini workstation :slight_smile:

Yeah, it does sound much better than I expected!

I’ll change the pitch bend to a tone in the next release. I’ve not been using the pitch wheel, as you can probably guess. My keyboard skills are currently up to the level of playing the riff from “Cars” over and over again, something that this synth is actually quite good at :slight_smile:

EDIT:

I’ve been meaning to write a little tracker-type program for a while now, it might be nice to use this in the audio engine.

Sounds great! :smiley:
Are the sources available?

I’ve also started something like that some time ago, except that there was no MIDI support or keyboard support yet, it’s just a library for creating a modular synth in java. Everything can be connected however you like, and it does have some nice modules which might be of some use to you (RingModulator, VCF, ADSREnvelope LFO, FMOperator etc), and it works with 32bit float accuracy.
If you’re interested, I can send you the sources or upload it somewhere.

As always, the sources are in the jar file :slight_smile: It’s pretty well-organised, but it’s not particularly re-usable. – I designed it as more of a self-contained “sound box”. There’s not much more I want to add to this program, a filter envelope would be nice to have.

I wrote it in mostly fixed point arithmetic, because I had J2ME at the back of my mind, but also because fixed point makes more sense when interpolating. It’s noticably quicker, probably because indexing an array with a float requires both a modulo-division and a cast-to-int, which is quite heavy compared to a left shift.

( EDIT: Sorry, just occured to me, indexing the array doesn’t need modulo-division, but getting at the fractional part in order to eg. linear-interpolate between two samples does … )

And then there’s the prospect of denormalisation. When the FPU encounters a really small number, it switches into a different mode, which can really kill performance, apparently. Not a problem when interpolating, but it needs to be considered, you don’t want a denormalised number in one of your filter coefficients, for example.

I got the filter code from musicdsp.org, and that has so far resisted my attempts to integerize it. I managed to make it work using long integers with 24-bit fractional accuracy, but the sound quality was not as good – it’s quite flat and muddy. The code is also really ugly compared to the floating point version, although it seems to run at about the same speed, possibly slightly faster. I think I’ll bite the bullet and go to floating point where it makes sense in this program, ie. keeping the oscillators in fixed-point but replacing the use of sine/log tables with floating point – it will make the code more readable.

erikd: I can’t promise I’ll do anything with it, but by all means send me the library. Maybe I’ll have a go at wiring the MIDI code up to it :slight_smile:

Most mobile devices don’t have hardware divides, which makes them dead slow. The GBA didn’t have hardware divides either, so a lot of tricks are used for 3D.

Fortunately it’s pretty easy to avoid division in audio, the only use of division in the synth that I can think of is in the pitch-bend wheel code (1 semitone is 1/12 of an octave). There’s also some in the envelope code, to calculate the number of samples in 1ms (even that could be replaced with a shift by approximating 1ms to 1/1024 seconds).

(EDIT: Heh, actually division is used both in the LFO and the envelopes, and somewhat differently to how I thought. I don’t know my own code, and I’ve just written it :slight_smile:

In fairness to the GBA it wasn’t really intended for 3D :slight_smile: And the provision of a single-cycle multiply-add unit was very generous of them. They could easily have used a 68000 instead. To be honest, that would probably have resulted in better games :slight_smile:

Couldn’t get any new features working tonight, but I’ve set the pitch bend to a full tone, and the filter slider has had further tweaks – the previous version didn’t allow it to be fully opened, so the sound is now back to its usual brightness!

http://www.geocities.com/sunet2000/liquinth-a24.jar

Massive improvements to the filter, the lowpass is interpolated on a per-sample basis, now it sounds as smooth as silk :slight_smile: This should also completely solve the feedback problems.

There’s also a simple filter envelope. I didn’t want to make this synth too complicated with loads of sliders so it only has a decay phase. Even so, it’s enough for me. There’s a big range of sounds possible now!

See what you think.

http://www.geocities.com/sunet2000/liquinth-a25.jar

Cheers,
Martin

Very cool :slight_smile:
I’m going to check if I can get it to work with Cubase too :smiley:

[quote]erikd: I can’t promise I’ll do anything with it, but by all means send me the library. Maybe I’ll have a go at wiring the MIDI code up to it
[/quote]
That’s okay, I’ve implemented it myself now, and it can now play MIDI files too.
I’ll upload it later, but I’ve got some issues to sort out first (it’s far too slow at the moment, only being able to play about 16 stereo voices with 2 oscillators, 2 LPF’s, and 2 LFO’s each in real time on a 2GHz machine. It should be easily possible to have at least 64 voice polyphony with 4 oscillators each imho).

Yeah, easily. This synth running 16 oscillators @ 192khz (internally) takes 2% of my 1.5ghz Sempron. The filter takes about 6% on top of that. Modern computers are insanely quick!

I try to calculate everything in as big chunks as possible, and minimise memory usage. There are 2 buffers in the synth, one for the mixing and oversampling, and a byte buffer for writing to the audio device.

Here’s another version. I’ve added a portamento control, and extended the per-sample smoothing technique to the pitch and amplitude of the oscillators. The changes are pretty invasive, so I hope I haven’t broken anything too badly :slight_smile:

http://geocities.com/sunet2000/liquinth-a26.jar

Cheers,
Martin

The portamento is cool, great work! 8)

[quote]Quote from: erikd on June 21, 2007, 07:35:11 am
I’ll upload it later, but I’ve got some issues to sort out first (it’s far too slow at the moment, only being able to play about 16 stereo voices with 2 oscillators, 2 LPF’s, and 2 LFO’s each in real time on a 2GHz machine. It should be easily possible to have at least 64 voice polyphony with 4 oscillators each imho).

Yeah, easily. This synth running 16 oscillators @ 192khz (internally) takes 2% of my 1.5ghz Sempron. The filter takes about 6% on top of that. Modern computers are insanely quick!
[/quote]
I’m guessing that my performance issue is caused by the massive amount of calls through java interfaces that’s slowing things down (my lib is basically a collection of little building blocks like oscillators, filters etc which you can connect in every way you like). Maybe I’ll try to create a more hardcoded synth and see if that makes things fast.

Thanks!

Obviously I haven’t seen your design, but I don’t think you really need to hardcode anything to avoid the call-overhead (if indeed that is the problem). For example if you are feeding an LFO into an oscillator, you can generate the LFO samples into a temporary buffer the size of 1 tick (ie 10-20ms) and use that buffer in the interpolation loop for the oscillator. With a few hints to the LFO you could probably get the it to generate the logarithmic step-values directly and simply read them out once per sample in the oscillator inner loop. A scheme to reuse the temporary buffers and keep everything cached would be a good idea but it should be both flexible and quick.

Cheers,
Martin