How to build a string synth

solina1

The Eminent Solina is a fameous string synthesizer from the 70’s.
It is based on a paraphonic oscillator, a simple envelope and a phaser.

solina6

The oscillators in the original was a divide down structure where you generate the frequencys for the top octave and divide those down by modulo 2 for the rest of the octaves.

We can’t really use a signal for every key so we use an alogorithm for generating 8 Saw waves and distribute those among the keys played on the keyboard.

Paraphonic means it’s kind of polyphonic but shares some parts of the soundpath.
In this case it’s the envelope shared by all the keys played on the keyboard.

So what does it sound like?


From http://bloghoskins.blogspot.com/2016/11/diy-arduino-string-synth.html

Pretty nice eh?

So what do we need to build our string synth?

solina8

An Arduino Nano, 3 potentiometers and a keyboard is all that is required.
I’ll show the full recipe but it’s very simple.

The schematics you need for the build is:

solina3

Don’t be frightened by the huge amount of diodes, it’s the keyboard and they are built in.

The keyboard used is a Miditech Gargage keys Mini.
It has an 8x5x2 matrix but we only use the 8×5 matrix because the x2 is for measuring velocity and we are not implementing that.

http://www.thomann.de/se/miditech_garagekey_mini.htm

solina7

You also need 3 potentiometers used for modulation, ensemble (phaser) and envelope.
The envelope use a preset countour like the Roland JD-Xi where only a single knob sets all of the Attack, Decay, Sustain and Release parameters. It selects among commonly used envelopes.

solina9

The pots can be of any value as long as they are linear and larger than 5Kohm.

There is a simple filter in the schematics. It’s used for smoothing the PWM signal output by the Nano. You can skip that if you want to be cheap but I recommend that you use it.

Values for the filter components are 1Kohm/100nF.

solina4

So I know how to build it, on with the code.

For any code modelled synthesis we start by modelling the oscillators.
In the Solina we have 8 oscillator but we structure them 2 by 2 so that we can use some detune modulation for a thicker sound. So it’s 4 voice polyphonic (or paraphonic).

The code for the oscillators:

//——————– 8 DCO block ——————————————

DCO=0;
for (uint8_t i=0;i<8;i++) {
if (integrators[i]) integrators[i]–; //Decrement integrators
DCOPH[i] += FREQ[i]; //Add freq to phaseacc’s
if (DCOPH[i]&0x800000) { //Check for integrator reset
DCOPH[i]&=0x7FFFFF; //Trim NCO
integrators[i]=28; //Reset integrator
}
DCO+=integrators[i];
}

writepointer++;
delayline[writepointer]=DCO;
DCO+=(delayline[(writepointer-lfoval2)&255]*PHASERMIX)>>8;

//——————————————————————– ——-

The first part is the 8 Saw DCO code that create the signal for our 8 oscillators and sum them together.

What is an Integrator?
In the analog World it creates a slope from an impulse.
Great. That is our Saw shape.

Why not use the DCOPH directly as it is saw shaped?
The integrator solution creates a variable sample rate oscillator that doesn’t drop samples so the aliasing is largely reduced. There is still some jitter from the phase accumulator but a much better sound.

The second part is a phaser.
A phaser is a delayline of about 50mS where the delaytime is modulated by a LFO.

The output from this delayline is also added to the signal and one of the pots sets the amount of mixed in phaser.

The next part is the envelope and the VCA or volume part of the sound:

//—————— VCA block ————————————

#define M(MX, MX1, MX2) \
asm volatile ( \
“clr r26 \n\t”\
“mulsu %B1, %A2 \n\t”\
“movw %A0, r0 \n\t”\
“mul %A1, %A2 \n\t”\
“add %A0, r1 \n\t”\
“adc %B0, r26 \n\t”\
“clr r1 \n\t”\
: \
“=&r” (MX) \
: \
“a” (MX1), \
“a” (MX2) \
: \
“r26″\
)

if ((ATTACK==255)&&(TRIG==1)) VCA=255;
if (!(envcnt–)) {
envcnt=20;
if (VCA<volume) VCA++;
if (VCA>volume) VCA–;
}
M(ENV, (int16_t)DCO, VCA);
OCR2A = ENV;

//—————————————————————–

There is some AVR assembler in this and thats because C++ is not fast enough to do 16x8bit signed multiplication for every sample in our waveform in realtime. You don’t need to understand it. It works and is fast.

The last part of our analog modell is the LFO and ENV:

//—————————— LFO Block ———————–

lfocounter+=LFO;
lfoval=(pgm_read_byte_near( sinetable + lfocounter ) * MOD)>>10; //LFO for pitch
lfoval2=pgm_read_byte_near( sinetable + (lfocounter2++) ); //LFO for the Phaser

//—————————————————————–

//——————— ENV block ———————————

if ((TRIG==1)&&(volume<255)) {
volume+=ATTACK;
if (volume>255) volume=255;
}
if ((TRIG==0)&&(volume>0)) {
volume-=RELEASE;
if (volume<0) volume=0;
}

//—————————————————————–

The LFO is just a low frequency oscillator that reads the sine table we defined at a slow rate (less than 20Hz) and changes the samplerate with that value.

The ENV sets the conture or envelope (volume in time) of our sound according to the pot and our preset envelope tables.

It changes from long attack/long decay through fast attack/fast decay to fast attack/long decay.

The code also contains a lot of glue to make it work.
One important thing is the keyboard scanner:

//—————— Key scanner —————————–
PORTC|=0x1F;
if ((k&0x38)==(0x00<<3)) PORTC&=B11111110;
if ((k&0x38)==(0x01<<3)) PORTC&=B11111101;
if ((k&0x38)==(0x02<<3)) PORTC&=B11111011;
if ((k&0x38)==(0x03<<3)) PORTC&=B11110111;
if ((k&0x38)==(0x04<<3)) PORTC&=B11101111;
keytable[k]=digitalReadFast((k&7)+2);
if (oldkeytable[k]!=keytable[k]) { //Handle keyevent
oldkeytable[k]=keytable[k];
if (keytable[k]==0) {
handleMIDINOTE(0x90,k+21,127);
}
else {
handleMIDINOTE(0x80,k+21,0);
}
}
k++;
if (k==40) {
k=0;
}
digitalWriteFast(10,TRIG);
//—————————————————————

This scans the keyboard, one key at the time by placing a column line low and finding out if a row input is low.

The keyboard gate signal is also output on D10 if you want to use external filters etc and gate an external envelope.

The code is MIDI aware but if you need MIDI input, add this function to the code:

ISR(USART_RX_vect) {
uint8_t MIDIRX;

MIDIRX = UDR0;

/*
Handling “Running status”
1.Buffer is cleared (ie, set to 0) at power up.
2.Buffer stores the status when a Voice Category Status (ie, 0x80 to 0xEF) is received.
3.Buffer is cleared when a System Common Category Status (ie, 0xF0 to 0xF7) is received.
4.Nothing is done to the buffer when a RealTime Category message is received.
5.Any data bytes are ignored when the buffer is 0.
*/

if ((MIDIRX>0xBF)&&(MIDIRX<0xF8)) {
MIDIRUNNINGSTATUS=0;
MIDISTATE=0;
return;
}

if (MIDIRX>0xF7) return;

if (MIDIRX & 0x80) {
MIDIRUNNINGSTATUS=MIDIRX;
MIDISTATE=1;
return;
}

if (MIDIRX < 0x80) {
if (!MIDIRUNNINGSTATUS) return;
if (MIDISTATE==1) {
MIDINOTE=MIDIRX;
MIDISTATE++;
return;
}
if (MIDISTATE==2) {
MIDIVEL=MIDIRX;
MIDISTATE=1;
if ((MIDIRUNNINGSTATUS==0x80)||(MIDIRUNNINGSTATUS==0x90)) handleMIDINOTE(MIDIRUNNINGSTATUS,MIDINOTE,MIDIVEL);
//if (MIDIRUNNINGSTATUS==0xB0) handleMIDICC(MIDINOTE,MIDIVEL);

return;
}
}

return;
}

And these global variables:

volatile uint8_t MIDISTATE=0;
volatile uint8_t MIDIRUNNINGSTATUS=0;
volatile uint8_t MIDINOTE;
volatile uint8_t MIDIVEL;

The potentiometers also needs to be scanned:

//————— ADC block ————————————-

while (bit_is_set(ADCSRA, ADSC)); //Wait for ADC EOC

if (MUX==7) DETUNE=((ADCL+(ADCH<<8))>>3);
if (MUX==7) MOD=((ADCL+(ADCH<<8))>>2);

if (MUX==6) PHASERMIX=((ADCL+(ADCH<<8))>>2);
if (MUX==5) ENVELOPE=((ADCL+(ADCH<<8))>>5);
if (MUX==5) ATTACK=ATTrates[ENVELOPE];
if (MUX==5) RELEASE=RELrates[ENVELOPE];

if (RELEASE==255) GATED=0;
if (RELEASE!=255) GATED=1;
if (DETUNE!=olddetune) {
olddetune=DETUNE;
for (uint8_t i=0;i<4;i++) {
if (FREQ[i<<1]) {
FREQ[(i<<1)|1]=FREQ[i<<1]+(((FREQ[i<<1]/50)>>0)*DETUNE/127);
}
}
}

MUX++;
if (MUX>7) MUX=5;
ADMUX = 64 | MUX; //Select MUX
sbi(ADCSRA, ADSC); //start next conversation

//——————————————————————–

They are read one at a time and the analog voltage is interpreted.
Again the low level hardware of the AVR is accessed directly to avoid the overhead of C++ and the Arduino librarys.

solina5

Finally our modelled signal is output by using one of the Nano pins used as a DAC at 62.5KHz PWM.

OCR2A = ENV;

That’s it.
That’s all that is requred to make a really cool 37-key string synth.

An it uses 3.5mm jacks for the audio and gate outputs so it can be plugged into your modular gear and used as a paraphonic keyboard saw oscillator.

And I hope I inspired you to hack some cool keyboards of your own?

You can download the full source code for upload in an Arduino Nano

Solina Sourcecode

My work on these free synthesizers is based on donations from people.
If you find the code useful, please consider a $3 donation to keep future developments open source.

Donate $3

Chips for building DIY synthesizers can be found at http://www.dspsynth.eu

I’m preparing a kit, including the keyboard, for building the Noob String Synth. If you are interested in a kit, please send an email to contact@dspsynth.eu Once I have 10 interested for the kit I will proceed with putting them together. A kit would be $250 including shipping.

 


%d bloggers like this: