E-Guitar synthesizer in 50 lines of Spin 2

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.