Shop OBEX P1 Docs P2 Docs Learn Events
E-Guitar synthesizer in 50 lines of Spin 2 — Parallax Forums

E-Guitar synthesizer in 50 lines of Spin 2

Wuerfel_21Wuerfel_21 Posts: 4,685
edited 2024-05-09 15:31 in Propeller 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

  • RaymanRayman Posts: 14,156

    This looks interesting. Is that complete? Looks like it needs some note data somewhere to play something?

    Seems could be warped into MIDI player...

  • @Rayman said:
    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

  • @Wuerfel_21 said:

    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.

    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

Sign In or Register to comment.