
@;
@;  synth.s - Synthesiser sample generators
@;
@;  created 9/1/21

@; This assembler code is a bit faster than the c version but there really
@; isn't much in it.

@; These equates must agree with the defines in main.h kbd.h and instrument.h

.set NOISE_SCALE,      16  @; to produce halfword noise output at a reasonable level
.set SCALE_BITS,       15  @; signed short int fractional bits
.set VLQ_BITS,         10  @; number of bits for master volume and filter resonance
.set FIX,              22  @; waveform pointer fractional bits
.set MIDI_PARAM_SCALE, 14  @; midi parameter scale
.set SYNTH_SCALE,       2  @; volume reduction when accumulating o/p
.set GEN_SIZE,        284  @; sizeof(gen_t)

@; syn element offsets
.set SEED,              0  @; syn.seed offset
.set NUM_GENS,          4  @; syn.num_gens offset
.set GEN,               8  @; syn.gen offset

@; syn.gen[i] element offsets
.set z1,                0  @; filter history     )
.set z2,                4  @; filter history     ) updated by the synth_generators
.set tone1_pointer,     8  @; wavetable pointer  )
.set tone2_pointer,    12  @; wavetable pointer  )
.set waveform1,        16  @; wavetable address
.set waveform2,        20  @; wavetable address
.set tone1_inc,        24  @; pointer increment
.set tone2_inc,        28  @; pointer increment
.set tone1_volume,     32  @; envelope + modulation, linear multiplier (Q15)
.set tone1_vol_vel,    36  @; channel volume + velocity, linear multiplier (Q15)
.set tone2_volume,     40  @; envelope + modulation + channel volume + velocity, linear multiplier (Q15)
.set fm_depth,         44  @; linear multiplier
.set noise_volume,     48  @; envelope + modulation + channel volume + velocity, linear multiplier (Q15)
.set filter_fc,        52  @; filter cutoff (Q10)
.set filter_q,         56  @; filter resonance (Q10)
.set switches,         60  @; bit field
.set left_vol,         64  @; channel pan + balance + instrument gain, linear multiplier (Q15)
.set right_vol,        68  @; channel pan + balance + instrument gain, linear multiplier (Q15)

@; switches bits
.set LOWPASS,           1  @; lowpass filter
.set BANDPASS,          2  @; bandpass filter
.set HIGHPASS,          4  @; highpass filter
.set FILTERED_TONE,     8  @; filtered tone generator
.set TONE1_OUT,        16  @; tone 1 output enable
.set TONE2_OUT,        32  @; tone 2 output enable
.set TONE_RING_MOD,    64  @; tone 1 and tone 2 ring modulation
.set ACTIVE,       (1<<31) @; set if generator active

    .text
    .align 2

step: .word 0x1d872b41             @; noise step

@; void synth_generators(syn_t* syn, int output[])   r0 = &syn, r1 = &output
    .global synth_generators
synth_generators:
    stmfd  sp!, {r4-r12, lr}
    sub sp, sp, #12
    str r0, [sp, #0]               @; &syn
    str r1, [sp, #4]               @; output[]
    ldr r1, [r0, #NUM_GENS]        @; syn.num_gens
    mov r2, #GEN_SIZE
    mla r2, r1, r2, r2             @; (gen size) * (num_gens+1)
    sub r12, r0, #(GEN_SIZE-GEN)   @; gen[-1] address
    add r2, r2, r12
    str r2, [sp, #8]               @; gen[num_gens] address
    ldr r11, [r0, #SEED]           @; syn.seed
    @; r12 = &gen[-1] (start here as we point to next gen first in the loop
    @; r11 = seed
    @; [sp+0] = &syn
    @; [sp+4] = &output[]
    @; [sp+8] = &syn.gen[syn.num_gens] (end address)

loop:
    add r12, r12, #GEN_SIZE        @; point to next gen
    ldr r0, [sp, #8]               @; &gen end
    cmp r12, r0                    @; last gen ?
    bcs exit                       @; exit if done all
    ldr r0, [r12, #switches]
    tst r0, #ACTIVE
    beq loop                       @; skip if not active

    add r10, r12, #tone1_pointer   @; start from tone1_pointer
    ldmia r10, {r0-r9}
    @; r0 = tone1_pointer
    @; r1 = tone2_pointer
    @; r2 = wave1
    @; r3 = wave2
    @; r4 = tone1_inc
    @; r5 = tone2_inc
    @; r6 = tone1_vol
    @; r7 = tone1_vol_vel
    @; r8 = tone2_vol
    @; r9 = fm_depth

    @; tone 1 generator
@; signed half word load
    movs lr, r0, lsr #FIX+1
    ldr lr, [r2, lr, asl #2]       @; tone1 = wave1[tone1_pointer >> FIX]
    movcc lr, lr, asl #16
    mov lr, lr, asr #16

    mul lr, r6, lr                 @; tone1 *= tone1_volume
    mov lr, lr, asr #SCALE_BITS    @; tone1 >>= SCALE_BITS
    add r0, r0, r4                 @; tone1_pointer += tone1_inc

    @; tone 2 generator
@; signed half word load
    movs r2, r1, lsr #FIX+1
    ldr r2, [r3, r2, asl #2]       @; tone2 = wave2[tone2_pointer >> FIX]
    movcc r2, r2, asl #16
    mov r2, r2, asr #16

    mul r2, r8, r2                 @; tone2 *= tone2_volume
    mov r2, r2, asr #SCALE_BITS    @; tone2 >>= SCALE_BITS
    add r1, r1, r5                 @; tone2_pointer += tone2_inc
    mla r1, r9, lr, r1             @; tone2_pointer += (tone1 * fm_depth)

    @; apply tone1_vol_vel after fm modulation
    mul lr, r7, lr                 @; tone1 *= tone1_vol_vel
    mov lr, lr, asr #MIDI_PARAM_SCALE @; tone1 >>= MIDI_PARAM_SCALE

    stmia r10, {r0-r1}             @; store tone1/2 pointers

    @; lr = tone1
    @; r2 = tone2

    add r10, r12, #noise_volume    @; start from noise_volume
    ldmia r10, {r4-r9}
    @; r4 = noise_volume
    @; r5 = filter_fc
    @; r6 = filter_q
    @; r7 = switches
    @; r8 = left_vol
    @; r9 = right_vol

    @; select tone output
    mov r3, #0                     @; tone,
    tst r7, #TONE1_OUT
    addne r3, r3, lr               @; tone, tone1
    tst r7, #TONE_RING_MOD
    mulne r10, lr, r2              @; tone1 * tone2
    addne r3, r3, r10, asr #SCALE_BITS-1
    tst r7, #TONE2_OUT
    addne r3, r3, r2

    @; noise generator
    movs r11, r11, lsl #1
    ldrcs r10, step
    eorcs r11, r11, r10            @; seed ^= step
    mov r10, r11, asr #NOISE_SCALE @; input = seed
    mul r10, r4, r10               @; input *= noise_volume
    mov r10, r10, asr #SCALE_BITS

    @; route tone output
    mov r4, #0                     @; sample
    tst r7, #FILTERED_TONE
    addne r10, r10, r3             @; input += tone
    addeq r4, r4, r3               @; sample += tone

    @; state variable filter
    ldmia r12, {r0-r1}             @; z1 z2
    muls r2, r0, r5                @; lp = z1 * filter_fc
    addmi r2, r2, #(1<<VLQ_BITS)
    add r2, r1, r2, asr #VLQ_BITS  @; lp = z2 + lp / (1<<VLQ_BITS)
    muls r3, r0, r6                @; hp = z1 * filter_q
    addmi r3, r3, #(1<<VLQ_BITS)
    sub r3, r10, r3, asr #VLQ_BITS @; hp = input - hp /(1<<VLQ_BITS)
    sub r3, r3, r2                 @; hp = hp - lp
    muls r1, r5, r3                @; bp = filter_fc * hp
    addmi r1, r1, #(1<<VLQ_BITS)
    add r1, r0, r1, asr #VLQ_BITS  @; bp = z1 + bp / (1<<VLQ_BITS)
    stmia r12, {r1-r2}             @; z1 = bp, z2 = lp

    @; select filter output
    tst r7, #LOWPASS
    addne r4, r4, r2               @; sample += lp
    tst r7, #BANDPASS
    addne r4, r4, r1               @; sample += bp
    tst r7, #HIGHPASS
    addne r4, r4, r3               @; sample += hp

    @; accumulate stereo output
    ldr r0, [sp, #4]               @; output[]
    ldmia r0, {r1-r2}
    mul r3, r4, r8
    add r1, r1, r3, asr #MIDI_PARAM_SCALE+SYNTH_SCALE
    mul r3, r4, r9
    add r2, r2, r3, asr #MIDI_PARAM_SCALE+SYNTH_SCALE
    stmia r0, {r1-r2}

    b   loop

exit:
    ldr r0, [sp, #0]               @; &syn start
    str r11, [r0, #SEED]           @; syn.seed
    add sp, sp, #12
    ldmfd  sp!, {r4-r12, pc}       @; return

