;; ;; tiny sid-like subtractive synth for 4k intro ;; copyright (c) 2008-2009 noora halme ;; ;; defines and structures ;; ; output sample rate %define OUTPUTFREQ 44100 ; sid control register %define CR_GATE 0 %define CR_SYNC 1 %define CR_RINGMOD 2 %define CR_TEST 3 %define CR_TRIANGLE 4 %define CR_SAW 5 %define CR_SQUARE 6 %define CR_NOISE 7 ; internal control bits in cr msb %define CR_TRIG 8 ; filter mode register (bits 0-3 are resonance) %define FM_LOWPASS 4 %define FM_BANDPASS 5 %define FM_HIGHPASS 6 ; sid voice data structure struc sid .cr: resw 1 .ad: resb 1 .sr: resb 1 .pw: resb 1 .vol: resw 1 .freq: resw 1 .fc: resd 1 .res: resw 1 .fm: resb 1 .osc_acc: resd 1 .env_acc: resd 1 .nr: resd 1 .lpf: resd 1 .hpf: resd 1 .bpf: resd 1 ;; ;; initialized data ;; .data ; output sample rate outputfreq dd OUTPUTFREQ ; constants used in filter coefficent calculations pd_ff_t1 dd 11.6 ;5.8 pd_ff_t2 dw 30 ; constants used in waveform attenuation pd_volscale dd 1056964545.0 ; maxenv*maxvol ; noise register bits used for noise waveform output pd_nr_bit_table db 2, 4, 7, 11, 13, 16, 20, 22 ; env ramp deltas precalced for 44khz "sid clock" pd_inc_ramp dd 190217,47554,23777,15217,10011,6793,5594,4755 dd 3804,1521,760,475,380,126,76,47 pd_dec_ramp dd -63405,-15851,-7925,-5283,-3337,-2264,-1864,-1585 dd -1268,-507,-253,-158,-126,-42,-25,-15 ;; ;; uninitialized data ;; .bss ; temporary store for one 32bit float sample tmpsample resd 1 ;; ;; code ;; .code ; _sid_output ; ; input: esi ptr to sid voice struct ; output: st0 sample (in [-1.0, 1.0] domain if clipping enabled) ; _sid_output: pushad .dco: ; phase accumulated dco (44.1Khz "clock") ; t=(v->freq*0x8000)/OUTPUTFREQ; t*=256; v->osc_acc+=t; ; v->osc_acc&=0x00ffffff; mov ecx, [byte esi+sid.osc_acc] ; save old accumulator to ecx xor edx, edx movzx eax, word [byte esi+sid.freq] shl eax, 15 mov ebx, [outputfreq] div ebx shl eax, 8 add eax, ecx ;[esi+sid.osc_acc] mov [byte esi+sid.osc_acc], eax ; test if we need to shift the noise register bt eax, 19 jnc short .noise_noshift xor eax, ecx ; bit 19 is high. xor with old acc bt eax, 19 jnc short .noise_noshift ; bit 19 went low->high so shift nr mov eax, [byte esi+sid.nr] ;v->nr = (v->nr<<1) | (bit(v->nr,22)^bit(v->nr,17)); xor ebx, ebx shl eax, 1 bt eax, 23 adc eax, ebx bt eax, 18 adc ebx, ebx xor eax, ebx mov [byte esi+sid.nr], eax .noise_noshift: .wave: ; waveform generator mov eax, 0x800 mov dl, [byte esi+sid.cr] and dl, 0xf0 jz short .dac .wave_enabled: mov ax, 0x0fff .wave_square: ; square/pulse test dl, 1<osc_acc>>16)>v->pw) ? 0x0000 : 0x0fff; mov cl, [byte esi+sid.osc_acc+2] ; endian abuse xor bx, bx sub cl, [byte esi+sid.pw] sbb bx, bx and ax, bx .wave_saw: ; sawtooth test dl, 1<osc_acc>>12)&0x0fff; mov ebx, [byte esi+sid.osc_acc] shr ebx, 12 and bx, 0x0fff and ax, bx .wave_triangle: ; triangle test dl, 1<osc_acc>>11)&0xffe); ; w^=(v->osc_acc&0x00800000)?0x0ffe:0x0000; mov ebx, [byte esi+sid.osc_acc] shr ebx, 11 and bx, 0x0ffe bt dword [byte esi+sid.osc_acc], 23 jnc short .wave_triangle_skip xor bx, 0x0ffe .wave_triangle_skip: and ax, bx .wave_noise: ; noise test dl, 1<nr,22) << 11) | (bit(v->nr,20) << 10) | (bit(v->nr,16) << 9) | (bit(v->nr,13) << 8) | ; (bit(v->nr,11) << 7) | (bit(v->nr, 7) << 6) | (bit(v->nr, 4) << 5) | (bit(v->nr, 2) << 4); xor edx, edx xor ebx, ebx mov ecx, 8 .wave_noiseloop: mov bl, [nr_bit_table+ecx-1] bt dword [esi+sid.nr], ebx adc dx, 0 shl dx, 1 loop .wave_noiseloop shl dx, 3 and ax, dx .dac: ; "dac" - waveform d/a conversion and dc bias adjust :) ; s=(float)(w)/2048.0f-1.0f; s+=0.000244f; mov [tmpsample+36], ax fild word [tmpsample+36] fidiv word [sid_wavegen_smax] fadd dword [sid_wavegen_bias] ; st0 = "analog" sample .env: ; linear adsr envelope generator mov cx, [byte esi+sid.ad] ; abuse small endianness, ch:cl = sr:ad lea edx, [inc_ramp] ; load inc_ramp table start address mov ax, [byte esi+sid.cr] test al, 1<fc*5.8+30) / OUTPUTFREQ ); fild word [byte esi+sid.fc] ; lsw only matters fmul dword [ff_t1] fiadd word [ff_t2] fldpi fmulp st1 fidiv dword [outputfreq] fsin fadd st0, st0 ;q=(float)(v->res)/15.0f; mov bl, [byte esi+sid.fm] and bx, 0x0f mov [byte esi+sid.res], bx fild word [byte esi+sid.res] fadd st0 ;mul res by two ; div by 30 fidiv word [ff_t2] ;r=sqrt(q); fld st0 fsqrt ;v->lp = v->lp + f*v->bp; fld dword [byte esi+sid.bpf] fld st0 fmul st4 fadd dword [byte esi+sid.lpf] fst dword [byte esi+sid.lpf] ;v->hp = r*s - v->lp - q*v->bp; fld st2 fmul st6 fsub st1 fld st4 fmul st3 fsubp st1 fst dword [byte esi+sid.hpf] ;v->bp=f*v->hp + v->bp; fld st5 fmul st1 fadd st3 fstp dword [byte esi+sid.bpf] ; add filter outputs to final output according to fm register fninit ; make some more room in the stack fldz test al, 1<