E-Guitar synthesizer in 50 lines of Spin 2
Wuerfel_21
Posts: 5,680
CON
SAMPLE_RATE = 44_100
_CLKFREQ = SAMPLE_RATE * 256 * 24
AUDIO_LEFT = 24+6 ' this pin and the next one will have audio
MAX_PERIOD = 16_000
VAR
long string_length
long buffer_ptr
long exciter_on
long retrigger_ctr
long note_cnt
word string_buffer[MAX_PERIOD]
PUB main() | sample, filter,seed, lpf, hpf
pinstart(AUDIO_LEFT addpins 1,P_DAC_124R_3V|P_DAC_DITHER_PWM|P_OE,CLKFREQ/SAMPLE_RATE,$7F80)
repeat
repeat until pinr(AUDIO_LEFT) ' wait for DAC ready
wypin(AUDIO_LEFT addpins 1,(sample sar 4)+$7F80) ' set sample
if --retrigger_ctr < 0 ' trigger new notes
retrigger_ctr := SAMPLE_RATE /4 ' time to next note
string_length := (SAMPLE_RATE / getnote()) <# MAX_PERIOD ' tune string
buffer_ptr := 0
exciter_on := 1
if exciter_on
sample := getrnd() signx 15 ' during first period, generate noise
else
sample := (string_buffer[buffer_ptr] signx 15) ' afterwards, read from buffer
sample := (sample + filter) sar 1 ' incrementally filter sample
filter := sample
sample := clamp(sample)
string_buffer[buffer_ptr] := sample
if ++buffer_ptr > string_length ' wrap buffer around
exciter_on := buffer_ptr := 0 ' also turn exciter off
if 1 ' E-Guitar amplifier simulation - disable for very dry sound
sample *= 3 ' gain knob
hpf += (sample-=hpf) sar 3 ' pre-amp AC coupling
sample := waveshape(sample)
lpf += (sample-lpf) sar 2 ' just an LPF to make it less harsh
sample := lpf
PRI getnote() : r
case (note_cnt+/12)+//3 ' simple arpeggio
0: r := lookupz(note_cnt+//3 : 262, 311, 392) ' C4 - Eb4 - G4
1: r := lookupz(note_cnt+//3 : 207, 262, 311) ' A3 - C4 - Eb4
2: r := lookupz(note_cnt+//3 : 233, 294, 349) ' Bb3 - D4 - F4
r >>= 1 ' transpose one octave down
note_cnt++
PRI waveshape(x) : r ' wave shaping using arctan transfer function
_,r := xypol($2000,x)
return clamp(r sar 15)
PRI clamp(x) : r ' clamp to valid 16 bit range
return x <# $7FFF #> -$7FFF
Something I wrote to pass an hour or so. It's a basic Karplus-Strong synth with a rudimentary tube amp simulation. It'd probably sound better if there were multiple channels and the decay of the previous note was allowed to continue. Or to play chords, of course.

Comments
This looks interesting. Is that complete? Looks like it needs some note data somewhere to play something?
Seems could be warped into MIDI player...
Yea, it just plays a little sequence of notes
Nice!
Once again cordic shines!
I am a great fan of asymmetric overdrive. So perhaps you might want to add some offset before the arctan, which will add 2nd harmonics and therefore enrich the sound in a nice way.
Also a little bit of even a very simple reverb can do much....
Christof
I dug this out again and played with it some more.
I may have went overboard.
Compile as
flexspin -2 karplus2.spin2 --compress- comes out to less than 4K of code!Pin config is also at the top of karplus2.spin2 (defaults to pins 54 and 55)
What this actually does is left as a suprise.
EDIT: added variant with slightly less annoying volume balance
lol understatement compared to the OP - cool though... I would've expected a lot more code, or PASM, but yeah this is really small (seems like most of it is the music data itself). I'm definitely not a musically inclined person but I've started to really get interested in synths, midi, etc and have (mostly unsuccessfully) tried muddling through making little bits of code related to each, so it's neat to take a look at the guts of something like this actually working.
Cheers
All ad-hoc code I just kinda threw together over the course of a (far too long) evening. If I was doing a proper synth object, it would probably be ASM and a little bit less hard-coded. OTOH it's fun to change those hardcoded values and add/remove code and listening to result.
Feel free to steal any of the code from this. Sorry there aren't more comments. The 0..127 note numbers are MIDI-spec and the routines to convert those into periods and NCO frequencies are probably useful to copy into any sort of music project.